Reading from bottom to top, “key” is a concatenation of:
- The content of the file “.htkey” in the $test_db->getTestSitePath() location; as explained earlier, if “.htkey” is a directory, “key_file” will be the empty string
- the creation time and inode number of the file “bootstrap.inc”
Keeping in mind that on Windows, PHP would return 0 as the file inode number, the only unknown value left is the creation time of “boostrap.inc”; everything else is under the control of the attacker.
To complicate matters slightly, the timestamp value needs to be within 600 seconds (10 minutes) with respect to server time.
To recap, building a valid “User-agent” string required:
- A “check string” composed of a valid site name (“simplesite5”), a timestamp within 600 seconds of the request we’re going to send, and a salt (arbitrary string)
- A “key”, generated by concatenating the creation time of “boostrap.inc” with the value 0
- A HMAC (message authentication code) of “check string” using “key”
As the only unknown, the creation time of “boostrap.inc” can be determined by estimating the installation time of the site and refined by brute-force. The required granularity is seconds.
For example, if the attacker can estimate the installation day, then brute-forcing the exact time would require 60 * 60 * 24 = 86,400 (60 seconds * 60 minutes * 24 hours) requests, which can easily be sent in parallel.
There might be other ways to know the installation time or time of the latest update of the Drupal site, for example public caches, scripts for uptime measurement or search engine indexes. Other exploits might reveal this information as well.
Creation of the “.htkey” directory
The “.htkey” directory could be created in several manners, for example:
- Through legitimate creation of files and folders via SFTP, if allowed by the hosting provider;
- By exploiting other vulnerabilities;
For this demonstration we’re going to exploit a vulnerability in a core module.
The vulnerable core module was “Interface Translation”, which contained a form that was vulnerable to Cross-Site Request Forgery (CSRF) attacks. Under normal circumstances, the form would be used to create folders to use for storing user-supplied translations of pages. By abusing this functionality, an attacker could create a malicious payload that would create the “.htkey” directory when viewed by an administrative user without the need for any interaction.
Once the directory had been created, it was possible to verify its presence by checking whether requesting it returns 403 or 404. An attacker would then be certain that their malicious web page would have succeeded and could then move to the next step: brute-forcing the creation time of the “boostrap.inc” file.
Brute-forcing creation time
With an estimate of the site installation time, an attacker could brute-force the creation time of the file “boostrap.inc” by using incremental values to calculate the “key” to compute the “hmac” part of the “User-Agent” HTTP header.
As the request can be carried out asynchronously, the brute-force requests could be run in parallel. Upon a successful request, the application would respond with a specific HTTP return code, indicating the right value had been found
Once found, the valid string can only be used for 600 seconds (10 minutes). However, it is trivial to re-compute it from a known creation time value, thus making the attack repeatable without the need to brute-force the time again.
Exploitation
The “test site” installation process can be fully automated; once the correct User-Agent string had been identified, the entire attack could be completed in less than a minute providing the site could reach the attacker-controlled database.
Once the site had been installed, the attacker would insert a malicious serialised object in the database and request a page on the site. By rendering the page, Drupal would write arbitrary content on disk (for example a PHP web shell), thus allowing the attacker remote code execution with the privileges of the user running the web server.
A variant of the known “Guzzle” gadget was used as a deserialisation payload, which abuses the destructor of the FileCookieJar class to write arbitrary strings in a cookie on disk.
The deserialised payload was injected in the database as a “dblog” entry, which is a database-backed log that shows events such as administrative log-ins. By visiting the URL of the event, the gadget writes a reverse shell to the root directory. Accessing the shell without the need for any special “User-Agent” string resulted in remote code execution with the privileges of the user running the web server.
Remediation
Install the latest updates from Drupal:
- Drupal 7.x: not vulnerable
- Drupal 8.x: install at least version 8.8.8 or 8.9.1
- Drupal 9.x: install at least version 9.0.1