r89558 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r89557‎ | r89558 | r89559 >
Date:11:59, 6 June 2011
Author:tstarling
Status:ok (Comments)
Tags:
Comment:
* Added a REQUEST_URI check to the bug 28235 handling.
* Moved most of the bug 28235 code out to a separate library class, since I was running out of distinct function names.
* Merged the QUERY_STRING and PATH_INFO security checks, since they are dealing with the exact same problem. Removed WebRequest::isQueryStringBad().
* Deal with img_auth.php by having it specify what extension it expects to be streaming out. This extension can then be compared with the extension that IE might detect.
Modified paths:
  • /trunk/phase3/img_auth.php (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/WebRequest.php (modified) (history)
  • /trunk/phase3/includes/libs/IEUrlExtension.php (added) (history)
  • /trunk/phase3/tests/phpunit/includes/FindIE6ExtensionTest.php (modified) (history)

Diff [purge]

Index: trunk/phase3/tests/phpunit/includes/FindIE6ExtensionTest.php
@@ -1,13 +1,13 @@
22 <?php
33
44 /**
5 - * Tests for WebRequest::findIE6Extension
 5+ * Tests for IEUrlExtension::findIE6Extension
66 */
7 -class FindIE6ExtensionTest extends MediaWikiTestCase {
 7+class IEUrlExtensionTest extends MediaWikiTestCase {
88 function testSimple() {
99 $this->assertEquals(
1010 'y',
11 - WebRequest::findIE6Extension( 'x.y' ),
 11+ IEUrlExtension::findIE6Extension( 'x.y' ),
1212 'Simple extension'
1313 );
1414 }
@@ -15,7 +15,7 @@
1616 function testSimpleNoExt() {
1717 $this->assertEquals(
1818 '',
19 - WebRequest::findIE6Extension( 'x' ),
 19+ IEUrlExtension::findIE6Extension( 'x' ),
2020 'No extension'
2121 );
2222 }
@@ -23,7 +23,7 @@
2424 function testEmpty() {
2525 $this->assertEquals(
2626 '',
27 - WebRequest::findIE6Extension( '' ),
 27+ IEUrlExtension::findIE6Extension( '' ),
2828 'Empty string'
2929 );
3030 }
@@ -31,7 +31,7 @@
3232 function testQuestionMark() {
3333 $this->assertEquals(
3434 '',
35 - WebRequest::findIE6Extension( '?' ),
 35+ IEUrlExtension::findIE6Extension( '?' ),
3636 'Question mark only'
3737 );
3838 }
@@ -39,7 +39,7 @@
4040 function testExtQuestionMark() {
4141 $this->assertEquals(
4242 'x',
43 - WebRequest::findIE6Extension( '.x?' ),
 43+ IEUrlExtension::findIE6Extension( '.x?' ),
4444 'Extension then question mark'
4545 );
4646 }
@@ -47,7 +47,7 @@
4848 function testQuestionMarkExt() {
4949 $this->assertEquals(
5050 'x',
51 - WebRequest::findIE6Extension( '?.x' ),
 51+ IEUrlExtension::findIE6Extension( '?.x' ),
5252 'Question mark then extension'
5353 );
5454 }
@@ -55,7 +55,7 @@
5656 function testInvalidChar() {
5757 $this->assertEquals(
5858 '',
59 - WebRequest::findIE6Extension( '.x*' ),
 59+ IEUrlExtension::findIE6Extension( '.x*' ),
6060 'Extension with invalid character'
6161 );
6262 }
@@ -63,7 +63,7 @@
6464 function testInvalidCharThenExtension() {
6565 $this->assertEquals(
6666 'x',
67 - WebRequest::findIE6Extension( '*.x' ),
 67+ IEUrlExtension::findIE6Extension( '*.x' ),
6868 'Invalid character followed by an extension'
6969 );
7070 }
@@ -71,7 +71,7 @@
7272 function testMultipleQuestionMarks() {
7373 $this->assertEquals(
7474 'c',
75 - WebRequest::findIE6Extension( 'a?b?.c?.d?e?f' ),
 75+ IEUrlExtension::findIE6Extension( 'a?b?.c?.d?e?f' ),
7676 'Multiple question marks'
7777 );
7878 }
@@ -79,7 +79,7 @@
8080 function testExeException() {
8181 $this->assertEquals(
8282 'd',
83 - WebRequest::findIE6Extension( 'a?b?.exe?.d?.e' ),
 83+ IEUrlExtension::findIE6Extension( 'a?b?.exe?.d?.e' ),
8484 '.exe exception'
8585 );
8686 }
@@ -87,7 +87,7 @@
8888 function testExeException2() {
8989 $this->assertEquals(
9090 'exe',
91 - WebRequest::findIE6Extension( 'a?b?.exe' ),
 91+ IEUrlExtension::findIE6Extension( 'a?b?.exe' ),
9292 '.exe exception 2'
9393 );
9494 }
@@ -95,7 +95,7 @@
9696 function testHash() {
9797 $this->assertEquals(
9898 '',
99 - WebRequest::findIE6Extension( 'a#b.c' ),
 99+ IEUrlExtension::findIE6Extension( 'a#b.c' ),
100100 'Hash character preceding extension'
101101 );
102102 }
@@ -103,7 +103,7 @@
104104 function testHash2() {
105105 $this->assertEquals(
106106 '',
107 - WebRequest::findIE6Extension( 'a?#b.c' ),
 107+ IEUrlExtension::findIE6Extension( 'a?#b.c' ),
108108 'Hash character preceding extension 2'
109109 );
110110 }
@@ -111,7 +111,7 @@
112112 function testDotAtEnd() {
113113 $this->assertEquals(
114114 '',
115 - WebRequest::findIE6Extension( '.' ),
 115+ IEUrlExtension::findIE6Extension( '.' ),
116116 'Dot at end of string'
117117 );
118118 }
Index: trunk/phase3/includes/WebRequest.php
@@ -772,25 +772,23 @@
773773 * message or redirect to a safer URL. Returns true if the URL is OK, and
774774 * false if an error message has been shown and the request should be aborted.
775775 */
776 - public function checkUrlExtension() {
777 - $query = isset( $_SERVER['QUERY_STRING'] ) ? $_SERVER['QUERY_STRING'] : '';
778 - if ( self::isUrlExtensionBad( $query ) ) {
 776+ public function checkUrlExtension( $extWhitelist = array() ) {
 777+ global $wgScriptExtension;
 778+ $extWhitelist[] = ltrim( $wgScriptExtension, '.' );
 779+ if ( IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist ) ) {
779780 if ( !$this->wasPosted() ) {
780 - if ( $this->attemptExtensionSecurityRedirect() ) {
 781+ $newUrl = IEUrlExtension::fixUrlForIE6(
 782+ $this->getFullRequestURL(), $extWhitelist );
 783+ if ( $newUrl !== false ) {
 784+ $this->doSecurityRedirect( $newUrl );
781785 return false;
782786 }
783787 }
784788 wfHttpError( 403, 'Forbidden',
785 - 'Invalid file extension found in the query string.' );
 789+ 'Invalid file extension found in the path info or query string.' );
786790
787791 return false;
788792 }
789 -
790 - if ( $this->isPathInfoBad() ) {
791 - wfHttpError( 403, 'Forbidden',
792 - 'Invalid file extension found in PATH_INFO.' );
793 - return false;
794 - }
795793 return true;
796794 }
797795
@@ -798,12 +796,7 @@
799797 * Attempt to redirect to a URL with a QUERY_STRING that's not dangerous in
800798 * IE 6. Returns true if it was successful, false otherwise.
801799 */
802 - protected function attemptExtensionSecurityRedirect() {
803 - $url = self::fixUrlForIE6( $this->getFullRequestURL() );
804 - if ( $url === false ) {
805 - return false;
806 - }
807 -
 800+ protected function doSecurityRedirect( $url ) {
808801 header( 'Location: ' . $url );
809802 header( 'Content-Type: text/html' );
810803 $encUrl = htmlspecialchars( $url );
@@ -844,162 +837,16 @@
845838 * Also checks for anything that looks like a file extension at the end of
846839 * QUERY_STRING, since IE 6 and earlier will use this to get the file type
847840 * if there was no dot before the question mark (bug 28235).
 841+ *
 842+ * @deprecated Use checkUrlExtension().
848843 */
849 - public function isPathInfoBad() {
 844+ public function isPathInfoBad( $extWhitelist = array() ) {
850845 global $wgScriptExtension;
851 -
852 - if ( $this->isQueryStringBad() ) {
853 - return true;
854 - }
855 -
856 - if ( !isset( $_SERVER['PATH_INFO'] ) ) {
857 - return false;
858 - }
859 - $pi = $_SERVER['PATH_INFO'];
860 - $dotPos = strrpos( $pi, '.' );
861 - if ( $dotPos === false ) {
862 - return false;
863 - }
864 - $ext = substr( $pi, $dotPos );
865 - return !in_array( $ext, array( $wgScriptExtension, '.php', '.php5' ) );
 846+ $extWhitelist[] = ltrim( $wgScriptExtension, '.' );
 847+ return IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist );
866848 }
867 -
868 - /**
869 - * Determine what extension IE6 will infer from a certain query string.
870 - * If the URL has an extension before the question mark, IE6 will use
871 - * that and ignore the query string, but per the comment at
872 - * isPathInfoBad() we don't have a reliable way to determine the URL,
873 - * so isPathInfoBad() just passes in the query string for $url.
874 - * All entry points have safe extensions (php, php5) anyway, so
875 - * checking the query string is possibly overly paranoid but never
876 - * insecure.
877 - *
878 - * The criteria for finding an extension are as follows:
879 - * - a possible extension is a dot followed by one or more characters not
880 - * in <>\"/:|?.#
881 - * - if we find a possible extension followed by the end of the string or
882 - * a #, that's our extension
883 - * - if we find a possible extension followed by a ?, that's our extension
884 - * - UNLESS it's exe, dll or cgi, in which case we ignore it and continue
885 - * searching for another possible extension
886 - * - if we find a possible extension followed by a dot or another illegal
887 - * character, we ignore it and continue searching
888 - *
889 - * @param $url string URL
890 - * @return mixed Detected extension (string), or false if none found
891 - */
892 - public static function findIE6Extension( $url ) {
893 - $pos = 0;
894 - $hashPos = strpos( $url, '#' );
895 - if ( $hashPos !== false ) {
896 - $urlLength = $hashPos;
897 - } else {
898 - $urlLength = strlen( $url );
899 - }
900 - $remainingLength = $urlLength;
901 - while ( $remainingLength > 0 ) {
902 - // Skip ahead to the next dot
903 - $pos += strcspn( $url, '.', $pos, $remainingLength );
904 - if ( $pos >= $urlLength ) {
905 - // End of string, we're done
906 - return false;
907 - }
908 -
909 - // We found a dot. Skip past it
910 - $pos++;
911 - $remainingLength = $urlLength - $pos;
912849
913 - // Check for illegal characters in our prospective extension,
914 - // or for another dot
915 - $nextPos = $pos + strcspn( $url, "<>\\\"/:|?*.", $pos, $remainingLength );
916 - if ( $nextPos >= $urlLength ) {
917 - // No illegal character or next dot
918 - // We have our extension
919 - return substr( $url, $pos, $urlLength - $pos );
920 - }
921 - if ( $url[$nextPos] === '?' ) {
922 - // We've found a legal extension followed by a question mark
923 - // If the extension is NOT exe, dll or cgi, return it
924 - $extension = substr( $url, $pos, $nextPos - $pos );
925 - if ( strcasecmp( $extension, 'exe' ) && strcasecmp( $extension, 'dll' ) &&
926 - strcasecmp( $extension, 'cgi' ) )
927 - {
928 - return $extension;
929 - }
930 - // Else continue looking
931 - }
932 - // We found an illegal character or another dot
933 - // Skip to that character and continue the loop
934 - $pos = $nextPos + 1;
935 - $remainingLength = $urlLength - $pos;
936 - }
937 - return false;
938 - }
939 -
940850 /**
941 - * Check for a bad query string, which IE 6 will use as a potentially
942 - * insecure cache file extension. See bug 28235. Returns true if the
943 - * request should be disallowed.
944 - *
945 - * @return Boolean
946 - */
947 - public function isQueryStringBad() {
948 - if ( !isset( $_SERVER['QUERY_STRING'] ) ) {
949 - return false;
950 - }
951 - return self::isUrlExtensionBad( $_SERVER['QUERY_STRING'] );
952 - }
953 -
954 - /**
955 - * The same as WebRequest::isQueryStringBad() except as a static function.
956 - */
957 - public static function isUrlExtensionBad( $query ) {
958 - if ( strval( $query ) === '' ) {
959 - return false;
960 - }
961 -
962 - $extension = self::findIE6Extension( $query );
963 - if ( strval( $extension ) === '' ) {
964 - /* No extension or empty extension (false/'') */
965 - return false;
966 - }
967 -
968 - /* Only consider the extension understood by IE to be potentially
969 - * dangerous if it is made of normal characters (so it is more
970 - * likely to be registered with an application)
971 - * Compromise with api.php convenience. Considers for instance
972 - * that no sane application will register a dangerous file type
973 - * in an extension containing an ampersand.
974 - */
975 - return (bool)preg_match( '/^[a-zA-Z0-9_-]+$/', $extension );
976 - }
977 -
978 - /**
979 - * Returns a variant of $url which will pass isUrlExtensionBad() but has the
980 - * same GET parameters, or false if it can't figure one out.
981 - */
982 - public static function fixUrlForIE6( $url ) {
983 - $questionPos = strpos( $url, '?' );
984 - if ( $questionPos === false || $questionPos === strlen( $url ) - 1 ) {
985 - return $url;
986 - }
987 -
988 - $beforeQuery = substr( $url, 0, $questionPos + 1 );
989 - $query = substr( $url, $questionPos + 1 );
990 - // Multiple question marks cause problems. Encode the second and
991 - // subsequent question mark.
992 - $query = str_replace( '?', '%3E', $query );
993 - // Append an invalid path character so that IE6 won't see the end of the
994 - // query string as an extension
995 - $query .= '&*';
996 - if ( self::isUrlExtensionBad( $query ) ) {
997 - // Avoid a redirect loop
998 - return false;
999 - }
1000 - return $beforeQuery . $query;
1001 - }
1002 -
1003 - /**
1004851 * Parse the Accept-Language header sent by the client into an array
1005852 * @return array( languageCode => q-value ) sorted by q-value in descending order
1006853 * May contain the "language" '*', which applies to languages other than those explicitly listed.
@@ -1235,7 +1082,11 @@
12361083 return $this->session;
12371084 }
12381085
1239 - public function isPathInfoBad() {
 1086+ public function isPathInfoBad( $extWhitelist = array() ) {
12401087 return false;
12411088 }
 1089+
 1090+ public function checkUrlExtension( $extWhitelist = array() ) {
 1091+ return true;
 1092+ }
12421093 }
Index: trunk/phase3/includes/AutoLoader.php
@@ -506,6 +506,7 @@
507507 'CSSJanus' => 'includes/libs/CSSJanus.php',
508508 'CSSMin' => 'includes/libs/CSSMin.php',
509509 'IEContentAnalyzer' => 'includes/libs/IEContentAnalyzer.php',
 510+ 'IEUrlExtension' => 'includes/libs/IEUrlExtension.php',
510511 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php',
511512
512513 # includes/media
Index: trunk/phase3/includes/libs/IEUrlExtension.php
@@ -0,0 +1,247 @@
 2+<?php
 3+
 4+/**
 5+ * Internet Explorer derives a cache filename from a URL, and then in certain
 6+ * circumstances, uses the extension of the resulting file to determine the
 7+ * content type of the data, ignoring the Content-Type header.
 8+ *
 9+ * This can be a problem, especially when non-HTML content is sent by MediaWiki,
 10+ * and Internet Explorer interprets it as HTML, exposing an XSS vulnerability.
 11+ *
 12+ * Usually the script filename (e.g. api.php) is present in the URL, and this
 13+ * makes Internet Explorer think the extension is a harmless script extension.
 14+ * But Internet Explorer 6 and earlier allows the script extension to be
 15+ * obscured by encoding the dot as "%2E".
 16+ *
 17+ * This class contains functions which help in detecting and dealing with this
 18+ * situation.
 19+ *
 20+ * Checking the URL for a bad extension is somewhat complicated due to the fact
 21+ * that CGI doesn't provide a standard method to determine the URL. Instead it
 22+ * is necessary to pass a subset of $_SERVER variables, which we then attempt
 23+ * to use to guess parts of the URL.
 24+ */
 25+class IEUrlExtension {
 26+ /**
 27+ * Check a subset of $_SERVER (or the whole of $_SERVER if you like)
 28+ * to see if it indicates that the request was sent with a bad file
 29+ * extension. Returns true if the request should be denied or modified,
 30+ * false otherwise. The relevant $_SERVER elements are:
 31+ *
 32+ * - SERVER_SOFTWARE
 33+ * - REQUEST_URI
 34+ * - QUERY_STRING
 35+ * - PATH_INFO
 36+ *
 37+ * If the a variable is unset in $_SERVER, it should be unset in $vars.
 38+ *
 39+ * @param $vars A subset of $_SERVER.
 40+ * @param $extWhitelist Extensions which are allowed, assumed harmless.
 41+ */
 42+ public static function areServerVarsBad( $vars, $extWhitelist = array() ) {
 43+ // Check QUERY_STRING or REQUEST_URI
 44+ if ( isset( $vars['SERVER_SOFTWARE'] )
 45+ && isset( $vars['REQUEST_URI'] )
 46+ && self::haveUndecodedRequestUri( $vars['SERVER_SOFTWARE'] ) )
 47+ {
 48+ $urlPart = $vars['REQUEST_URI'];
 49+ } elseif ( isset( $vars['QUERY_STRING'] ) ) {
 50+ $urlPart = $vars['QUERY_STRING'];
 51+ } else {
 52+ $urlPart = '';
 53+ }
 54+
 55+ if ( self::isUrlExtensionBad( $urlPart, $extWhitelist ) ) {
 56+ return true;
 57+ }
 58+
 59+ // Some servers have PATH_INFO but not REQUEST_URI, so we check both
 60+ // to be on the safe side.
 61+ if ( isset( $vars['PATH_INFO'] )
 62+ && self::isUrlExtensionBad( $vars['PATH_INFO'], $extWhitelist ) )
 63+ {
 64+ return true;
 65+ }
 66+
 67+ // All checks passed
 68+ return false;
 69+ }
 70+
 71+ /**
 72+ * Given a right-hand portion of a URL, determine whether IE would detect
 73+ * a potentially harmful file extension.
 74+ *
 75+ * @param $urlPart The right-hand portion of a URL
 76+ * @param $extWhitelist An array of file extensions which may occur in this
 77+ * URL, and which should be allowed.
 78+ * @return bool
 79+ */
 80+ public static function isUrlExtensionBad( $urlPart, $extWhitelist = array() ) {
 81+ if ( strval( $urlPart ) === '' ) {
 82+ return false;
 83+ }
 84+
 85+ $extension = self::findIE6Extension( $urlPart );
 86+ if ( strval( $extension ) === '' ) {
 87+ // No extension or empty extension
 88+ return false;
 89+ }
 90+
 91+ if ( in_array( $extension, array( 'php', 'php5' ) ) ) {
 92+ // Script extension, OK
 93+ return false;
 94+ }
 95+ if ( in_array( $extension, $extWhitelist ) ) {
 96+ // Whitelisted extension
 97+ return false;
 98+ }
 99+
 100+ if ( !preg_match( '/^[a-zA-Z0-9_-]+$/', $extension ) ) {
 101+ // Non-alphanumeric extension, unlikely to be registered.
 102+ //
 103+ // The regex above is known to match all registered file extensions
 104+ // in a default Windows XP installation. It's important to allow
 105+ // extensions with ampersands and percent signs, since that reduces
 106+ // the number of false positives substantially.
 107+ return false;
 108+ }
 109+
 110+ // Possibly bad extension
 111+ return true;
 112+ }
 113+
 114+ /**
 115+ * Returns a variant of $url which will pass isUrlExtensionBad() but has the
 116+ * same GET parameters, or false if it can't figure one out.
 117+ */
 118+ public static function fixUrlForIE6( $url, $extWhitelist = array() ) {
 119+ $questionPos = strpos( $url, '?' );
 120+ if ( $questionPos === false ) {
 121+ $beforeQuery = $url . '?';
 122+ $query = '';
 123+ } elseif ( $questionPos === strlen( $url ) - 1 ) {
 124+ $beforeQuery = $url;
 125+ $query = '';
 126+ } else {
 127+ $beforeQuery = substr( $url, 0, $questionPos + 1 );
 128+ $query = substr( $url, $questionPos + 1 );
 129+ }
 130+
 131+ // Multiple question marks cause problems. Encode the second and
 132+ // subsequent question mark.
 133+ $query = str_replace( '?', '%3E', $query );
 134+ // Append an invalid path character so that IE6 won't see the end of the
 135+ // query string as an extension
 136+ $query .= '&*';
 137+ // Put the URL back together
 138+ $url = $beforeQuery . $query;
 139+ if ( self::isUrlExtensionBad( $url, $extWhitelist ) ) {
 140+ // Avoid a redirect loop
 141+ return false;
 142+ }
 143+ return $url;
 144+ }
 145+
 146+ /**
 147+ * Determine what extension IE6 will infer from a certain query string.
 148+ * If the URL has an extension before the question mark, IE6 will use
 149+ * that and ignore the query string, but per the comment at
 150+ * isPathInfoBad() we don't have a reliable way to determine the URL,
 151+ * so isPathInfoBad() just passes in the query string for $url.
 152+ * All entry points have safe extensions (php, php5) anyway, so
 153+ * checking the query string is possibly overly paranoid but never
 154+ * insecure.
 155+ *
 156+ * The criteria for finding an extension are as follows:
 157+ * - a possible extension is a dot followed by one or more characters not
 158+ * in <>\"/:|?.#
 159+ * - if we find a possible extension followed by the end of the string or
 160+ * a #, that's our extension
 161+ * - if we find a possible extension followed by a ?, that's our extension
 162+ * - UNLESS it's exe, dll or cgi, in which case we ignore it and continue
 163+ * searching for another possible extension
 164+ * - if we find a possible extension followed by a dot or another illegal
 165+ * character, we ignore it and continue searching
 166+ *
 167+ * @param $url string URL
 168+ * @return mixed Detected extension (string), or false if none found
 169+ */
 170+ public static function findIE6Extension( $url ) {
 171+ $pos = 0;
 172+ $hashPos = strpos( $url, '#' );
 173+ if ( $hashPos !== false ) {
 174+ $urlLength = $hashPos;
 175+ } else {
 176+ $urlLength = strlen( $url );
 177+ }
 178+ $remainingLength = $urlLength;
 179+ while ( $remainingLength > 0 ) {
 180+ // Skip ahead to the next dot
 181+ $pos += strcspn( $url, '.', $pos, $remainingLength );
 182+ if ( $pos >= $urlLength ) {
 183+ // End of string, we're done
 184+ return false;
 185+ }
 186+
 187+ // We found a dot. Skip past it
 188+ $pos++;
 189+ $remainingLength = $urlLength - $pos;
 190+
 191+ // Check for illegal characters in our prospective extension,
 192+ // or for another dot
 193+ $nextPos = $pos + strcspn( $url, "<>\\\"/:|?*.", $pos, $remainingLength );
 194+ if ( $nextPos >= $urlLength ) {
 195+ // No illegal character or next dot
 196+ // We have our extension
 197+ return substr( $url, $pos, $urlLength - $pos );
 198+ }
 199+ if ( $url[$nextPos] === '?' ) {
 200+ // We've found a legal extension followed by a question mark
 201+ // If the extension is NOT exe, dll or cgi, return it
 202+ $extension = substr( $url, $pos, $nextPos - $pos );
 203+ if ( strcasecmp( $extension, 'exe' ) && strcasecmp( $extension, 'dll' ) &&
 204+ strcasecmp( $extension, 'cgi' ) )
 205+ {
 206+ return $extension;
 207+ }
 208+ // Else continue looking
 209+ }
 210+ // We found an illegal character or another dot
 211+ // Skip to that character and continue the loop
 212+ $pos = $nextPos + 1;
 213+ $remainingLength = $urlLength - $pos;
 214+ }
 215+ return false;
 216+ }
 217+
 218+ /**
 219+ * When passed the value of $_SERVER['SERVER_SOFTWARE'], this function
 220+ * returns true if that server is known to have a REQUEST_URI variable
 221+ * with %2E not decoded to ".". On such a server, it is possible to detect
 222+ * whether the script filename has been obscured.
 223+ *
 224+ * The function returns false if the server is not known to have this
 225+ * behaviour. Microsoft IIS in particular is known to decode escaped script
 226+ * filenames.
 227+ *
 228+ * SERVER_SOFTWARE typically contains either a plain string such as "Zeus",
 229+ * or a specification in the style of a User-Agent header, such as
 230+ * "Apache/1.3.34 (Unix) mod_ssl/2.8.25 OpenSSL/0.9.8a PHP/4.4.2"
 231+ *
 232+ * @param $serverSoftware
 233+ * @return bool
 234+ *
 235+ */
 236+ public static function haveUndecodedRequestUri( $serverSoftware ) {
 237+ static $whitelist = array(
 238+ 'Apache',
 239+ 'Zeus',
 240+ 'LiteSpeed' );
 241+ if ( preg_match( '/^(.*?)($|\/| )/', $serverSoftware, $m ) ) {
 242+ return in_array( $m[1], $whitelist );
 243+ } else {
 244+ return false;
 245+ }
 246+ }
 247+
 248+}
Property changes on: trunk/phase3/includes/libs/IEUrlExtension.php
___________________________________________________________________
Added: svn:eol-style
1249 + native
Index: trunk/phase3/img_auth.php
@@ -42,14 +42,20 @@
4343 wfForbidden('img-auth-accessdenied','img-auth-public');
4444 }
4545
 46+$matches = WebRequest::getPathInfo();
 47+$path = $matches['title'];
 48+
4649 // Check for bug 28235: QUERY_STRING overriding the correct extension
47 -if ( $wgRequest->isQueryStringBad() )
 50+$dotPos = strpos( $path, '.' );
 51+$whitelist = array();
 52+if ( $dotPos !== false ) {
 53+ $whitelist[] = substr( $path, $dotPos + 1 );
 54+}
 55+if ( !$wgRequest->checkUrlExtension( $whitelist ) )
4856 {
49 - wfForbidden( 'img-auth-accessdenied', 'img-auth-bad-query-string' );
50 -}
 57+ return;
 58+}
5159
52 -$matches = WebRequest::getPathInfo();
53 -$path = $matches['title'];
5460 $filename = realpath( $wgUploadDirectory . $path );
5561 $realUpload = realpath( $wgUploadDirectory );
5662

Follow-up revisions

RevisionCommit summaryAuthorDate
r89559Test filename updates for r89558.tstarling12:00, 6 June 2011
r89627MFT r89397, r89558, etc.: bug 28840 IE URL extensiontstarling05:57, 7 June 2011
r89628Merge r89627 from 1.18, equivalent to trunk r89558, r89397, etc.: bug 28840 I...tstarling07:00, 7 June 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r85844Fix for bug 28235: IE6 looks for the file extension in the query stringtstarling00:55, 12 April 2011
r85845MFT r85844, fix for bug 28235 (IE6 looks for the file extension in the query ...tstarling01:07, 12 April 2011
r85846MFT r85844, fix for bug 28235 (IE6 looks for the file extension in the query ...tstarling01:15, 12 April 2011
r86027(bug 28507) Fix for r85844: that revision was not actually sufficient to fix ...tstarling07:10, 14 April 2011
r89397(bug 28840) If the query string hits bug 28235, redirect to a safer URL inste...tstarling05:32, 3 June 2011

Comments

#Comment by Tim Starling (talk | contribs)   05:58, 9 June 2011

I'll tag r89628 for backport to 1.17wmf1 instead of this, since it will be easier.

Status & tagging log