Make WordPress Core


Ignore:
Timestamp:
12/12/2019 06:17:35 PM (6 years ago)
Author:
whyisjake
Message:

Ensure that a user can publish_posts before making a post sticky.

Props: danielbachhuber, whyisjake, peterwilson, xknown.

Prevent stored XSS through wp_targeted_link_rel().

Props: vortfu, whyisjake, peterwilsoncc, xknown, SergeyBiryukov, flaviozavan.

Update wp_kses_bad_protocol() to recognize : on uri attributes,

wp_kses_bad_protocol() makes sure to validate that uri attributes don’t contain invalid/or not allowed protocols. While this works fine in most cases, there’s a risk that by using the colon html5 named entity, one is able to bypass this function.

Brings r46895 to the 5.3 branch.

Props: xknown, nickdaugherty, peterwilsoncc.

Prevent stored XSS in the block editor.

Brings r46896 to the 5.3 branch.

Prevent escaped unicode characters become unescaped in unsafe HTML during JSON decoding.

Props: aduth, epiqueras.

Location:
branches/5.2
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • branches/5.2

  • branches/5.2/src/wp-includes/formatting.php

    r45991 r46901  
    30443044function wp_targeted_link_rel( $text ) {
    30453045    // Don't run (more expensive) regex if no links with targets.
    3046     if ( stripos( $text, 'target' ) !== false && stripos( $text, '<a ' ) !== false ) {
    3047         $text = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $text );
     3046    if ( stripos( $text, 'target' ) === false || stripos( $text, '<a ' ) === false || is_serialized( $text ) ) {
     3047        return $text;
     3048    }
     3049
     3050    $script_and_style_regex = '/<(script|style).*?<\/\\1>/si';
     3051
     3052    preg_match_all( $script_and_style_regex, $text, $matches );
     3053    $extra_parts = $matches[0];
     3054    $html_parts  = preg_split( $script_and_style_regex, $text );
     3055
     3056    foreach ( $html_parts as &$part ) {
     3057        $part = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $part );
     3058    }
     3059
     3060    $text = '';
     3061    for ( $i = 0; $i < count( $html_parts ); $i++ ) {
     3062        $text .= $html_parts[ $i ];
     3063        if ( isset( $extra_parts[ $i ] ) ) {
     3064            $text .= $extra_parts[ $i ];
     3065        }
    30483066    }
    30493067
     
    30633081 */
    30643082function wp_targeted_link_rel_callback( $matches ) {
    3065     $link_html = $matches[1];
    3066     $rel_match = array();
     3083    $link_html          = $matches[1];
     3084    $original_link_html = $link_html;
     3085
     3086    // Consider the html escaped if there are no unescaped quotes
     3087    $is_escaped = ! preg_match( '/(^|[^\\\\])[\'"]/', $link_html );
     3088    if ( $is_escaped ) {
     3089        // Replace only the quotes so that they are parsable by wp_kses_hair, leave the rest as is
     3090        $link_html = preg_replace( '/\\\\([\'"])/', '$1', $link_html );
     3091    }
     3092
     3093    $atts = wp_kses_hair( $link_html, wp_allowed_protocols() );
    30673094
    30683095    /**
     
    30763103    $rel = apply_filters( 'wp_targeted_link_rel', 'noopener noreferrer', $link_html );
    30773104
    3078     // Avoid additional regex if the filter removes rel values.
    3079     if ( ! $rel ) {
    3080         return "<a $link_html>";
    3081     }
    3082 
    3083     // Value with delimiters, spaces around are optional.
    3084     $attr_regex = '|rel\s*=\s*?(\\\\{0,1}["\'])(.*?)\\1|i';
    3085     preg_match( $attr_regex, $link_html, $rel_match );
    3086 
    3087     if ( empty( $rel_match[0] ) ) {
    3088         // No delimiters, try with a single value and spaces, because `rel =  va"lue` is totally fine...
    3089         $attr_regex = '|rel\s*=(\s*)([^\s]*)|i';
    3090         preg_match( $attr_regex, $link_html, $rel_match );
    3091     }
    3092 
    3093     if ( ! empty( $rel_match[0] ) ) {
    3094         $parts     = preg_split( '|\s+|', strtolower( $rel_match[2] ) );
    3095         $parts     = array_map( 'esc_attr', $parts );
    3096         $needed    = explode( ' ', $rel );
    3097         $parts     = array_unique( array_merge( $parts, $needed ) );
    3098         $delimiter = trim( $rel_match[1] ) ? $rel_match[1] : '"';
    3099         $rel       = 'rel=' . $delimiter . trim( implode( ' ', $parts ) ) . $delimiter;
    3100         $link_html = str_replace( $rel_match[0], $rel, $link_html );
    3101     } elseif ( preg_match( '|target\s*=\s*?\\\\"|', $link_html ) ) {
    3102         $link_html .= " rel=\\\"$rel\\\"";
    3103     } elseif ( preg_match( '#(target|href)\s*=\s*?\'#', $link_html ) ) {
    3104         $link_html .= " rel='$rel'";
    3105     } else {
    3106         $link_html .= " rel=\"$rel\"";
     3105    // Return early if no rel values to be added or if no actual target attribute
     3106    if ( ! $rel || ! isset( $atts['target'] ) ) {
     3107        return "<a $original_link_html>";
     3108    }
     3109
     3110    if ( isset( $atts['rel'] ) ) {
     3111        $all_parts = preg_split( '/\s/', "{$atts['rel']['value']} $rel", -1, PREG_SPLIT_NO_EMPTY );
     3112        $rel       = implode( ' ', array_unique( $all_parts ) );
     3113    }
     3114
     3115    $atts['rel']['whole'] = 'rel="' . esc_attr( $rel ) . '"';
     3116    $link_html            = join( ' ', array_column( $atts, 'whole' ) );
     3117
     3118    if ( $is_escaped ) {
     3119        $link_html = preg_replace( '/[\'"]/', '\\\\$0', $link_html );
    31073120    }
    31083121
     
    48094822
    48104823/**
     4824 * Remove non-allowable HTML from parsed block attribute values when filtering
     4825 * in the post context.
     4826 *
     4827 * @since 5.3.1
     4828 *
     4829 * @param string         $string            Content to be run through KSES.
     4830 * @param array[]|string $allowed_html      An array of allowed HTML elements
     4831 *                                          and attributes, or a context name
     4832 *                                          such as 'post'.
     4833 * @param string[]       $allowed_protocols Array of allowed URL protocols.
     4834 * @return string Filtered text to run through KSES.
     4835 */
     4836function wp_pre_kses_block_attributes( $string, $allowed_html, $allowed_protocols ) {
     4837    /*
     4838     * `filter_block_content` is expected to call `wp_kses`. Temporarily remove
     4839     * the filter to avoid recursion.
     4840     */
     4841    remove_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10 );
     4842    $string = filter_block_content( $string, $allowed_html, $allowed_protocols );
     4843    add_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10, 3 );
     4844
     4845    return $string;
     4846}
     4847
     4848/**
    48114849 * WordPress implementation of PHP sprintf() with filters.
    48124850 *
Note: See TracChangeset for help on using the changeset viewer.