Skip to content

WordPress Plugin Security Testing Cheat Sheet

Ryan Dewhurst edited this page Jun 8, 2021 · 16 revisions
WPScan logo

WordPress Plugin Security Testing Cheat Sheet

This is cheat sheet is heavily used by the WPScan Team when we conduct penetration testing against WordPress plugins. If you would like to have your WordPress plugin, or WordPress website, tested by WordPress security experts, please contact us.

How to: Find WordPress Plugin Vulnerabilities Free eBook

We also have an free eBook with similar content but in a less cheat sheet style:

https://wpscan.com/howto-find-wordpress-plugin-vulnerabilities-wpscan-ebook.pdf

Testing Environments

Proxy WordPress

To proxy WordPress HTTP requests through a proxy, such as Burp Suite, you can configure WordPress to use a proxy. This is useful if a plugin makes backend API requests. You may need to set Burp Suite to listen on all interfaces. You may also need to disable SSL verification within the plugin's source code.

define('WP_PROXY_HOST', '127.0.0.1');
define('WP_PROXY_PORT', '8080');
define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org');

Log HTTP Requests

The Log HTTP Requests WordPress plugin logs any outgoing HTTP requests, which can be useful if a plugin uses a backend API.

DISALLOW_UNFILTERED_HTML

When doing dynamic testing for XSS the following setting in the wp-config.php file may reduce false positive results as it prevents administrative and editor users from being able to embed/execute JavaScript/HTML, which by default they are permitted to do.

define( 'DISALLOW_UNFILTERED_HTML', true );

Enable error logging

Add the following to your wp-config.php file:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );

The PHP errors will be saved to the /wp-content/debug.log file.

To do some logging yourself, you can use the error_log( 'This is a log' ); function.

Cross-Site Scripting (XSS)

Check if the following global PHP variables are echo'd to pages, or stored in the database and echo'd at a later time without first being sanitised or output encoded.

  • $_GET
  • $_POST
  • $_REQUEST
  • $_SERVER['REQUEST_URI']
  • $_SERVER['PHP_SELF']
  • $_SERVER['HTTP_REFERER']
  • $_COOKIE

(Note: the list of sources above is not extensive nor complete)

Cross-Site Scripting (XSS) Tips

Unsafe API functions

The following functions can cause XSS if not secured as they use the PHP_SELF variable when the second or third paramater is not a URL:

  • add_query_arg()
  • remove_query_arg()

Example regex: add_query_arg\(['"\s]+[^,]+['"]\s?\)

SQL Injection

Unsafe API methods (require sanitising/escaping):

  • $wpdb->query()
  • $wpdb->get_var()
  • $wpdb->get_row()
  • $wpdb->get_col()
  • $wpdb->get_results()
  • $wpdb->replace()

Safe API methods (according to WordPress):

  • $wpdb->insert()
  • $wpdb->update()
  • $wpdb->delete()

Safe code, prepared statement:

<?php $sql = $wpdb->prepare( 'query' , value_parameter[, value_parameter ... ] ); ?>

Note: Before WordPress 3.5 $wpdb->prepare could be used insecurely as you could just pass the query without using placeholders, like in the following example:

$wpdb->query( $wpdb->prepare( "INSERT INTO table (user, pass) VALUES ('$user', '$pass')" ) );

Example regex: wpdb->(query|get_var|get_row|get_col|get_results|replace)\((?!.*prepare).*\);

SQL Injection Tips

Unsafe escaping ('securing') API methods:

Displaying/hiding SQL errors:

<?php $wpdb->show_errors(); ?> 
<?php $wpdb->hide_errors(); ?> 
<?php $wpdb->print_error(); ?>

File Download

  • file()
  • readfile()
  • file_get_contents()

File Inclusion

  • include()
  • require()
  • include_once()
  • require_once()
  • fread()

File Manipulation

  • unlink() delete arbitrary files

File Upload

  • sanitize_file_name() can create valid PHP files, turns test.(php) into test.php

PHP Object Injection

  • unserialize() any raw user input passed to this function it is probably exploitable
  • maybe_unserialize() any raw user input passed to this function it is probably exploitable

PHP Object Injection Tips

Use this simple Burp Suite extention along with the PHP Object Injection WordPress Plugin created by White Fir Design.

Command Execution

  • system()
  • exec()
  • passthru()
  • shell_exec()

PHP Code Execution

  • eval()
  • assert()
  • preg_replace() dangerous "e" flag deprecated since PHP >= 5.5.0 and removed in PHP >= 7.0.0.
  • php://input reads raw data from the request body, can lead to RCE if used in eval
  • call_user_func() calls a function from a string, see https://owasp.org/www-community/attacks/Function_Injection

Authorisation

  • is_admin() does not check if the user is authenticated as administrator, only checks if page displayed is in the admin section, can lead to auth bypass if misused.
  • is_user_admin() same as above
  • current_user_can() used for checking authorisation. This is what should be used to check authorisation.
  • add_action( 'wp_ajax_nopriv_ permits non-authenticated users to use the AJAX function (https://codex.wordpress.org/Plugin_API/Action_Reference/wp_ajax_(action)).

Open Redirect

  • wp_redirect() function can be used to redirect to user supplied URLs. If user input is not sanitised or validated this could lead to Open Redirect vulnerabilities.

Cross-Site Request Forgery (CSRF)

  • wp_nonce_field() adds CSRF token to forms
  • wp_nonce_url() adds CSRF token to URL
  • wp_verify_nonce() checks the CSRF token validity server side
  • check_admin_referer() checks the CSRF token validity server side and came from admin screen
  • check_ajax_referer() checks the CSRF token validity server side for AJAX scripts

Gotcha

Passing false or 0 as the third argument to check_ajax_referer() will not cause the script to die, making the check useless.

Example:check_ajax_referer( 'ajax-login-nonce', 'security', false );

SSL/TLS

  • CURLOPT_SSL_VERIFYHOST if set to 0 then does not check name in host certificate
  • CURLOPT_SSL_VERIFYPEER if set to FALSE then does not check if the certificate (inc chain), is trusted. A Man-in-the-Middle (MitM) attacker could use a self-signed certificate.
  • Check if HTTP is used to communicate with backend servers or APIs. A grep for "http://" should be sufficient.

Priviledge Escalation

  • update_option() if user input is sent unvalidated, it could allow an attacker to update arbitrary WordPress options.
  • do_action() if user input is sent unvalidated, it could allow an attacker to update arbitrary WordPress actions.

See: https://www.wordfence.com/blog/2018/11/privilege-escalation-flaw-in-wp-gdpr-compliance-plugin-exploited-in-the-wild/

Automated Static Code Analysis

  • WordPress-Coding-Standards contains some security rules.

Example:

./vendor/bin/phpcs --standard=WordPress --sniffs=WordPress.CSRF.NonceVerification,WordPress.DB.PreparedSQL,WordPress.DB.PreparedSQLPlaceholders,WordPress.DB.RestrictedClasses,WordPress.DB.RestrictedFunctions,WordPress.Security.NonceVerification,WordPress.Security.PluginMenuSlug,WordPress.Security.SafeRedirect,WordPress.Security.ValidatedSanitizedInput,WordPress.Security.EscapeOutputSniff,WordPress.WP.PreparedSQL,WordPress.XSS.EscapeOutput -p -d memory_limit=256M --colors /path/to/plugin/

See: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards

Further reading/references:

  1. https://developer.wordpress.org/plugins/security/
  2. https://make.wordpress.org/plugins/2013/11/24/how-to-fix-the-intentionally-vulnerable-plugin/
  3. http://wordpress.tv/2011/01/29/mark-jaquith-theme-plugin-security/
  4. https://www.wordfence.com/learn/
  5. https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html
  6. https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html
  7. https://www.owasp.org/index.php/OWASP_Wordpress_Security_Implementation_Guideline
  8. http://php.net/manual/en/function.preg-replace.php
Clone this wiki locally