On friday, 23th September 2016 we talked about a High Risk XSS vulnerability in W3TC, and like I said at the end of this post “security consultant will try to find more“, well, I did.
I did it because we, at WP Media, think it’s important for everyone to have a secure website, whatever the plugin they’re using. Every month we do some security audit on a few plugins from the free WordPress repository just to test, just to be sure that everyone is safe.
4 New Vulnerabilities
There is many different ways to find vulnerabilities in a plugin, sometimes you fall on them, sometimes you look for some bad patterns or echoing stuff.
For me, the goal was to find something very harmful so I focused on user’s file and PHP code.
You can find the 4 reports on wpvulndb:
https://wpvulndb.com/vulnerabilities/8626
https://wpvulndb.com/vulnerabilities/8627
https://wpvulndb.com/vulnerabilities/8628
https://wpvulndb.com/vulnerabilities/8629
Security Token ByPass
The /pub/apc.php
file is useful to empty the OPCache/APC. The script seems protected by a nonce (aka security token):
[pastacode lang=”php” manual=”%24nonce%20%3D%20W3_Request%3A%3Aget_string(‘nonce’)%3B%0A%24uri%20%3D%20%24_SERVER%5B’REQUEST_URI’%5D%3B%0A%0Aif%20(wp_hash(%24uri)%20%3D%3D%20%24nonce)%20%7B%0A” message=”3 lines from /pub/apc.php” highlight=”” provider=”manual”/]
But the flaw stays in the ==
operator which is not the one to use when you want to compare hashes because of PHP type juggling.
You can find an example of type juggling on 3v4l.org.
To exploit the vulnerability, the token has to start with 0e
and all other chars have to be numbers, then the user can just add a parameter in the url like ?nonce=0
and it will be validated.
Example of URL: http://example.com/wp-content/plugins/w3-total-cache/pub/apc.php?nonce=0&command=reload_files
How to get a nonce like 0e12345678
? You can’t guess, just be extra lucky.
DREAD Score: 0+1+8+0+8 = Low Risk
File Upload
Reading the vulnerability name is already harmful for me. Yes, you can upload files in W3TC support form.
The same support form with the XSS vulnerability, check this screenshot:
When you’re creating a support ticket, you can add one or more of your files from your computer, or whatever.
Then this file will be sent to the author to help him resolving your issue, good idea.
When we look at the code, W3TC does that:
[pastacode lang=”php” manual=”%20%20%20%20%20%20%20%20%2F**%0A%20%20%20%20%20%20%20%20%20*%20Attach%20other%20files%0A%20%20%20%20%20%20%20%20%20*%2F%0A%20%20%20%20%20%20%20%20if%20(!empty(%24_FILES%5B’files’%5D))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24files%20%3D%20(array)%24_FILES%5B’files’%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(%24i%20%3D%200%2C%20%24l%20%3D%20count(%24files)%3B%20%24i%20%3C%20%24l%3B%20%24i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(isset(%24files%5B’tmp_name’%5D%5B%24i%5D)%20%26%26%20isset(%24files%5B’name’%5D%5B%24i%5D)%20%26%26%20isset(%24files%5B’error’%5D%5B%24i%5D)%20%26%26%20%24files%5B’error’%5D%5B%24i%5D%20%3D%3D%20UPLOAD_ERR_OK)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24path%20%3D%20W3TC_CACHE_TMP_DIR%20.%20’%2F’%20.%20%24files%5B’name’%5D%5B%24i%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(%40move_uploaded_file(%24files%5B’tmp_name’%5D%5B%24i%5D%2C%20%24path))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24attachments%5B%5D%20%3D%20%24path%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A” message=”Lines 401 to 414 from /lib/W3/AdminActions/SupportActionsAdmin.php” highlight=”” provider=”manual”/]
[pastacode lang=”php” manual=”%20%20%20%20%20%20%20%20%2F**%0A%20%20%20%20%20%20%20%20%20*%20Remove%20temporary%20files%0A%20%20%20%20%20%20%20%20%20*%2F%0A%20%20%20%20%20%20%20%20foreach%20(%24attachments%20as%20%24attachment)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(strstr(%24attachment%2C%20W3TC_CACHE_TMP_DIR)%20!%3D%3D%20false)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%40unlink(%24attachment)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A” message=”Lines 508 to 514 from the same file” highlight=”” provider=”manual”/]
Ok, so, when you submit the form as an administrator, W3TC uploads our file in its temporary folder /wp-content/cache/tmp/
then will delete them right after that, the file will live only a few milliseconds.
But what if I try to send 2 files, the first one is a 2 Kb malicious PHP file containing a backdoor, the second one is a 20 Mb file. The submission will last more longer, the first file won’t be deleted since the second one is not uploaded, I can now access to the first file.
An administrator is not always allowed to execute custom PHP code, he’s not the webmaster but a WordPress administrator, so this represent a vulnerability.
DREAD Score: 10+8+6+2+8 = High Risk
File Download
Same chills for me, when I read this kind of flaw. In the same screen, same form you can also select files from your theme:
Now you select one, you send the form and same as for the files before, you will send it to the author to help him to fix the issue.
How does it work:
[pastacode lang=”php” manual=”%20%20%20%20%20%20%20%20%2F**%0A%20%20%20%20%20%20%20%20%20*%20Attach%20templates%0A%20%20%20%20%20%20%20%20%20*%2F%0A%20%20%20%20%20%20%20%20foreach%20(%24templates%20as%20%24template)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(!empty(%24template))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24attachments%5B%5D%20%3D%20%24template%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A” message=”Lines 392 to 399 from same file again” highlight=”” provider=”manual”/]
[pastacode lang=”php” manual=”%20%20%20%20%20%20%20%20foreach%20(%24attachments%20as%20%24attachment)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(is_network_admin())%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20update_site_option(‘attachment_’%20.%20md5(%24attachment)%2C%20%24attachment)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20else%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20update_option(‘attachment_’%20.%20md5(%24attachment)%2C%20%24attachment)%3B%0A%20%20%20%20%20%20%20%20%7D%0A” message=”Lines 476 to 481″ highlight=”” provider=”manual”/]
[pastacode lang=”php” manual=”%20%20%20%20%20%20%20%20%2F**%0A%20%20%20%20%20%20%20%20%20*%20Remove%20temporary%20files%0A%20%20%20%20%20%20%20%20%20*%2F%0A%20%20%20%20%20%20%20%20foreach%20(%24attachments%20as%20%24attachment)%20%7B%0A%2F%2F%20…%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(is_network_admin())%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20delete_site_option(‘attachment_’%20.%20md5(%24attachment))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20else%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20delete_option(‘attachment_’%20.%20md5(%24attachment))%3B%0A%20%20%20%20%20%20%20%20%7D” message=”Lines 508 to 519″ highlight=”” provider=”manual”/]
[pastacode lang=”php” manual=”%24attachment_location%20%3D%20filter_var(urldecode(%24_REQUEST%5B’file’%5D)%2C%20FILTER_SANITIZE_STRING)%3B%0A%24md5%20%3D%20md5(%24attachment_location)%3B%0A%24nonce%20%3D%20%24_REQUEST%5B’nonce’%5D%3B%0A%24stored_nonce%20%3D%20get_site_option(‘w3tc_support_request’)%20%3F%20get_site_option(‘w3tc_support_request’)%20%3A%20get_option(‘w3tc_support_request’)%3B%0A%24stored_attachment%20%3D%20get_site_option(‘w3tc_support_request’)%20%3F%20get_site_option(‘attachment_’%20.%20%24md5)%20%3A%20get_option(‘attachment_’%20.%20%24md5)%3B%0A%0Aif%20(file_exists(%24attachment_location)%20%26%26%20%24nonce%20%3D%3D%20%24stored_nonce%20%26%26%20!empty(%24stored_nonce)%20%26%26%20%24stored_attachment%20%3D%3D%20%24attachment_location)%20%7B%0A” message=”Lines 28 to 34 from /pub/files.php” highlight=”” provider=”manual”/]
First, our choices are added to the attachments array, secondly an option is added, this will be used to be sure that this file was chosen from this support form, then this options are deleted when the submission is done.
Between the option creation and deletion, the files.php
file is called to get the attachment, verified with a nonce and with the created option.
The vulnerability stays in the fact that we can modify – using FireBug for example – the templates name to another existing file from the site, like wp-config.php
:
So now, an option has been created with this fake theme template. Then using the same type juggling flaw as before, I can validate the nonce because of the ==
.
You also have to add a 20 Mb file to gain time to exploit this.
Pointing on the files.php
URL you can now download the wp-config.php
, because for the same reason as before, an administrator is not always allowed to read the config file, he’s not the webmaster but a WordPress administrator, so this represent a vulnerability.
Example of URL: http://example.com/wp-content/plugins/w3-total-cache/pub/files.php?file=/Users/julio/Sites/wpsolo/wp-config.php&nonce=0
DREAD Score: 10+2+4+2+6 = Low Risk
PHP Eval
The worst for you is to allow a user, even an administrator, to eval any PHP code.
Using the File Upload flaw is a lookalike one, but because of the type juggling, it’s not possible to exploit it easily.
This one is so much easy to exploit using the import settings feature:
What W3TC will do once your file is uploaded? Check it:
[pastacode lang=”php” manual=”%20%20%20%20%2F**%0A%20%20%20%20%20*%20Imports%20config%20content%0A%20%20%20%20%20*%0A%20%20%20%20%20*%20%40param%20string%20%24filename%0A%20%20%20%20%20*%20%40return%20boolean%0A%20%20%20%20%20*%2F%0A%20%20%20%20function%20import(%24filename)%20%7B%0A%20%20%20%20%20%20%20%20if%20(file_exists(%24filename)%20%26%26%20is_readable(%24filename))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24data%20%3D%20file_get_contents(%24filename)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(substr(%24data%2C%200%2C%205)%20%3D%3D%20’%3C%3Fphp’)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24data%20%3D%20substr(%24data%2C%205)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%24config%20%3D%20eval(%24data)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(is_array(%24config))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20foreach%20(%24config%20as%20%24key%20%3D%3E%20%24value)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%24this-%3Eset(%24key%2C%20%24value)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%7D%0A” message=”The import() method from the W3_Config class” highlight=”” provider=”manual”/]
The bad line is $config = eval($data);
because it means that all my file content will be evaluated like any other PHP code. Basically we can send a PHP script that will create a backdoor.
DREAD Score: 10+8+8+2+10 = High Risk
W3TC 0.9.5
We contacted Frederick Townes to tell him about these vulnerabilities, he replied very quickly asking me to test the 0.9.5 zip file before sending it on the official repository.
So for now he has patched the free plugin on the repository already, we recommend you to update as soon as possible, but first check the support tab to be sure your site won’t crash.