WordPress Plugin Security Testing Cheat Sheet
Pages 6
-
- WordPress Plugin Security Testing Cheat Sheet
- How to: Find WordPress Plugin Vulnerabilities Free eBook
- Testing Environments
- Proxy WordPress
- Log HTTP Requests
- DISALLOW_UNFILTERED_HTML
- Enable error logging
- Cross-Site Scripting (XSS)
- Cross-Site Scripting (XSS) Tips
- Unsafe API functions
- SQL Injection
- SQL Injection Tips
- Displaying/hiding SQL errors:
- File Download
- File Inclusion
- File Manipulation
- File Upload
- PHP Object Injection
- PHP Object Injection Tips
- Command Execution
- PHP Code Execution
- Authorisation
- Open Redirect
- Cross-Site Request Forgery (CSRF)
- Gotcha
- SSL/TLS
- Priviledge Escalation
- Automated Static Code Analysis
- Further reading/references:
Clone this wiki locally

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.
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:
-
esc_sql()
function does not adequately protect against SQL Injection https://codex.wordpress.org/Function_Reference/esc_sql -
escape()
same as above -
esc_like()
same as above -
like_escape()
same as above
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, turnstest.(php)
intotest.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.
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:
- https://developer.wordpress.org/plugins/security/
- https://make.wordpress.org/plugins/2013/11/24/how-to-fix-the-intentionally-vulnerable-plugin/
- http://wordpress.tv/2011/01/29/mark-jaquith-theme-plugin-security/
- https://www.wordfence.com/learn/
- https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html
- https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html
- https://www.owasp.org/index.php/OWASP_Wordpress_Security_Implementation_Guideline
- http://php.net/manual/en/function.preg-replace.php