writing secure wordpress code wordcamp nyc 2014
DESCRIPTION
Learn the proper way to write the most secure code in WordPress. Whether you’re a plugin developer or build themes, it’s extremely important to understand how to secure your code from hacks and exploits. Overlooking some very easy to follow techniques can expose your website to the hackers everywhere. WordPress features a number of built-in methods to help make sure your code is safe and secure, and we’ll cover each and every one in detail.TRANSCRIPT
WRITING SECURE WORDPRESS CODE BY BRAD WILLIAMS
Brad Williams @williamsba
h-p://www.slideshare.net/williamsba/wri>ng-‐secure-‐wordpress-‐code-‐wordcamp-‐nyc-‐2014
WHO IS BRAD?
Brad Williams @williamsba
Brad Williams
CO-HOST DRADCAST
Brad Williams @williamsba
TODAY’S TOPICS
Brad Williams @williamsba
• Cover the big three exploits • SQL Injec>on -‐ SQLi • Cross-‐Site Scrip>ng -‐ XSS • Cross-‐Site Request Forgery – CSRF
• Hack Examples • Data Valida>on and Sani>za>on • Resources
TRUST NO ONE
Brad Williams @williamsba
Golden Rule of Code
Trust No One
TRUST NO ONE
Brad Williams @williamsba
Consider all data invalid unless it can be proven valid
SQL INJECTION - SQLI
Brad Williams @williamsba
SQL Injec>on (SQLi)
SQL INJECTION - SQLI
Brad Williams @williamsba
SQL injec*on is a code injec>on technique in which malicious SQL statements are inserted into an entry field for execu>on
SQL INJECTION - SQLI
Brad Williams @williamsba
SQL Injec>on Example global $wpdb;
$ID = $_GET['ID']; $sql = "SELECT post_title FROM $wpdb->posts WHERE ID = '$ID';";
SELECT post_>tle FROM wp_posts WHERE ID = '5';
SQL INJECTION - SQLI
Brad Williams @williamsba
SQL Injec>on Example
SELECT post_>tle FROM wp_posts WHERE ID = ''; SELECT * FROM wp_users WHERE 1 = '1';
global $wpdb; $ID = "'; SELECT * FROM wp_users WHERE 1 = '1"; $sql = "SELECT post_title FROM $wpdb->posts WHERE ID = '$ID';";
SQL INJECTION - SQLI
Brad Williams @williamsba
h-p://www.sitepoint.com/forums/showthread.php?83772-‐web-‐site-‐hacked
My Introduc>on to SQLi
SQL INJECTION - SQLI
Brad Williams @williamsba
h-p://www.sitepoint.com/forums/showthread.php?83772-‐web-‐site-‐hacked
My Introduc>on to SQLi
SQL INJECTION - SQLI
Brad Williams @williamsba
WordPress Database Class
SQL INJECTION - SQLI
Brad Williams @williamsba
$wpdb->insert()
SQL INJECTION - SQLI
Brad Williams @williamsba
$wpdb->insert( $wpdb->postmeta, array( 'post_id' => '5', 'meta_key' => '_custom_meta_key', 'meta_value' => 'true' ), array( '%d', '%s', '%s' ) );
$wpdb->insert() $wpdb->insert( $table, $data, $format )
Example:
%s handles strings %d handles integers %f handles floats
SQL INJECTION - SQLI
Brad Williams @williamsba
$wpdb->update()
SQL INJECTION - SQLI
Brad Williams @williamsba
$wpdb->update( $wpdb->postmeta', array( 'meta_value' => 'false' ), array( 'post_id' => 5, 'meta_key' => '_custom_meta_key' ), array( '%s' ), array( '%d', '%s' ) );
$wpdb->update() $wpdb->update( $table, $data, $where, $format, $where_format )
Example:
SQL INJECTION - SQLI
Brad Williams @williamsba
$wpdb->delete()
SQL INJECTION - SQLI
Brad Williams @williamsba
$wpdb->delete( $wpdb->posts, array( 'ID' => 5 ), array( '%d' ) );
$wpdb->delete() $wpdb->delete( $table, $where, $where_format )
Example:
SQL INJECTION - SQLI
Brad Williams @williamsba
$wpdb->prepare()
SQL INJECTION - SQLI
Brad Williams @williamsba
• Handles strings (%s) and integers (%d)
• Does the escaping for you • No need to quote %s
$wpdb->prepare( " SELECT post_title FROM $wpdb->posts WHERE ID = %d ", $ID );
$wpdb->prepare()
SQL INJECTION - SQLI
Brad Williams @williamsba
• Handles strings (%s) and integers (%d)
• Does the escaping for you • No need to quote %s
$wpdb->prepare( " DELETE FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s ", 420, 'Europe' );
$wpdb->prepare()
SQL INJECTION - SQLI
Brad Williams @williamsba
$wpdb-‐>prepare() only prepares the query, it does not execute it.
$wpdb->query( $wpdb->prepare( " DELETE FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s ", 420, 'Europe' ) );
$wpdb->prepare()
echo $wpdb->prepare( " DELETE FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s ", 420, 'Europe' );
To view the fully prepared query simply echo it
SQL INJECTION - SQLI
Brad Williams @williamsba
h-p://xkcd.com/327/
Don’t be Li-le Bobby Tables
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
Cross-‐Site Scrip>ng (XSS)
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
What is Cross-‐Site Scrip>ng?
A-acker injects client-‐side scripts into your web pages
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
Escaping To escape is to take the data you may already have and help secure it prior to
rendering it for the end user
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
1. esc_ is the prefix for all escaping func>ons 2. a-r is the context being escaped 3. _e is the op>onal transla>on suffix
Props to Mark Jaquith!
Escaping
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<h1><?php echo $title; ?></h1>
BAD
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<?php $title = "<script>alert('YO!');</script>"; ?> <h1><?php echo $title; ?></h1>
BAD
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<?php $title = "<script>alert('Hello Europe!');</script>"; ?> <h1><?php echo esc_html( $title ); ?></h1>
View Source: <h1><script>alert('Hello Europe!');</script></h1>
GOOD
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<input type="text" name="name" value="<?php echo esc_attr( $text ); ?>" />
esc_attr() Used whenever you need to display data inside an HTML element
h-p://codex.wordpress.org/Func>on_Reference/esc_a-r
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<textarea name="bio"> <?php echo esc_textarea( $bio); ?> </textarea>
esc_textarea() Used to encode text for use in a <textarea> form element
h-p://codex.wordpress.org/Func>on_Reference/esc_textarea
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<a href="<?php echo esc_url( $url); ?>">Link</a>
esc_url() Used for valida>ng and sani>zing URLs
h-p://codex.wordpress.org/Func>on_Reference/esc_url
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<?php $url = 'http://wordpress.org'; $response = wp_remote_get( esc_url_raw( $url ) ); ?>
esc_url_raw() Used for escaping a URL for database queries, redirects, and HTTP requests
Similar to esc_url(), but does not replace en>>es for display
h-p://codex.wordpress.org/Func>on_Reference/esc_url_raw
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<script> var bwar='<?php echo esc_js( $text ); ?>'; </script>
esc_js() Used to escape text strings in JavaScript
h-p://codex.wordpress.org/Func>on_Reference/esc_js
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
Integers
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
$ID = absint( $_GET['ID'] );
absint() Coverts a value to a non-‐nega>ve integer
h-p://codex.wordpress.org/Func>on_Reference/absint
<input type="text" name="number_posts" value="<?php echo absint( $number ); ?>" />
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
$ID = intval( $_GET['ID'] );
intval() Returns the integer value. Works with nega>ve values
h-p://php.net/manual/en/func>on.intval.php
<input type="text" name="number_posts" value="<?php echo intval( $number ); ?>" />
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
Sani>zing To sani>ze is to take the data and clean
to make safe
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<?php update_post_meta(
420,
'_post_meta_key',
$_POST['new_meta_value'] ); ?>
BAD
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
sanitize_text_field() Sani>ze a string
h-p://codex.wordpress.org/Func>on_Reference/sani>ze_text_field
<?php update_post_meta(
34,
'_post_meta_key',
sanitize_text_field( $_POST['new_meta_value'] ) ); ?>
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
sanitize_email() Strip out all characters not allowed in an email address
h-p://codex.wordpress.org/Func>on_Reference/sani>ze_email
<?php update_post_meta(
34,
'_email_address',
sanitize_email( $_POST['email'] ) ); ?>
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
sanitize_user() Sani>ze username stripping out unsafe characters
h-p://codex.wordpress.org/Func>on_Reference/sani>ze_user
<?php update_post_meta(
34,
'_custom_username',
sanitize_user( $_POST['username'] ) ); ?>
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
wp_kses() Filters content and keeps only allowable HTML elements.
h-p://codex.wordpress.org/Func>on_Reference/wp_kses
<a href="#">link</a>. This is bold and <strong>strong</strong>
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
wp_kses_post() Filters post content and keeps only allowable HTML elements.
h-p://codex.wordpress.org/Func>on_Reference/wp_kses_post
HTML tags allowed to be put into Posts by non-‐admin users
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
Cross-‐site Request Forgery (CSRF)
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
Exploit of a website whereby unauthorized commands are transmi-ed from a user that the website trusts.
Cross-‐site Request Forgery (CSRF)
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
Nonces Ac>on, object, & user specific >me-‐
limited secret keys
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
<?php if ( isset( $_POST['email'] ) ) { //process form data } ?> <form method="post"> <input type="text" name="email /><br /> <input type="submit" name="submit" value="Submit" /> </form>
Example
There is no way to know where $_POST[‘email’] is being posted from
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
<form method="post"> <?php wp_nonce_field( 'bw_process_email_action', 'bw_newsletter' ); ?> <input type="text" name="email" /><br /> <input type="submit" name="submit" value="Submit" /> </form>
wp_nonce_field()
<form method="post"> <input type="hidden" id="bw_newsletter" name="bw_newsletter" value="287de957e8" /> <input type="hidden" name="_wp_http_referer" value="/x/sample-page/" /> <input type="text" name="email" /><br /> <input type="submit" name="submit" value="Submit" /> </form>
View Source:
Form Code:
h-p://codex.wordpress.org/Func>on_Reference/wp_nonce_field
wp_nonce_field( $action, $name, $referer, $echo );
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
if ( isset( $_POST['email'] ) ) { check_admin_referer( 'bw_process_email_action', 'bw_newsletter' ); //process form data }
check_admin_referer()
Processing Code:
check_admin_referer( $action, $query_arg );
h-p://codex.wordpress.org/Func>on_Reference/check_admin_referer
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
<?php if ( isset( $_POST['email'] ) ) { check_admin_referer( 'bw_process_email_action', 'bw_newsletter' ); //process form data } ?> <form method="post"> <?php wp_nonce_field( 'bw_process_email_action', 'bw_newsletter' ); ?> <input type="text" name="email" /><br /> <input type="submit" name="submit" value="Submit" /> </form>
Fixed Example
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
$url = 'http://example.com/wp-admin/?ID=5'; $url = wp_nonce_url( $url, 'bw_process_email_action', 'bw_newsletter' );
wp_nonce_url()
http://example.com/wp-admin/?ID=5&bw_newsletter=287de957e8 New URL:
URL Code:
h-p://codex.wordpress.org/Func>on_Reference/wp_nonce_url
wp_nonce_url( $actionurl, $action, $name );
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
if ( isset( $_GET[ID'] ) ) { check_admin_referer( 'bw_process_email_action', 'bw_newsletter' ); //process data }
wp_nonce_url()
Processing Code:
h-p://codex.wordpress.org/Func>on_Reference/check_admin_referer
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
Nonces Specific to
• WordPress User • Ac>on A-empted • Object of a-empted ac>on • Time Window
RESOURCES
Brad Williams @williamsba
• Security Ar>cles • h-p://codex.wordpress.org/Data_Valida>on • h-p://codex.wordpress.org/Valida>ng_Sani>zing_and_Escaping_User_Data • h-p://wp.tutsplus.com/tutorials/7-‐simple-‐rules-‐wordpress-‐plugin-‐development-‐best-‐
prac>ces/ • h-p://wpengine.com/2013/05/brad-‐williams-‐on-‐secure-‐wordpress-‐development/ • h-p://codex.wordpress.org/WordPress_Nonces
• Security Presenta>ons • h-p://wordpress.tv/2013/08/09/mike-‐adams-‐three-‐security-‐issues-‐you-‐thought-‐youd-‐fixed/ • h-p://wordpress.tv/2013/09/26/brennen-‐byrne-‐employing-‐best-‐security-‐prac>ces-‐for-‐
wordpress-‐sites-‐3/ • h-p://wordpress.tv/2011/01/29/mark-‐jaquith-‐theme-‐plugin-‐security/
DRADCAST PLUG
Brad Williams @williamsba
Listen to the DradCast WordPress Podcast
LIVE every Wednesday @ 8pm EDT
DradCast.com
CONTACT BRAD
Brad Williams @williamsba
Brad Williams [email protected] Blog: strangework.com Twi-er: @williamsba
h-p://bit.ly/prowp2