During our regular cleanup process we came across a reinfection case that caught our attention.
This particular environment didn’t have anything special or fancy, it was an updated WordPress installation and had 3 out-of-date plugins; that’s pretty reasonable.
After running through our processes and cleaning the environment we kept coming back to a reinfection; the attacker kept uploading nefarious files on the server.
This got us very curious and so we had to dig a little deeper.
The malicious files were being uploaded to ‘/wp-content/uploads/gravity_forms’ and ‘/wp-content/uploads’ on Feb 21st, but how?
While Forensics is not a default offering and the client was not using our Website Firewall (which would have prevented the reinfection), we do love a good challenge. So why not investigate when the cases are curious – such as this one. Fortunately, we had access to the logs and were able to find some interesting requests to those particular files.
With that in mind, we started looking for different variations of “_input_” and found a lot requests to those files.
We went back a few days in the logs to find what preceded those requests and where they could have come from.
In analyzing the files, we came across these requests to “?gf_page=upload”; that sounds interesting doesn’t it?
We searched for that string in the file system and found an instance within the WordPress plugin GravityForms (out-dated version 1.8.19).
Gravity Forms is a WordPress plugin used originally for contact forms, but in a more general sense, it allows site owners to create forms to collect information. Gravity Forms can be used for contact forms, WordPress post creation, calculators, employment applications and more.
Written in PHP, Gravity Forms uses many WordPress built-in functions and features to power its form builder. It also uses the same MySQL database system as WordPress, but stores all forms and entries in its own tables.
Gravity Forms is open source and GPL licensed. All of the code included is unencrypted, and easy to modify. We’ve added in tons of hooks and filters to be able to customize Gravity Forms to your hearts content.
Upon further investigation, we found “?gf_page=upload” inside common.php in line 3635.
It was very interesting, not in a good way though; there was no sanitization of that request.
For testing purposes, I requested “?gf_page=upload” to see what would happen and interestingly enough, we got this:
Which lead to searching where the message was being processed.
From checking the upload.php, we see that $_REQUEST[“form_id”] has to be set otherwise the upload fails.
Keep in mind that at this time, we already bypassed any protections that could prevent unauthorized users from accessing that resource.
I set the value of form_id to 1 and made another request in a crafted upload form and this time the error was a little bit different.
As I tried sending a test.php, we hit a function file_name_has_disallowed_extension() that didn’t allow such a file from being uploaded, but we’re stubborn, so let’s not give up.
From checking the declaration of those functions in common.php we found why this happened.
There’s a list of allowed extensions in get_disallowed_file_extensions() that the function file_name_has_disallowed_extension() checks against and php is there.
It basically means that we can’t upload .php files, right? Wrong and let’s see why.
Inside includes/upload.php, we see that we have total control over the filename that is being uploaded as well how the file is saved into the server. The lines 54 and 55 give us such power through simple HTTP requests.
We also see that if $field is empty, the execution dies (59,60), so we set a value 1 to field_id.
After changing the filename from test.php to test.jpg we got a very interesting response.
The file was uploaded to the server but its name is _input_1_, therefore we can’t do much with it.
It turns out that this is how the temp_filename is created:
Breaking that down, we have the following:
$form_unique_id = We didn't set any value here _input_ = Hardcoded $field_id = We set that 1 as mentioned above _ = Harcoded $file_name = We control this data, therefore we can set .php here
Our $tmp_file_name is ready to go and we finally got what we were looking for!
From checking the changelog for gravity forms we see that the security fix was applied in the version (1.8.20) please update soonest: http://www.gravityhelp.com/gravity-forms-v1-8-20-released/
Gravity Forms v1.8.20 is now available via automatic update and the customer downloads page. This is an important security and maintenance release.
We recommend all users update as soon as possible. It is important to always keep WordPress, plugins and themes up to date as a matter of best practice.
- Fixed a security issue with the file upload field.
The versions 1.8.19 and lower might be affected by this vulnerability.
We always say that keeping all software updated is one of the most important steps you can take towards reducing the risks of infection and this post is a good example of why.
This is a dangerous vulnerability, you should update all of your websites using this plugin as soon as possible. If for any reason you cannot, we highly recommend you to have a look at our Website Firewall (WAF) product. It’s designed to help you stay ahead of vulnerabilities like the one described here, and many more.