Independently secure, together not so much – a story of 2 WP plugins


Recently we had to do a security assessment on a WordPress website. Obviously when dealing with a WordPress installation the best option is to always target the plugins. We’ve quickly enumerated the plugins using WPScan and then we recreated this setup in our local environment for easier testing & debugging. We found 2 interesting plugins which support file uploads and that is always interesting functionality to abuse, so we will study them one by one.

The objective of this assignment was to check what security issues we can find in the plugins attack surface and see if we can obtain RCE by abusing the existing plugins and sure enough we found a few WordPress plugins vulnerabilities. We have reported an insecure randomness vulnerability and an arbitrary file upload vulnerability.

Although the 2 plugins seem secure when you look at them independently we discovered that when used together this can actually result in an RCE, because one plugin can be used to circumvent the security mechanisms created by the other. Hence the reason for this post. Authentication is needed to exploit this. Let’s start.

Contact Form 7

This seems to be a very popular plugin with over 5+ Million installations. It allows you to add a contact form to any article and also optionally attach a file. Let’s try to upload a file and do a bit of dynamic analysis on this to check how it behaves, what sort of sanitization it does on the filename etc.

Wordpress plugins vulnerabilities - Contact Form 7 file upload form
Contact Form 7 file upload form

By tracing the code and adding various breakpoints we reach the following code where the plugin copies the file to a random dir:

Random dir name creation
Random dir name creation

The plugin creates a random dir inside /wp-content/uploads/wpcf7_uploads and it looks like this:

Wordpress plugins vulnerabilities -  Insecure radomness vulnerability  in the dirname creation
Random dir name – low entropy

Insecure random directory name

So, it’s a numeric filename, interesting, any guess how the plugin generates this name?

Wordpress plugins vulnerabilities -  Insecure radomness vulnerability  in the dirname creation
Insecure radom dir name generation

Indeed they use mt_rand together with a 0 prefix. mt_rand is not crypto secure as you probably know, this is our first WP plugin vulnerability, perhaps we can use that in an attack for later. Let’s have a look now at filename sanitization.

Filename sanitization method
Filename sanitization method

It calls a plugin specific function, wpcf7_antiscript_file_name to prevent uploading all sort of malicious like php, sh, py, rb anything which could result in a direct shell basically. Let’s see how our filename with double extension looks like after sanitization.

Filename sanitization on php extensions
Filename sanitization on php extensions

Ok, basically it adds an underscore to the ‘.php’ extension to neutralize it. Interesting, let’s do more testing with the debugger, to analyze the behavior and get more insight:

Filename sanitization behavior
Filename sanitization behavior

It sanitizes ‘.php’ extension, by adding an underscore and it also adds a ‘.txt’ extension at the end. On the other hand if we use the ‘.phar’ extension nothing happens. Nice 🙂

For reference the full function is bellow:

Filename sanitization - checking for dangerous extensions
Filename sanitization – checking for dangerous extensions

However, this is not everything. Tracing the code further, we found out that wp_unique_filename is called, which is a WordPress specific function, which does a great job at sanitizing the filename again. This implements an excellent whitelist, so even if you found an issue with the plugin function wpcf7_antiscript_file_name, you will have to bypass this one as well.

Let’s quickly study it’s behavior, considering what we found above:

Filename sanitization
Filename sanitization

So it sanitizes the “.phar” extension if it’s the first extension, but it ignores it if it’s the second extension.

Race condition

Whilst tracing the code further we realized that we have one more challenge, the plugin deletes everything shortly after upload.

Wordpress plugins vulnerabilities - race condition vulnerability
Wordpress plugins vulnerabilities – race condition vulnerability

Thus, we would have to exploit a race condition as well. Sounds doable to me. But let’s check one more thing, if we put a breakpoint before deletion, can we actually access this new directory and what’s inside it?

Not really, perhaps a .htaccess file is stopping us? Let’s check.

Indeed, we have a “Deny from all” in the plugin upload directory. So, to summarize we have the following challenges to overcome:

  • we need to guess a random directory name which holds our newly uploaded file
  • various sanitizations on the filename – the WordPress sanitization is solid
  • the plugins deletes the random directory and the uploaded file after upload
  • .htaccess is preventing us from accessing the directory

We already found 2 wordpress plugins vulnerabilities: an insecure dir name creation and a race condition vulnerability.

Arbitrary file upload vulnerability

Enter LiteSpeed Cache. This plugin performs various optimizations and makes your WordPress install load faster. Without further ado, we’ve found an (authenticated) arbitrary file upload vulnerability:

Wordpress plugins vulnerabilities -  AFU vulnerability in the LiteSpeed Cache
AFU vulnerability in the LiteSpeed Cache

You control the path and also the file contents. Some strict validation is done on the path(extension), we couldn’t find a bypass for that. However, there’s no restriction on the file content, you can write anything there. We will overwrite the .htaccess file from the previous plugin. We’ve decided to make the entire folder publicly accessible with “Allow from All” directive and also make .txt and .png file executable as php code.

Thus, from the 3 challenges we had to overcome, we can now leak the random directory name, sanitization on the filename/extension is irrelevant and we’re only left with exploiting a race condition. We’ve developed a POC in python for winning this race. It’s very simple, it runs a thread which will continuously try to read the name of the newly created random directory. The POC continuously searches a phpinfo.txt in the created directory, so this is what you’ll have to upload from the frontend. When you win the race and execute phpinfo.txt, this will write another file, shell.txt which will be our permanent shell on the system.

A phpinfo.txt file gets written and then it’s quickly deleted:

Wordpress plugins vulnerabilities Phpinfo uploaded
Phpinfo uploaded

Winning the race and executing phpinfo.txt will write shell.txt, a permanent shell.

Wordpress plugins vulnerabilities - Winning the race and writing a shell
Winning the race and writing a shell

One thing we noticed is that our newly written permanent shell (shell.txt) will stop the newly random directory from being deleted. In case you need some extra help to win the race condition we found this resource to contain some great tips: wallarm. And here is our glorious shell executing commands:

RCE on the server
RCE on the server

WordPress plugins vulnerabilities – summary

This vulnerability has been fixed in the latest version of LiteSpeed Cache plugin, to clarify there is no security issue directly exploitable with Contact Form 7 plugin. In particular we would like to note that arbitrary file upload vulnerabilities are still very common, you just have to keep your eyes open. Speaking of file upload issues, you should follow this blog, we have more file uploads/race conditions articles coming, much more difficult to exploit than this one. As always WordPress plugins vulnerabilities are out there, you just have to look for them. As always we recommend installing only the WP plugins that you really need. And if your blog contains highly sensitive data your should audit them as well! Thanks for reading.


Another race condition in the file upload exploit (double race condition)

About Post Author