writing secure wordpress code
DESCRIPTION
My WordCamp Europe 2013 presentation on writing secure WordPress code.TRANSCRIPT
WRITING SECURE WORDPRESS CODE BY BRAD WILLIAMS
Brad Williams @williamsba
WHO IS BRAD?
Brad Williams @williamsba
Brad Williams
Co-‐Founder WebDevStudios.com Co-‐Author Professional WordPress
& Professional WordPress Plugin Development
Co-‐Organizer WordCamp Philly
Co-‐Host DradCast
TODAY’S TOPICS
Brad Williams @williamsba
• Cover the big three exploits • SQL InjecLon -‐ SQLi • Cross-‐Site ScripLng -‐ XSS • Cross-‐Site Request Forgery – CSRF
• Hack Examples • Data ValidaLon and SaniLzaLon • 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 InjecLon (SQLi)
SQL INJECTION - SQLI
Brad Williams @williamsba
SQL injec*on is a code injecLon technique, used to aYack data driven applicaLons, in
which malicious SQL statements are inserted into an entry field for execuLon
SQL INJECTION - SQLI
Brad Williams @williamsba
SQL InjecLon Example
global $wpdb; $ID = $_GET['ID']; $sql = "SELECT post_title FROM $wpdb->posts WHERE ID = '$ID';";
SELECT post_Ltle FROM wp_posts WHERE ID = '5';
SQL INJECTION - SQLI
Brad Williams @williamsba
SQL InjecLon Example
SELECT post_Ltle 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
hYp://www.sitepoint.com/forums/showthread.php?83772-‐web-‐site-‐hacked
My IntroducLon to SQLi
SQL INJECTION - SQLI
Brad Williams @williamsba
hYp://www.sitepoint.com/forums/showthread.php?83772-‐web-‐site-‐hacked
My IntroducLon to SQLi
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:
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
hYp://xkcd.com/327/
Don’t be LiYle Bobby Tables
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
Cross-‐Site ScripLng (XSS)
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
What is Cross-‐Site ScripLng?
AYacker 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 funcLons 2. aYr is the context being escaped 3. _e is the opLonal translaLon 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('Hello Europe!');</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
hYp://codex.wordpress.org/FuncLon_Reference/esc_aYr
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
hYp://codex.wordpress.org/FuncLon_Reference/esc_textarea
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
<a href="<?php echo esc_url( $url); ?>">Link</a>
esc_url() Used for validaLng and saniLzing URLs
hYp://codex.wordpress.org/FuncLon_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 enLLes for display
hYp://codex.wordpress.org/FuncLon_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
hYp://codex.wordpress.org/FuncLon_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-‐negaLve integer
hYp://codex.wordpress.org/FuncLon_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 negaLve values
hYp://php.net/manual/en/funcLon.intval.php
<input type="text" name="number_posts" value="<?php echo intval( $number ); ?>" />
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
SaniLzing To saniLze 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() SaniLze a string
hYp://codex.wordpress.org/FuncLon_Reference/saniLze_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
hYp://codex.wordpress.org/FuncLon_Reference/saniLze_email
<?php update_post_meta(
34,
'_email_address',
sanitize_email( $_POST['email'] ) ); ?>
CROSS-SITE SCRIPTING - XSS
Brad Williams @williamsba
sanitize_user() SaniLze username stripping out unsafe characters
hYp://codex.wordpress.org/FuncLon_Reference/saniLze_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.
hYp://codex.wordpress.org/FuncLon_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.
hYp://codex.wordpress.org/FuncLon_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 transmiYed from a user that the website trusts.
Cross-‐site Request Forgery (CSRF)
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
Nonces AcLon, object, & user specific Lme-‐
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:
hYp://codex.wordpress.org/FuncLon_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 );
hYp://codex.wordpress.org/FuncLon_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:
hYp://codex.wordpress.org/FuncLon_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:
hYp://codex.wordpress.org/FuncLon_Reference/check_admin_referer
CROSS-SITE REQUEST FORGERY - CSRF
Brad Williams @williamsba
Nonces Specific to
• WordPress User • AcLon AYempted • Object of aYempted acLon • Time Window
RESOURCES
Brad Williams @williamsba
• Security ArLcles • hYp://codex.wordpress.org/Data_ValidaLon • hYp://codex.wordpress.org/ValidaLng_SaniLzing_and_Escaping_User_Data • hYp://wp.tutsplus.com/tutorials/7-‐simple-‐rules-‐wordpress-‐plugin-‐development-‐best-‐
pracLces/ • hYp://wpengine.com/2013/05/brad-‐williams-‐on-‐secure-‐wordpress-‐development/
• Security PresentaLons • hYp://wordpress.tv/2013/08/09/mike-‐adams-‐three-‐security-‐issues-‐you-‐thought-‐youd-‐fixed/ • hYp://wordpress.tv/2013/09/26/brennen-‐byrne-‐employing-‐best-‐security-‐pracLces-‐for-‐
wordpress-‐sites-‐3/ • hYp://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
RESOURCES
Brad Williams @williamsba
#wceu after party
Drink Time!
CONTACT BRAD
Brad Williams @williamsba
Brad Williams [email protected] Blog: strangework.com TwiYer: @williamsba
Professional WordPress Second EdiLon is OUT!
hYp://bit.ly/prowp2