Prevent stored XSS through wp_targeted_link_rel().
Props: vortfu, whyisjake, peterwilsoncc, xknown, SergeyBiryukov, flaviozavan. Built from https://develop.svn.wordpress.org/trunk@46894 git-svn-id: http://core.svn.wordpress.org/trunk@46694 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
9e87c5b7b4
commit
80eab318eb
|
@ -3148,9 +3148,25 @@ function wp_rel_ugc( $text ) {
|
||||||
*/
|
*/
|
||||||
function wp_targeted_link_rel( $text ) {
|
function wp_targeted_link_rel( $text ) {
|
||||||
// Don't run (more expensive) regex if no links with targets.
|
// Don't run (more expensive) regex if no links with targets.
|
||||||
if ( stripos( $text, 'target' ) !== false && stripos( $text, '<a ' ) !== false ) {
|
if ( stripos( $text, 'target' ) === false || stripos( $text, '<a ' ) === false || is_serialized( $text ) ) {
|
||||||
if ( ! is_serialized( $text ) ) {
|
return $text;
|
||||||
$text = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $text );
|
}
|
||||||
|
|
||||||
|
$script_and_style_regex = '/<(script|style).*?<\/\\1>/si';
|
||||||
|
|
||||||
|
preg_match_all( $script_and_style_regex, $text, $matches );
|
||||||
|
$extra_parts = $matches[0];
|
||||||
|
$html_parts = preg_split( $script_and_style_regex, $text );
|
||||||
|
|
||||||
|
foreach ( $html_parts as &$part ) {
|
||||||
|
$part = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $part );
|
||||||
|
}
|
||||||
|
|
||||||
|
$text = '';
|
||||||
|
for ( $i = 0; $i < count( $html_parts ); $i++ ) {
|
||||||
|
$text .= $html_parts[ $i ];
|
||||||
|
if ( isset( $extra_parts[ $i ] ) ) {
|
||||||
|
$text .= $extra_parts[ $i ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3170,7 +3186,16 @@ function wp_targeted_link_rel( $text ) {
|
||||||
*/
|
*/
|
||||||
function wp_targeted_link_rel_callback( $matches ) {
|
function wp_targeted_link_rel_callback( $matches ) {
|
||||||
$link_html = $matches[1];
|
$link_html = $matches[1];
|
||||||
$rel_match = array();
|
$original_link_html = $link_html;
|
||||||
|
|
||||||
|
// Consider the html escaped if there are no unescaped quotes
|
||||||
|
$is_escaped = ! preg_match( '/(^|[^\\\\])[\'"]/', $link_html );
|
||||||
|
if ( $is_escaped ) {
|
||||||
|
// Replace only the quotes so that they are parsable by wp_kses_hair, leave the rest as is
|
||||||
|
$link_html = preg_replace( '/\\\\([\'"])/', '$1', $link_html );
|
||||||
|
}
|
||||||
|
|
||||||
|
$atts = wp_kses_hair( $link_html, wp_allowed_protocols() );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the rel values that are added to links with `target` attribute.
|
* Filters the rel values that are added to links with `target` attribute.
|
||||||
|
@ -3182,35 +3207,21 @@ function wp_targeted_link_rel_callback( $matches ) {
|
||||||
*/
|
*/
|
||||||
$rel = apply_filters( 'wp_targeted_link_rel', 'noopener noreferrer', $link_html );
|
$rel = apply_filters( 'wp_targeted_link_rel', 'noopener noreferrer', $link_html );
|
||||||
|
|
||||||
// Avoid additional regex if the filter removes rel values.
|
// Return early if no rel values to be added or if no actual target attribute
|
||||||
if ( ! $rel ) {
|
if ( ! $rel || ! isset( $atts['target'] ) ) {
|
||||||
return "<a $link_html>";
|
return "<a $original_link_html>";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value with delimiters, spaces around are optional.
|
if ( isset( $atts['rel'] ) ) {
|
||||||
$attr_regex = '|rel\s*=\s*?(\\\\{0,1}["\'])(.*?)\\1|i';
|
$all_parts = preg_split( '/\s/', "{$atts['rel']['value']} $rel", -1, PREG_SPLIT_NO_EMPTY );
|
||||||
preg_match( $attr_regex, $link_html, $rel_match );
|
$rel = implode( ' ', array_unique( $all_parts ) );
|
||||||
|
|
||||||
if ( empty( $rel_match[0] ) ) {
|
|
||||||
// No delimiters, try with a single value and spaces, because `rel = va"lue` is totally fine...
|
|
||||||
$attr_regex = '|rel\s*=(\s*)([^\s]*)|i';
|
|
||||||
preg_match( $attr_regex, $link_html, $rel_match );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! empty( $rel_match[0] ) ) {
|
$atts['rel']['whole'] = 'rel="' . esc_attr( $rel ) . '"';
|
||||||
$parts = preg_split( '|\s+|', strtolower( $rel_match[2] ) );
|
$link_html = join( ' ', array_column( $atts, 'whole' ) );
|
||||||
$parts = array_map( 'esc_attr', $parts );
|
|
||||||
$needed = explode( ' ', $rel );
|
if ( $is_escaped ) {
|
||||||
$parts = array_unique( array_merge( $parts, $needed ) );
|
$link_html = preg_replace( '/[\'"]/', '\\\\$0', $link_html );
|
||||||
$delimiter = trim( $rel_match[1] ) ? $rel_match[1] : '"';
|
|
||||||
$rel = 'rel=' . $delimiter . trim( implode( ' ', $parts ) ) . $delimiter;
|
|
||||||
$link_html = str_replace( $rel_match[0], $rel, $link_html );
|
|
||||||
} elseif ( preg_match( '|target\s*=\s*?\\\\"|', $link_html ) ) {
|
|
||||||
$link_html .= " rel=\\\"$rel\\\"";
|
|
||||||
} elseif ( preg_match( '#(target|href)\s*=\s*?\'#', $link_html ) ) {
|
|
||||||
$link_html .= " rel='$rel'";
|
|
||||||
} else {
|
|
||||||
$link_html .= " rel=\"$rel\"";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "<a $link_html>";
|
return "<a $link_html>";
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
*
|
*
|
||||||
* @global string $wp_version
|
* @global string $wp_version
|
||||||
*/
|
*/
|
||||||
$wp_version = '5.4-alpha-46893';
|
$wp_version = '5.4-alpha-46894';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
||||||
|
|
Loading…
Reference in New Issue