securing wordpress
TRANSCRIPT
Securing WordPress
OWASP Ottawa October 2015 Meetup !
Shawn HooperChief Technology Officer, Actionable Books
@shawnhooper - shawnhooper.ca
• I’m Shawn Hooper, CTO at Actionable Books. Former Freelance Developer
• WordPress Core Contributor
• GIAC Certified .NET Secure Software Programmer
Hi!
@shawnhooper - shawnhooper.ca
• Open Source Content Management System (CMS)
• PHP, MySQL, jQuery, BackboneJS
• Runs 24.7% of all web sites
• Has a 58.7% share of the CMS space
WordPress
@shawnhooper - shawnhooper.ca
http://w3techs.com/technologies/overview/content_management/all
• WordPress.com is a hosted WordPress service run by Automattic
• WordPress.org is the downloadable, self-hosted version of WordPress
• A huge ecosystem of themes (2K just in .org repo) and plugins (40K) that take advantage of the hook and filter system
WordPress
@shawnhooper - shawnhooper.ca
WordPress
@shawnhooper - shawnhooper.ca
This market share makes it a big target for hackers!
We are going to look at a couple of different types of attacks and how to avoid them:
* SQL Injection
* Cross Site Scripting (XSS)
* Cross Site Request Forgery (CSRF)
* Unvalidated Redirects and Forwards
We’re Under Attack!
@shawnhooper - shawnhooper.ca
!
!
!
on the Open Web Application Security Project
(OWASP) Top Ten List
Injection Attacks
@shawnhooper - shawnhooper.ca
SQL injection is a code injection technique, used to attack data-driven applications, in
which malicious SQL statements are inserted into an entry field for execution (e.g. to dump
the database contents to the attacker).
- Wikipedia
SQL Injection Attacks
@shawnhooper - shawnhooper.ca
Without protecting against injection attacks, what would happen if a login form allowed this:
!
' OR '1'='1' --
SQL Injection Attacks
@shawnhooper - shawnhooper.ca
SELECT * FROM wp_users WHERE user_pass = '' OR '1'='1' --'
SQL Injection Attacks
@shawnhooper - shawnhooper.ca
'; DROP TABLE wp_users; --
SQL Injection Attacks
@shawnhooper - shawnhooper.ca
SELECT * FROM wp_users WHERE user_pass = ''; DROP TABLE
wp_users; --
SQL Injection Attacks
@shawnhooper - shawnhooper.ca
!
!
!
on the Open Web Application Security Project
(OWASP) Top Ten List
Cross Site Scripting (XSS)
@shawnhooper - shawnhooper.ca
Cross-site scripting (XSS) is a type of computer security vulnerability typically found in web
applications. XSS enables attackers to inject client-side script into web pages viewed by other users. A
cross-site scripting vulnerability may be used by attackers to bypass access controls such as the
same-origin policy.
- Wikipedia
Cross Site Scripting (XSS)
@shawnhooper - shawnhooper.ca
Cross Site Scripting can be used to capture a user’s authentication / session cookie and then impersonate them on a trusted website.
!
Reflected (ex, delivered by e-mail) vs. Persistant (ex, return by DB in a forum)
Cross Site Scripting (XSS)
@shawnhooper - shawnhooper.ca
!
!
!
on the Open Web Application Security Project
(OWASP) Top Ten List
Cross Site Request Forgery
@shawnhooper - shawnhooper.ca
Cross-site request forgery, also known as a one-click attack or session riding and abbreviated as CSRF
(sometimes pronounced sea-surf) or XSRF, is a type of malicious exploit of a website whereby
unauthorized commands are transmitted from a user that the website trusts.
-Wikipedia
Cross Site Request Forgery
@shawnhooper - shawnhooper.ca
An example of a simple CSRF attack would be getting you to visit a link that would change your
password to something the attacker knows.
Cross Site Request Forgery
@shawnhooper - shawnhooper.ca
!
!
!
on the Open Web Application Security Project
(OWASP) Top Ten List
Unvalidated Forwards & Redirects
@shawnhooper - shawnhooper.ca
Could allow code in your website to forward the user to a malicious (ex: phishing) website.
Unvalidated Forwards & Redirects
@shawnhooper - shawnhooper.ca
@shawnhooper - shawnhooper.ca
Scared Yet?
Let’s figure out how to stop all this stuff from happening…..
Validation
@shawnhooper - shawnhooper.ca
* Are values of the correct type?
* Are values in range?
Validation
@shawnhooper - shawnhooper.ca
Is an input supposed to be an integer?
intval($_POST[‘quantity’])
or
absint($_POST[‘quantity’])
Validation
@shawnhooper - shawnhooper.ca
Is it in range?
$quantity = absint($_POST[‘quantity’])
!
if ( $quantity > 10 ) {
die(‘Quantity Out of Range’);
}
Validation
@shawnhooper - shawnhooper.ca
Should it be an e-mail address?
$email = is_email( $_POST[‘email’] );
returns false if invalid
Sanitization
@shawnhooper - shawnhooper.ca
Should it be an e-mail address?
$email = sanitize_email( $_POST[‘email’] );
removes characters that are not valid
in an e-mail address.
Escaping Text
@shawnhooper - shawnhooper.ca
esc_html( $string );
esc_html__( $string, $domain );
ex:
Hello <?php echo esc_html( $string ); ?> !
Escaping Text
@shawnhooper - shawnhooper.ca
esc_attr( $text );
esc_attr__( $text, $domain );
Escaping a string for use in an HTML attribute tag.
<div data-value=“<?php echo esc_attr( $value ); ?>”>
Escaping Text
@shawnhooper - shawnhooper.ca
esc_js( $text );
Escaping a string for echoing in JavaScript.
Escaping URLs
@shawnhooper - shawnhooper.ca
esc_url ($url );
esc_url_raw ( $url );
urlencode ( $string );
urlencode_deep ( $array );
Escaping HTML
@shawnhooper - shawnhooper.ca
wp_kses( $fragment, $allowed_html, $protocols);
array( 'a' => array( 'href' => array(), 'title' => array() ),
'br' => array(), 'em' => array(), 'strong' => array() );
Escaping HTML
@shawnhooper - shawnhooper.ca
wp_rel_nofollow( $html )
!
Adds rel=“nofollow” to every link in the HTML fragment.
$wpdb->insert( ‘table_name’, array( 'column1' => 'value1', 'column2' => 123 ), array( '%s', '%d' ) );
Database Sanitization
@shawnhooper - shawnhooper.ca
$wpdb->update( 'table', array( 'column1' => 'value1', // string 'column2' => 'value2' // integer (number) ), array( 'ID' => 1 ), array( '%s', // value1 '%d' // value2 ), array( '%d' ) );
Database Sanitization
@shawnhooper - shawnhooper.ca
$wpdb->delete( 'table', array( 'ID' => 1 ), array( '%d' ) );
Database Sanitization
@shawnhooper - shawnhooper.ca
What about other general queries? !
Statements that include joins? !
$wpdb->query()
Database Sanitization
@shawnhooper - shawnhooper.ca
$wpdb->prepare() to make sure query is safe: !!
$wpdb->prepare(SQL Code with Placeholders, variable 1, variable 2, etc.);
Database Sanitization
@shawnhooper - shawnhooper.ca
Database Sanitization
@shawnhooper - shawnhooper.ca
$safeSQL = $wpdb->prepare(“SELECT * FROM mytable WHERE col1 = ‘%s’ AND col2 = %d”, $sParam, $iParam); !$wpdb->query($safeSQL);
Database Sanitization
@shawnhooper - shawnhooper.ca
Valid Placeholders are: !
%s for strings !
%d for integers !
%f for floats
Database Sanitization
@shawnhooper - shawnhooper.ca
If your query includes a LIKE statement in the WHERE clause, use
esc_like()
to properly escape %, _ and \ characters,
which have special meanings.
Still requires $wpdb->prepare()
Database Sanitization
@shawnhooper - shawnhooper.ca
$likeValue = ‘value_’;
$safeSQL = $wpdb->prepare(“SELECT * FROM table WHERE col1 LIKE ‘%s’", esc_like($likeValue) . '%' );
Input Sanitization
@shawnhooper - shawnhooper.ca
There are a pile of functions to do input sanitization:
sanitize_title() sanitize_user() balance_tags() tag_escape() is_email() sanitize_html_class() array_map() sanitize_email() sanitize_file_name() sanitize_term() sanitize_term_field()
sanitize_html_class() sanitize_key() sanitize_mime_type() sanitize_option() sanitize_sql_orderby() sanitize_text_field() sanitize_title_for_query() sanitize_title_with_dashes() sanitize_user() sanitize_meta()
Nonces
@shawnhooper - shawnhooper.ca
A “number used once” to help protect URLs from malicious use (Cross Site Request
Forgery)
Nonces
@shawnhooper - shawnhooper.ca
NOTE: In WordPress, a nonce is not a number, and it is not used once.
!
!
!
Nonces
@shawnhooper - shawnhooper.ca
Create a Nonce for a URL:
$complete_url = wp_nonce_url( $bare_url, 'trash-post_'.$post-
>ID );
Nonces
@shawnhooper - shawnhooper.ca
Create a Nonce for a Form: wp_nonce_field( 'delete-comment_'.$comment_id );
Nonces
@shawnhooper - shawnhooper.ca
Generates code like this: <input type="hidden" id="_wpnonce"
name="_wpnonce" value="796c7766b1" />
<input type="hidden" name="_wp_http_referer" value="/wp-admin/edit-comments.php" />
Nonces
@shawnhooper - shawnhooper.ca
Generic Nonce:
!
$nonce = wp_create_nonce( 'my-action_'.$post->ID );
Validate Nonces
@shawnhooper - shawnhooper.ca
To verify a nonce that was passed in a URL or a form in an admin screen:
!
check_admin_referer( 'delete-comment_'.$comment_id );
Validate Nonces
@shawnhooper - shawnhooper.ca
To verify a nonce that was passed in an AJAX request:
(parameter is the action sent via AJAX)
!
check_ajax_referer( 'process-comment' );
Validate Nonces
@shawnhooper - shawnhooper.ca
To verify a generic nonce:
!
wp_verify_nonce( $_REQUEST['my_nonce'], 'process-comment'.$comment_id );
!
Returns false if the nonce fails
Nonces
@shawnhooper - shawnhooper.ca
!
To learn more about nonces,
see the WordPress Codex:
!
https://codex.wordpress.org/WordPress_Nonces
Redirecting
@shawnhooper - shawnhooper.ca
wp_redirect( $url, $status ); exit;
wp_safe_redirect( $url, $status ); exit;
!
$status defaults to 302 (temporary)
safe_redirect only allows redirects to a specified set of hostnames, which can be set using the
allowed_redirect_hosts filter
Responsible Disclosure
@shawnhooper - shawnhooper.ca
If you find what you think may be a security vulnerability in WordPress’ code, be responsible. Send an
e-mail with as much detail to:
Don’t blog about it, Facebook it, put it in Trac, Tweet it, etc. Allow the team time to confirm and fix the bug
before letting all the hackers out there know it exists.
Backups
@shawnhooper - shawnhooper.ca
• Make Them
• Make Them Regularly
• Test Them
Keep WordPress Updated
@shawnhooper - shawnhooper.ca
• Updates to Core
• Updates to Themes
• Updates to Plugins
Keep WordPress Updated
@shawnhooper - shawnhooper.ca
• Automatic Updates to Core for all minor releases
• Manual Updates via wp-admin dashboard
@shawnhooper - shawnhooper.ca
File Permissions
@shawnhooper - shawnhooper.ca
• Make sure files & directories have only the permissions required.
• Allowing files in your uploads folder to be executed leads to ugly phishing attacks.
“Admin” Username
@shawnhooper - shawnhooper.ca
• This was the default in old versions of WordPress.
• Most commonly attacked username when attempting logins.
Enable Two-Factor Authentication
@shawnhooper - shawnhooper.ca
• Clef
• https://wordpress.org/plugins/search.php?q=two+factor
• Feature Plugin underway for core
Least Privilege
@shawnhooper - shawnhooper.ca
• Only grant users the appropriate roles they need in wp-admin.
Change Keys
@shawnhooper - shawnhooper.ca
define('AUTH_KEY', '[w$u#*IL-lLtigU?Un)DY>DSbE}C -<d*+Z{gzc}Qw~p%o%g+INE3MiLBsT@%fjf');
define('SECURE_AUTH_KEY', '+=fttecyOK0jVI/~Q}f+|QMKo0H:}iV9C*koL@ci#L|ERr7i[J`>VDz{qd@zX2rq');
define('LOGGED_IN_KEY', ';5+<dNW?)zzrm*6zb+7-dB IRY%{D0;P2H|^v5BJYh]E[blAUU-n49Hgw0S@#nR-');
define('NONCE_KEY', 'R^@%&qAN$;t;<OTq$<Sm(447Rio}c<2,ts)+bVq1BE-?$Cw+a_@i7!*<`7?K4ne2');
define('AUTH_SALT', '@`Z-(+4Aq}{Y|*ow!OWSe&UNK4v^)hpi|}v)Xe-j14UN|lombcE}pv7#|/]VeG#U');
define('SECURE_AUTH_SALT', 'y9wF-&[!<PzrU]bII>RL0+OiI)D)]juvkojz$40l<Wbejx|xnvn5P,DI9816X-(]');
define('LOGGED_IN_SALT', 'l5&&8omK=~.},&!1w3VyVqFSF}edd7ldN,Y7cI)]XKq7+GUGQKfxjq<%6;v5|v|r');
define('NONCE_SALT', '?vsQ>D>oYiX_g=FnGHU%Sv-f?DuNCD@%1RGeTAL~|%,n(=+-Wr?~1uzmXlw?QW9N');
wp-config.php
Disable XML-RPC
@shawnhooper - shawnhooper.ca
• Used for remote blogging, tracebacks, pingbacks, etc.
• Also a great way to DDoS
Remove Version Number
@shawnhooper - shawnhooper.ca
<meta name="generator" content="WordPress 4.3.1”>
remove_action('wp_head', 'wp_generator');
Security Plugins
@shawnhooper - shawnhooper.ca
• WordFence (Plugin)
• iThemes Security (Plugin)
• Sucuri (Plugin)
Limit Login Attempts
@shawnhooper - shawnhooper.ca
• JetPack Brute Force
• iThemes Security
Hosting
@shawnhooper - shawnhooper.ca
• WordPress Managed Hosting
• Manages Updates
• Custom Firewall Configurations
Thank you! Slides: www.shawnhooper.ca
E-Mail: [email protected]
Twitter: @shawnhooper
WordPress Slack: shooper
@shawnhooper - shawnhooper.ca