r67684 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r67683‎ | r67684 | r67685 >
Date:06:39, 9 June 2010
Author:tstarling
Status:ok (Comments)
Tags:
Comment:
Fixes for r61911:
* Do not follow redirects by default. This breaks on safe_mode, and may potentially open security vulnerabilities in callers which blacklist domain names. Instead, send followRedirects=true option in the HttpTest caller that needs it.
* Added a check for the cURL security vulnerability CVE-2009-0037, which allowed redirects to file:/// and scp://. Refuse to follow redirects if a vulnerable client library is present.
* Factored out the redirect compatibility test into public function canFollowRedirects() so that callers can provide this information to users.
* In PhpHttpRequest, only follow redirects to HTTP URLs, do not fopen() arbitrary locations. This is not quite as bad as it sounds, since the lack of response headers prevents file:/// content from being returned to the caller.
* Fixed vertical alignment in Http::request(), per style guide.
* 304, 305 and 306 responses are not really redirects and cannot contain a Location header.
Modified paths:
  • /trunk/phase3/includes/HttpFunctions.php (modified) (history)
  • /trunk/phase3/maintenance/tests/HttpTest.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/tests/HttpTest.php
@@ -529,7 +529,7 @@
530530 }
531531
532532 function runCookieRequests() {
533 - $r = HttpRequest::factory( "http://www.php.net/manual" );
 533+ $r = HttpRequest::factory( "http://www.php.net/manual", array( 'followRedirects' => true ) );
534534 $r->execute();
535535
536536 $jar = $r->getCookieJar();
@@ -564,4 +564,4 @@
565565 Http::$httpEngine = 'curl';
566566 self::runCookieRequests();
567567 }
568 -}
\ No newline at end of file
 568+}
Index: trunk/phase3/includes/HttpFunctions.php
@@ -16,16 +16,18 @@
1717 * @param $url string Full URL to act on
1818 * @param $options options to pass to HttpRequest object
1919 * Possible keys for the array:
20 - * timeout Timeout length in seconds
21 - * postData An array of key-value pairs or a url-encoded form data
22 - * proxy The proxy to use.
23 - * Will use $wgHTTPProxy (if set) otherwise.
24 - * noProxy Override $wgHTTPProxy (if set) and don't use any proxy at all.
25 - * sslVerifyHost (curl only) Verify hostname against certificate
26 - * sslVerifyCert (curl only) Verify SSL certificate
27 - * caInfo (curl only) Provide CA information
28 - * maxRedirects Maximum number of redirects to follow (defaults to 5)
29 - * followRedirects Whether to follow redirects (defaults to true)
 20+ * timeout Timeout length in seconds
 21+ * postData An array of key-value pairs or a url-encoded form data
 22+ * proxy The proxy to use.
 23+ * Will use $wgHTTPProxy (if set) otherwise.
 24+ * noProxy Override $wgHTTPProxy (if set) and don't use any proxy at all.
 25+ * sslVerifyHost (curl only) Verify hostname against certificate
 26+ * sslVerifyCert (curl only) Verify SSL certificate
 27+ * caInfo (curl only) Provide CA information
 28+ * maxRedirects Maximum number of redirects to follow (defaults to 5)
 29+ * followRedirects Whether to follow redirects (defaults to false).
 30+ * Note: this should only be used when the target URL is trusted,
 31+ * to avoid attacks on intranet services accessible by HTTP.
3032 * @returns mixed (bool)false on failure or a string on success
3133 */
3234 public static function request( $method, $url, $options = array() ) {
@@ -138,7 +140,7 @@
139141 protected $parsedUrl;
140142 protected $callback;
141143 protected $maxRedirects = 5;
142 - protected $followRedirects = true;
 144+ protected $followRedirects = false;
143145
144146 protected $cookieJar;
145147
@@ -386,7 +388,7 @@
387389 }
388390
389391 $status = (int)$this->respStatus;
390 - if ( $status >= 300 && $status < 400 ) {
 392+ if ( $status >= 300 && $status <= 303 ) {
391393 return true;
392394 }
393395 return false;
@@ -481,6 +483,14 @@
482484
483485 return $this->url;
484486 }
 487+
 488+ /**
 489+ * Returns true if the backend can follow redirects. Overridden by the
 490+ * child classes.
 491+ */
 492+ public function canFollowRedirects() {
 493+ return true;
 494+ }
485495 }
486496
487497
@@ -793,11 +803,21 @@
794804 $this->setStatus();
795805 return $this->status;
796806 }
 807+
 808+ public function canFollowRedirects() {
 809+ if ( strval( ini_get( 'open_basedir' ) ) !== '' || wfIniGetBool( 'safe_mode' ) ) {
 810+ wfDebug( "Cannot follow redirects in safe mode\n" );
 811+ return false;
 812+ }
 813+ if ( !defined( 'CURLOPT_REDIR_PROTOCOLS' ) ) {
 814+ wfDebug( "Cannot follow redirects with libcurl < 7.19.4 due to CVE-2009-0037\n" );
 815+ return false;
 816+ }
 817+ return true;
 818+ }
797819 }
798820
799821 class PhpHttpRequest extends HttpRequest {
800 - protected $manuallyRedirect = false;
801 -
802822 protected function urlToTcp( $url ) {
803823 $parsedUrl = parse_url( $url );
804824
@@ -809,9 +829,7 @@
810830
811831 // At least on Centos 4.8 with PHP 5.1.6, using max_redirects to follow redirects
812832 // causes a segfault
813 - if ( version_compare( '5.1.7', phpversion(), '>' ) ) {
814 - $this->manuallyRedirect = true;
815 - }
 833+ $manuallyRedirect = version_compare( phpversion(), '5.1.7', '<' );
816834
817835 if ( $this->parsedUrl['scheme'] != 'http' ) {
818836 $this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] );
@@ -830,7 +848,7 @@
831849 $options['request_fulluri'] = true;
832850 }
833851
834 - if ( !$this->followRedirects || $this->manuallyRedirect ) {
 852+ if ( !$this->followRedirects || $manuallyRedirect ) {
835853 $options['max_redirects'] = 0;
836854 } else {
837855 $options['max_redirects'] = $this->maxRedirects;
@@ -864,21 +882,32 @@
865883 $reqCount = 0;
866884 $url = $this->url;
867885 do {
868 - $again = false;
869886 $reqCount++;
870887 wfSuppressWarnings();
871888 $fh = fopen( $url, "r", false, $context );
872889 wfRestoreWarnings();
873 - if ( $fh ) {
874 - $result = stream_get_meta_data( $fh );
875 - $this->headerList = $result['wrapper_data'];
876 - $this->parseHeader();
877 - $url = $this->getResponseHeader("Location");
878 - $again = $this->manuallyRedirect && $this->followRedirects && $url
879 - && $this->isRedirect() && $this->maxRedirects > $reqCount;
 890+ if ( !$fh ) {
 891+ break;
880892 }
881 - } while ( $again );
 893+ $result = stream_get_meta_data( $fh );
 894+ $this->headerList = $result['wrapper_data'];
 895+ $this->parseHeader();
 896+ if ( !$manuallyRedirect || !$this->followRedirects ) {
 897+ break;
 898+ }
882899
 900+ # Handle manual redirection
 901+ if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) {
 902+ break;
 903+ }
 904+ # Check security of URL
 905+ $url = $this->getResponseHeader("Location");
 906+ if ( substr( $url, 0, 7 ) !== 'http://' ) {
 907+ wfDebug( __METHOD__.": insecure redirection\n" );
 908+ break;
 909+ }
 910+ } while ( true );
 911+
883912 if ( $oldTimeout !== false ) {
884913 ini_set('default_socket_timeout', $oldTimeout);
885914 }

Follow-up revisions

RevisionCommit summaryAuthorDate
r67840* Fix for r67684: in the curl backend, don't redirect if canFollowRedirects()...tstarling06:10, 11 June 2010
r101808use isValidURI for redirect checkmah14:04, 3 November 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r61911follow-up r61655 fill out the rest of the missing messages...mah06:19, 3 February 2010

Comments

#Comment by Platonides (talk | contribs)   08:39, 9 June 2010

Shouldn't canFollowRedirects() be somewhere in the implementation?

For instance, I don't see the CURLOPT_REDIR_PROTOCOLS actually being checked. That leaves everything to the caller.

Any special reason not to follow https:// urls? (provided the user has openssl extension enabled)

#Comment by Tim Starling (talk | contribs)   06:15, 11 June 2010

Fixed in r67840. The special reason not to follow https:// URLs is that they were already disallowed:

if ( $this->parsedUrl['scheme'] != 'http' ) {
	$this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] );
}

It wouldn't make sense to allow them for redirect targets but not for initial requests.

#Comment by Brion VIBBER (talk | contribs)   18:28, 1 June 2011

Stumbled on this old change while poking something else; I'm not sure that that check should actually be there to begin with, I suspect it was meant to exclude file paths and such rather than to actually exclude https specifically.

I've stuck a quick note in bug 29231 -- the addition of the check seems to have been in refactoring in r61352, and it probably should just be changed to check both 'http' and 'https'.

As for the curl mode; we could probably handle the redirect following ourselves rather than letting CURL do it, so we can apply our own security checks on each fresh URL, so there'd be no need to check for the old broken versions.

#Comment by Brion VIBBER (talk | contribs)   18:34, 1 June 2011

made a note on that in bug bug 29232.

#Comment by Duplicatebug (talk | contribs)   17:23, 1 April 2011

Please have a look at bug 28277. Transwiki import is broken for some interlanguage sources, when the source is on another sister project, like en.wikipedia for pt.wikibooks, because $followRedirects defaults to false

#Comment by Hashar (talk | contribs)   15:07, 3 November 2011

r101808 changed the evil regex to use HTTP::isValidURI() this way we allow redirects to both http and https.

Status & tagging log