Skip to content

Commit cfdde3b

Browse files
committed
Better validation of the URL used in HTTP redirects.
Merges [36444] to the 3.8 branch. git-svn-id: https://develop.svn.wordpress.org/branches/3.8@36453 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 09625cc commit cfdde3b

2 files changed

Lines changed: 117 additions & 3 deletions

File tree

src/wp-includes/pluggable.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,8 @@ function wp_validate_redirect($location, $default = '') {
973973
// In php 5 parse_url may fail if the URL query part contains http://, bug #38143
974974
$test = ( $cut = strpos($location, '?') ) ? substr( $location, 0, $cut ) : $location;
975975

976-
$lp = parse_url($test);
976+
// @-operator is used to prevent possible warnings in PHP < 5.3.3.
977+
$lp = @parse_url($test);
977978

978979
// Give up if malformed URL
979980
if ( false === $lp )
@@ -983,9 +984,17 @@ function wp_validate_redirect($location, $default = '') {
983984
if ( isset($lp['scheme']) && !('http' == $lp['scheme'] || 'https' == $lp['scheme']) )
984985
return $default;
985986

986-
// Reject if scheme is set but host is not. This catches urls like https:host.com for which parse_url does not set the host field.
987-
if ( isset($lp['scheme']) && !isset($lp['host']) )
987+
// Reject if certain components are set but host is not. This catches urls like https:host.com for which parse_url does not set the host field.
988+
if ( ! isset( $lp['host'] ) && ( isset( $lp['scheme'] ) || isset( $lp['user'] ) || isset( $lp['pass'] ) || isset( $lp['port'] ) ) ) {
988989
return $default;
990+
}
991+
992+
// Reject malformed components parse_url() can return on odd inputs.
993+
foreach ( array( 'user', 'pass', 'host' ) as $component ) {
994+
if ( isset( $lp[ $component ] ) && strpbrk( $lp[ $component ], ':/?#@' ) ) {
995+
return $default;
996+
}
997+
}
989998

990999
$wpp = parse_url(home_url());
9911000

tests/phpunit/tests/formatting/redirect.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,21 @@
33
/**
44
* @group pluggable
55
* @group formatting
6+
* @group redirect
67
*/
78
class Tests_Formatting_Redirect extends WP_UnitTestCase {
9+
function setUp() {
10+
add_filter( 'home_url', array( $this, 'home_url' ) );
11+
}
12+
13+
function tearDown() {
14+
remove_filter( 'home_url', array( $this, 'home_url' ) );
15+
}
16+
17+
function home_url() {
18+
return 'http://example.com/';
19+
}
20+
821
function test_wp_sanitize_redirect() {
922
$this->assertEquals('http://example.com/watchthelinefeedgo', wp_sanitize_redirect('http://example.com/watchthelinefeed%0Ago'));
1023
$this->assertEquals('http://example.com/watchthelinefeedgo', wp_sanitize_redirect('http://example.com/watchthelinefeed%0ago'));
@@ -14,4 +27,96 @@ function test_wp_sanitize_redirect() {
1427
$this->assertEquals('http://example.com/watchthecarriagereturngo', wp_sanitize_redirect('http://example.com/watchthecarriagereturn%0%0ddgo'));
1528
$this->assertEquals('http://example.com/watchthecarriagereturngo', wp_sanitize_redirect('http://example.com/watchthecarriagereturn%0%0DDgo'));
1629
}
30+
31+
/**
32+
* @dataProvider valid_url_provider
33+
*/
34+
function test_wp_validate_redirect_valid_url( $url, $expected ) {
35+
$this->assertEquals( $expected, wp_validate_redirect( $url ) );
36+
}
37+
38+
/**
39+
* @dataProvider invalid_url_provider
40+
*/
41+
function test_wp_validate_redirect_invalid_url( $url ) {
42+
$this->assertEquals( false, wp_validate_redirect( $url, false ) );
43+
}
44+
45+
function valid_url_provider() {
46+
return array(
47+
array( 'http://example.com', 'http://example.com' ),
48+
array( 'http://example.com/', 'http://example.com/' ),
49+
array( 'https://example.com/', 'https://example.com/' ),
50+
array( '//example.com', 'http://example.com' ),
51+
array( '//example.com/', 'http://example.com/' ),
52+
array( 'http://example.com/?foo=http://example.com/', 'http://example.com/?foo=http://example.com/' ),
53+
array( 'http://user@example.com/', 'http://user@example.com/' ),
54+
array( 'http://user:@example.com/', 'http://user:@example.com/' ),
55+
array( 'http://user:pass@example.com/', 'http://user:pass@example.com/' ),
56+
);
57+
}
58+
59+
function invalid_url_provider() {
60+
return array(
61+
// parse_url() fails
62+
array( '' ),
63+
array( 'http://:' ),
64+
65+
// non-safelisted domain
66+
array( 'http://non-safelisted.example/' ),
67+
68+
// unsupported schemes
69+
array( 'data:text/plain;charset=utf-8,Hello%20World!' ),
70+
array( 'file:///etc/passwd' ),
71+
array( 'ftp://example.com/' ),
72+
73+
// malformed input
74+
array( 'http:example.com' ),
75+
array( 'http:80' ),
76+
array( 'http://example.com:1234:5678/' ),
77+
array( 'http://user:pa:ss@example.com/' ),
78+
79+
array( 'http://user@@example.com' ),
80+
array( 'http://user@:example.com' ),
81+
array( 'http://user?@example.com' ),
82+
array( 'http://user@?example.com' ),
83+
array( 'http://user#@example.com' ),
84+
array( 'http://user@#example.com' ),
85+
86+
array( 'http://user@@example.com/' ),
87+
array( 'http://user@:example.com/' ),
88+
array( 'http://user?@example.com/' ),
89+
array( 'http://user@?example.com/' ),
90+
array( 'http://user#@example.com/' ),
91+
array( 'http://user@#example.com/' ),
92+
93+
array( 'http://user:pass@@example.com' ),
94+
array( 'http://user:pass@:example.com' ),
95+
array( 'http://user:pass?@example.com' ),
96+
array( 'http://user:pass@?example.com' ),
97+
array( 'http://user:pass#@example.com' ),
98+
array( 'http://user:pass@#example.com' ),
99+
100+
array( 'http://user:pass@@example.com/' ),
101+
array( 'http://user:pass@:example.com/' ),
102+
array( 'http://user:pass?@example.com/' ),
103+
array( 'http://user:pass@?example.com/' ),
104+
array( 'http://user:pass#@example.com/' ),
105+
array( 'http://user:pass@#example.com/' ),
106+
107+
array( 'http://user.pass@@example.com' ),
108+
array( 'http://user.pass@:example.com' ),
109+
array( 'http://user.pass?@example.com' ),
110+
array( 'http://user.pass@?example.com' ),
111+
array( 'http://user.pass#@example.com' ),
112+
array( 'http://user.pass@#example.com' ),
113+
114+
array( 'http://user.pass@@example.com/' ),
115+
array( 'http://user.pass@:example.com/' ),
116+
array( 'http://user.pass?@example.com/' ),
117+
array( 'http://user.pass@?example.com/' ),
118+
array( 'http://user.pass#@example.com/' ),
119+
array( 'http://user.pass@#example.com/' ),
120+
);
121+
}
17122
}

0 commit comments

Comments
 (0)