WordPress plugins are widely adopted and an attractive target for attackers. In this technical blog post we analyze the most critical vulnerabilities in WordPress plugins of 2017 and share insights about how static code analysis can detect these.

WordPress is used by 29.0% of all the websites . Due to its wide adoption, specifically the security of WordPress plugins moved into the focus of cyber criminals. Often, the plugins provided by third parties do not share the same level of security as the WordPress core itself. Security vulnerabilities are actively exploited in order to compromise large amounts of installations that use vulnerable plugins. Can static code analysis detect these vulnerabilities out of the box?

Vulnerability Selection

We chose publicly known plugin vulnerabilities that

were released in 2017,

affect a plugin that has many active installs,

have great impact,

do not require authentication or server requirements (e.g. WP statistics SQLi),

do not affect a commercial plugin with non-public source code.

RIPS is able to analyze a plugin without the WordPress core. In the following, we will cover relevant WordPress features that we built into our analysis engine for an in-depth plugin analysis.

Loginizer 1.3.5 - SQL Injection (CVE-2017-12650)

The Loginizer plugin is installed on more than 550,000 WordPress sites. It is supposed to add security to the WP login by blacklisting bruteforce attempts, enabling Two Factor Authentication, and by adding a reCAPTCHA. But in August a SQL injection vulnerability was reported in the login routine of Loginizer that puts exactly the admin credentials at risk that it is meant to protect.

Let’s have a look at the affected code lines and what a static analysis tool needs to be capable of in order to detect this issue. Note that in the following we are working with simplified code snippets and an actual analysis is more complex.

Step 1: Identify custom SQL wrapper

As a first fundamental step, the SAST tool has to identify that the user-defined function lz_selectquery() used in this plugin executes a SQL query with the WordPress database driver. Whenever this function is called, its first parameter requires analysis for SQL injection.

modules/Emails/DetailView.php

1 2 3 4 function lz_selectquery ( $query , $array = 0 ){ global $wpdb ; $result = $wpdb -> get_results ( $query , 'ARRAY_A' ); }

Step 2: Identify uncommon sources

As another fundamental step, the SAST tool must be aware of all common and uncommon sources of user input in PHP. The user-defined function lz_getip() returns a HTTP request header that can be modified by an attacker. Thus, the return value of this function is considered as tainted and its data flow must be followed precisely.

modules/Emails/DetailView.php

1 2 3 4 5 6 7 8 9 10 function lz_getip () { global $loginizer ; if ( isset ( $_SERVER [ "REMOTE_ADDR" ])) { $ip = $_SERVER [ "REMOTE_ADDR" ]; } if ( isset ( $_SERVER [ "HTTP_X_FORWARDED_FOR" ])) { $ip = $_SERVER [ "HTTP_X_FORWARDED_FOR" ]; } return $ip ; }

Step 3: Analyze WordPress actions and filter

WordPress allows to define many different actions and filters that invoke custom functions. The SAST tool needs to be able to understand how these callbacks work and what is invoked in order to follow the control flow of the plugin. In the following snippet we see that the function loginizer_load_plugin() is invoked through an action. This function retrieves the user input from lz_getip() , stores it in a global array $loginizer , and then adds an invocation of another custom function ( loginizer_wp_authenticate() ) through a WordPress filter. Hence, next to WordPress actions, the SAST tool needs to understand how WordPress filters work.

modules/Emails/DetailView.php

1 2 3 4 5 6 function loginizer_load_plugin () { global $loginizer ; $loginizer [ 'current_ip' ] = lz_getip (); add_filter ( 'authenticate' , 'loginizer_wp_authenticate' , 10001 , 3 ); } add_action ( 'plugins_loaded' , 'loginizer_load_plugin' );

Step 4: Analyze global variables

What looks simple for the human eye in the following code snippets is highly complex for a SAST tool to analyze. It has to analyze the data flow of the global array $loginizer throughout several function calls. Only then it is able to detect that the user input from lz_getip() is passed along to the function loginizer_can_login() where it is used unsanitized in a SQL query. The query is executed with the custom SQL function lz_selectquery() . Although WordPress simulates magic_quotes that would prevent the injection by escaping, the user input comes from a HTTP header that is not affected by WordPress’ escaping.

modules/Emails/DetailView.php

1 2 3 4 5 6 7 8 9 10 11 function loginizer_wp_authenticate ( $user , $username , $password ) { global $loginizer , $lz_error , $lz_cannot_login , $lz_user_pass ; if ( loginizer_can_login ()) { return $user ; } } function loginizer_can_login () { global $wpdb , $loginizer , $lz_error ; $result = lz_selectquery ( "SELECT * FROM " . $wpdb -> prefix . "loginizer_logs WHERE ip = '" . $loginizer [ 'current_ip' ] . "';" ); }

Due to RIPS precise analysis algorithms that are dedicated to the PHP language and its fine-tuning for WordPress specifics, the complex to follow data flow of this SQL injection is successfully detected.

Ultimate Form Builder Lite 1.3.6 - SQL Injection (CVE-2017-15919)

The Ultimate Form Builder Lite plugin has more than 50,000 active installations. It allows to easily create contact forms with a drag and drop form builder. In October, an actively exploited SQL injection vulnerability was reported in this plugin that in the end allows an attacker to take over the WordPress site.

In the following, we investigate what went wrong and how a SAST tool can detect the security issue.

Step 1: Identify the sink and its context

As a first step, all SQL queries executed by the plugin have to be analyzed. The WordPress database driver is not part of the plugin code but its methods, e.g. get_row() are known to the RIPS engine for potential SQL injections.

modules/Emails/DetailView.php

1 2 3 4 5 6 7 8 class UFBL_Model { public static function get_form_detail ( $form_id ) { global $wpdb ; $table = UFBL_FORM_TABLE ; $form_row = $wpdb -> get_row ( "SELECT * FROM $table WHERE form_id = $form_id " ); return $form_row ; } }

From there, the engine analyzes the SQL query for the injection context. Commonly, security bugs are very subtle. If it would read form_id='$form_id' instead of form_id=$form_id this query would not be vulnerable because of WordPress’ escaping of user input. Only a context-sensitive SAST tool is able to evaluate these subtlenesses.

Step 2: Follow the data flow of user input

This step sounds easier than it is. As you can see from the following code lines, the taint analysis of $form_id can span over different function calls and loops.

modules/Emails/DetailView.php

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class UFBL_Lib { public static function do_form_process () { $form_data = array (); foreach ( $_POST [ 'form_data' ] as $val ) { if ( strpos ( $val [ 'name' ], '[]' ) !== false ) { $form_data_name = str_replace ( '[]' , '' , $val [ 'name' ] ); if ( ! isset ( $form_data [ $form_data_name ] ) ) { $form_data [ $form_data_name ] = array (); } $form_data [ $form_data_name ][] = $val [ 'value' ]; } else { $form_data [ $val [ 'name' ]] = $val [ 'value' ]; } } $form_id = sanitize_text_field ( $form_data [ 'form_id' ] ); $form_row = UFBL_Model :: get_form_detail ( $form_id ); } }

Here, the POST form_data is assigned to a different array and is processed with the WordPress internal sanitization function sanitize_text_field() . However, this sanitization function does not prevent the exploitation of a SQL injection for our detected SQL context. To detect this issue and to prevent false positives at the same time, a SAST tool needs be capable of following complex data flow, needs to be aware of WordPress internal functions, and has to evaluate their effects for different contexts.

Zen Mobile App Native 3.0 - File Upload (2017-02-27)

The Zen Mobile App Native plugin is reported to be amongst the most actively attacked plugins recently. It is apparently one out of only three 2017 vulnerabilities that are actively targeted by attackers. The remaining exploited vulnerabilities are 2-3 years old. The vulnerability is summarized in the following code that also affects multiple other plugins, such as Mobile App Builder 1.05.

/zen-mobile-app-native/server/images.php

1 2 3 4 5 6 7 8 9 if ( ! $_FILES [ 'file' ][ 'error' ]) { $name = md5 ( rand ( 100 , 200 )); $ext = explode ( '.' , $_FILES [ 'file' ][ 'name' ]); $filename = $name . '.' . $ext1 ; $destination = 'images/' . $filename ; $location = $_FILES [ "file" ][ "tmp_name" ]; move_uploaded_file ( $location , $destination ); echo $plugin_url . '/server/images/' . $filename ; }

Only a few code lines are affected and hence this issue can be easily spotted by a SAST tool. A file is uploaded via move_uploaded_file() . RIPS analyzes its two arguments for user input. While the file name is replaced with a random hash, the file extension remains in the attacker’s control. Thus, an upload of a PHP shell file with .php extension is possible that enables an attacker to execute arbitrary code on the web server.

Appointments 2.2.1 - PHP Object Injection (2017-10-02)

The last example is very easy to spot, even for the many tools that use only simple signatures or heuristics. A PHP Object Injection vulnerability was reported in the Appointments plugin. The same issue was also reported in other plugins, such as Flickr Gallery and RegistrationMagic Custom Registration Forms. A sophisticated data flow analysis is clearly not needed as the flow from user input into a sensitive sink happens within one line of code.

/flickr-gallery.php

1 $pager = unserialize ( stripslashes ( $_POST [ 'pager' ]));

More important here is that this single line is not overlooked and that the SAST tool explains in detail to the developer why it poses a serious security risk. This issue was found to be actively exploited in the wild by attackers.

Summary

In this blog post we analyzed 4 critical WordPress plugin vulnerabilities that were reported in 2017. The security issues were hiding in various types of code, from complex and WordPress-specific interactions to simple code blocks. We demonstrated how these issues can be revealed automatically and what code analysis capabilities are necessary for detection. As there are over 40,000 different WordPress plugins available for download, we will likely see many more vulnerability and attack reports in 2018. Stay tuned for upcoming blog posts about vulnerability analyses of previously unknown issues in popular WordPress plugins.