-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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.
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
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');
The Log HTTP Requests WordPress plugin logs any outgoing HTTP requests, which can be useful if a plugin uses a backend API.
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 );
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.
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)
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?\)
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).*\);
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
<?php $wpdb->show_errors(); ?>
<?php $wpdb->hide_errors(); ?>
<?php $wpdb->print_error(); ?>
file()
readfile()
file_get_contents()
include()
require()
include_once()
require_once()
fread()
-
unlink()
delete arbitrary files
-
sanitize_file_name()
can create valid PHP files, turnstest.(php)
intotest.php
-
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
Use this simple Burp Suite extention along with the PHP Object Injection WordPress Plugin created by White Fir Design.
system()
exec()
passthru()
shell_exec()
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
-
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)).
-
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.
-
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
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 );
-
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.
-
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.
-
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
- 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