Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Security with WordPress

Jon Ang
April 28, 2016

Security with WordPress

Some standard security stuff to watch out for when developing with WordPress (WordPress itself is safe, developers usually aren't)

Jon Ang

April 28, 2016
Tweet

More Decks by Jon Ang

Other Decks in Technology

Transcript

  1. Topics Today • Talk: Pressing On – Jon Ang •

    WordPress Security – Jon Ang • Community Open Space – Everyone [ 3 ]
  2. Types • CSRF (Cross-Site Request Forgery) • XSS (Cross-Site Scripting)

    • SQLI (SQL Injection) • And more • Examples provided by George Stephanis [ 13 ]
  3. CSRF • Cross-Site Request Forgery attacks involve malicious websites attempting

    to forge legitimate requests, often from a different site. • Can your code tell the difference between • <form action="process.php"> <input type="hidden" name="action" value="delete" /> <input type="hidden" name="id" value="42" /> <input type="submit" /> </form> • and • <img src="//target.io/admin/process.php?action=delete&id=42" /> [ 14 ]
  4. CSRF • How do we know the submission is legitimate?

    • We check for something that only we can provide — a nonce. • NONCE: Number used ONCE. • Nonces in WordPress are valid for 12–24 hours, and are generated from: • the current 12 hour tick, • the action name, • the current user’s ID, • their session token. • It also leverages the unique generated strings in wp-config.php for security. [ 15 ]
  5. CSRF • Revisiting our previous example: • <form action="process.php"> +

    → <input type="hidden" name="_nonce" value="JD7ANaoKG" /> <input type="hidden" name="action" value="delete" /> <input type="hidden" name="id" value="42" /> <input type="submit" /> </form> • The rotating nonce cannot be anticipated. • <img src="//target.io/admin/process.php?action=delete&id=42&_nonce=…………?" /> • ALSO: Nonces are for intent, not for authentication! They are for validating that the user intended to do an action. Always also check against user caps. [ 16 ]
  6. XSS • Cross-Site Scripting means — in short — that

    some villain can sneak malicious javascript code into your site, and trick you into running the script. • Anything* you can do, it can do too! • * (nearly) [ 17 ]
  7. XSS • <div id="<?php echo $_GET['id']; ?>"> • But what

    about … • /page?id=X"><script>alert('XSS');</script><" • Becomes … • <div id="X"><script>alert('XSS');</script><""> [ 18 ]
  8. XSS • <div id="<?php echo esc_attr( $_GET[‘id’] ); ?>"> •

    Again, if we try … • /page?id=X"><script>alert('XSS');</script><" • This time, it becomes … • <div id="X&quot;&gt;&lt;script&gt;alert(&#039;XSS&# 039;);&lt;/script&gt;&lt;&quot;"> [ 20 ]
  9. XSS • It does a lot more than cause alerts.

    • It can load up admin pages, extract nonces, and spoof form submissions. • It can use the Plugin and Theme Editor in the admin to write nasty PHP code to your server. • It can change your password without your knowledge. • It can create a new Administrator account for itself. • It can install new plugins and themes. • It can inject malware links and embeds into your posts. [ 21 ]
  10. XSS • Cross-Site Scripting isn’t all-powerful. • Anything that you

    need to re-authenticate for, it can’t spoof. #31779-core • Anything the currently logged in user can’t do, XSS can’t either. • This is why folks recommend creating yourself an Author or Editor account for every day use, and not doing everything as Administrator. • Rule of : If a sitting at your can do it, so can XSS. ! [ 22 ]
  11. SQLi • SQL Injection lets attackers shoehorn tricky code into

    existing database calls. • SELECT * FROM `wp_posts` WHERE `slug` = '{$value}' LIMIT 1; • // $value = "foo" SELECT * FROM `wp_posts` WHERE `slug` = 'foo' LIMIT 1; • // $value = "x'; UPDATE `wp_users` SET `user_email` = '[email protected]' WHERE `ID` = 1; --" • SELECT * FROM `wp_posts` WHERE `slug` = 'x'; • UPDATE `wp_users` SET `user_email` = '[email protected]' WHERE `ID` = 1; • -- LIMIT 1; [ 23 ]
  12. SQLi • So what can SQL Injection do? • DROP

    (delete) all of the tables in your database. • INSERT new users, posts, etc. • DELETE users, posts, meta. • UPDATE (change) user info, passwords, roles. • Getting access to an Administrator user (either by creating a new one or changing the password of an existing) is normally game over. [ 24 ]
  13. SQLi • ! This code is a simplified version of

    real code that was once in use by a popular plugin (that shall remain nameless). It is insecure. Do not use it. • $orderby = 'post_title'; $order = 'ASC'; if ( ! empty( $_GET['orderby'] ) { $orderby = esc_sql( sanitize_text_field( $_GET['orderby'] ) ); } if ( ! empty( $_GET['order'] ) ) { $order = esc_sql( strtoupper( sanitize_text_field( $_GET['order'] ) ) ); } $wpdb->get_col( "SELECT `ID` FROM {$wpdb->posts} ORDER BY {$orderby} {$order} LIMIT 10" ); • We’re fetching posts and letting the user change the sorting. • Fairly harmless? #nopenopenope [ 25 ]
  14. SQLi • But what do the functions do? –sanitize_text_field(): Checks

    for invalid UTF-8, Convert single < characters to entity, strip all tags, remove line breaks, tabs and extra white space, strip octets. • Not really useful (or relevant) to us here. –esc_sql(): Prepares a string for use as an SQL query. This function is a glorified addslashes() that works with arrays. • esc_sql() is also deprecated in favor of using $wpdb->prepare(). It will escape single quotes in values, but as ORDER and ORDERBY aren’t escaped in the query, in this case it’s useless. [ 26 ]
  15. SQLi • Sometimes generic functions aren’t enough. • We can

    fix the prior example by whitelisting values: • $orderby = 'post_title'; $order = 'ASC'; $orderbys = array( 'slug', 'id', 'post_date' ); if ( isset( $_GET['orderby'] && in_array( $_GET['orderby'], $orderbys ) ) { $orderby = $_GET['orderby']; } if ( isset( $_GET['order'] ) && 'DESC' === strtoupper( $_GET['order'] ) ) { $order = 'DESC'; } $wpdb->get_col( "SELECT `ID` FROM {$wpdb->posts} ORDER BY {$orderby} {$order} LIMIT 10" ); [ 27 ]
  16. SQLi • The `$wpdb->prepare()` method will take your data, escape

    it, and then pop them into your query. • $wpdb->prepare( "SELECT * FROM `table` WHERE `str` = %s AND `num` = %d;”, "Sonny's", 3 ); • Becomes… • SELECT * FROM `table` WHERE `str` = 'Sonny\'s' AND `num` = 3; • But you can’t use prepare with order / orderby arguments as they’re not quoted in the query. [ 28 ]
  17. Few ways to avoid this • Developers – There are

    not many use-cases to write your own SQL code – Use WordPress’ Classes, Objects and Queries • Eg. Use WP_Query instead of writing your own SQL line • Users – Ask your developers to use native WordPress functions [ 29 ]
  18. Take-away • Developers – Don’t trust user input • Users

    – Ask developers if they have taken appropriate CSRF, XSS, SQLi preventions – Like asking your local butcher if the meat has been through AVA checks [ 30 ]
  19. Best Practices • Keep Upgrading • Checking for intent •

    Sanitising • Escaping • Check for user capabilities • Whitelisting [ 32 ]
  20. Sanitisation • Sanitise Early – Because variables can be passed

    around at any time, sanitise early so you know it’s clean, because other functions that make use of your variable may not have cleaning lines • Understand your content – What content do you want? • Functions – intval() – sanitize_text_field() – https://codex.wordpress.org/Validating_Sanitizing_and_Escaping_User_Data [ 39 ]
  21. Escaping • Escape late – Variable can be used to

    output to different context – Different context requires different code • Understand the context – What is it going to be used for? • Functions – esc_url – esc_html – https://codex.wordpress.org/Validating_Sanitizing_and_Escaping_User_Da ta [ 40 ]
  22. SSL • Has a huge advantage – Allows HTTP/2 and

    SPDY – Secured sites are ranked higher by Google – Fast sites are ranked higher too [ 43 ]
  23. Escaping • Plugins – Contact the plugin author • Especially

    if it’s a non WordPress.org plugin – Contact [email protected] • Themes – Contact the theme author • WordPress Core – Contact [email protected] [ 45 ]