Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/wp-includes/kses.php
Original file line number Diff line number Diff line change
Expand Up @@ -3152,7 +3152,11 @@ function wp_kses_bad_protocol_once( $content, $allowed_protocols, $count = 1 ) {
$content = preg_replace( '/(&#0*58(?![;0-9])|&#x0*3a(?![;a-f0-9]))/i', '$1;', $content );
$content2 = preg_split( '/:|&#0*58;|&#x0*3a;|:/i', $content, 2 );

if ( isset( $content2[1] ) && ! preg_match( '%/\?%', $content2[0] ) ) {
/*
* A scheme cannot contain "/", "?" or "#", so a colon after one of them is not a protocol separator.
* A "#" right after "&" or "&" may belong to a character reference, so it is not counted.
*/
if ( isset( $content2[1] ) && ! preg_match( '%[/?]|(?<!&)(?<!&amp;)#%', $content2[0] ) ) {
$content = trim( $content2[1] );
$protocol = wp_kses_bad_protocol_once2( $content2[0], $allowed_protocols );
if ( 'feed:' === $protocol ) {
Expand Down
8 changes: 8 additions & 0 deletions tests/phpunit/tests/formatting/escUrl.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,4 +311,12 @@ public function test_ipv6_hosts() {
$this->assertSame( '//[::FFFF::127.0.0.1]/?foo%5Bbar%5D=baz', esc_url( '//[::FFFF::127.0.0.1]/?foo[bar]=baz' ) );
$this->assertSame( 'http://[::FFFF::127.0.0.1]/?foo%5Bbar%5D=baz', esc_url( 'http://[::FFFF::127.0.0.1]/?foo[bar]=baz' ) );
}

/**
* @ticket 41304
*/
public function test_colon_after_fragment_or_query() {
$this->assertSame( '#fn:1', esc_url( '#fn:1' ), 'A colon in a fragment should be kept.' );
$this->assertSame( '?page=a:b', esc_url( '?page=a:b' ), 'A colon in a query should be kept.' );
}
}
108 changes: 108 additions & 0 deletions tests/phpunit/tests/kses.php
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,114 @@ public function test_wp_kses_bad_protocol() {
}
}

/**
* Tests that a colon after a path, query or fragment is not treated as a protocol.
*
* @ticket 41304
*
* @covers ::wp_kses_bad_protocol
*
* @dataProvider data_wp_kses_bad_protocol_should_keep_colon_after_path_query_or_fragment
*
* @param string $url URL to check.
*/
public function test_wp_kses_bad_protocol_should_keep_colon_after_path_query_or_fragment( $url ) {
$this->assertSame( $url, wp_kses_bad_protocol( $url, wp_allowed_protocols() ) );
}

/**
* Data provider.
*
* @return array[]
*/
public function data_wp_kses_bad_protocol_should_keep_colon_after_path_query_or_fragment() {
return array(
'footnote fragment' => array( '#fn:1' ),
'fragment' => array( '#section:2' ),
'path and fragment' => array( '/page#section:2' ),
'relative path' => array( 'path/to:file' ),
'query' => array( 'images?q=tbn:abc' ),
'text fragment' => array( '#:~:text=highlight' ),
'query and text fragment' => array( '/page?a=1&b=2#:~:text=highlight' ),
'protocol-relative URL with port' => array( '//example.org:8080/' ),
'semicolon before fragment' => array( 'path;#section:2' ),
'feed URL with port' => array( 'feed://example.org:8080/' ),
);
}

/**
* Tests that a fragment containing a colon is kept when only other protocols are allowed.
*
* @ticket 41304
*
* @covers ::wp_kses_bad_protocol
*/
public function test_wp_kses_bad_protocol_should_keep_fragment_with_colon_when_only_mailto_is_allowed() {
$this->assertSame( '#fn:1', wp_kses_bad_protocol( '#fn:1', array( 'mailto' ) ) );
}

/**
* Tests that a protocol is still removed when a path, query or fragment follows it.
*
* @ticket 41304
*
* @covers ::wp_kses_bad_protocol
*
* @dataProvider data_wp_kses_bad_protocol_should_strip_protocol_before_path_query_or_fragment
*
* @param string $url URL to check.
* @param string $expected Expected result.
*/
public function test_wp_kses_bad_protocol_should_strip_protocol_before_path_query_or_fragment( $url, $expected ) {
$this->assertSame( $expected, wp_kses_bad_protocol( $url, wp_allowed_protocols() ) );
}

/**
* Data provider.
*
* @return array[]
*/
public function data_wp_kses_bad_protocol_should_strip_protocol_before_path_query_or_fragment() {
return array(
'colon before fragment' => array( 'javascript:alert(1)#x', 'alert(1)#x' ),
'colon before path' => array( 'javascript:alert(1)//x', 'alert(1)//x' ),
'decimal entity colon' => array( 'javascript&#58;alert(1)/x', 'alert(1)/x' ),
'hex entity colon' => array( 'javascript&#x3a;alert(1)?x', 'alert(1)?x' ),
'named entity colon' => array( 'javascript&colon;alert(1)#x', 'alert(1)#x' ),
'decimal entity in scheme' => array( '&#106;avascript:alert(1)', 'alert(1)' ),
'hex entity in scheme' => array( 'java&#x73;cript:alert(1)', 'alert(1)' ),
'encoded number sign in scheme' => array( 'java&#35;script:alert(1)', 'alert(1)' ),
'escaped entity in scheme' => array( '&amp;#106;avascript:alert(1)', 'alert(1)' ),
);
}

/**
* Tests that wp_kses_post() keeps links whose fragment contains a colon.
*
* @ticket 41304
*
* @covers ::wp_kses_post
*/
public function test_wp_kses_post_should_keep_footnote_link() {
$html = '<a href="#fn:1">1</a>';

$this->assertEqualHTML( $html, wp_kses_post( $html ) );
}

/**
* Tests that wp_kses_post() treats a decoded number sign before the colon as a relative link.
*
* @ticket 41304
*
* @covers ::wp_kses_post
*/
public function test_wp_kses_post_should_keep_encoded_number_sign_before_colon_as_relative_link() {
$this->assertEqualHTML(
'<a href="java#script:alert(1)">x</a>',
wp_kses_post( '<a href="java&#35;script:alert(1)">x</a>' )
);
}

public function test_hackers_attacks() {
$xss = simplexml_load_file( DIR_TESTDATA . '/formatting/xssAttacks.xml' );
foreach ( $xss->attack as $attack ) {
Expand Down
Loading