%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/includes/libs/ |
Current File : /www/varak.net/wiki.varak.net/includes/libs/IEUrlExtension.php |
<?php /** * Checks for validity of requested URL's extension. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * * @file */ /** * Internet Explorer derives a cache filename from a URL, and then in certain * circumstances, uses the extension of the resulting file to determine the * content type of the data, ignoring the Content-Type header. * * This can be a problem, especially when non-HTML content is sent by MediaWiki, * and Internet Explorer interprets it as HTML, exposing an XSS vulnerability. * * Usually the script filename (e.g. api.php) is present in the URL, and this * makes Internet Explorer think the extension is a harmless script extension. * But Internet Explorer 6 and earlier allows the script extension to be * obscured by encoding the dot as "%2E". * * This class contains functions which help in detecting and dealing with this * situation. * * Checking the URL for a bad extension is somewhat complicated due to the fact * that CGI doesn't provide a standard method to determine the URL. Instead it * is necessary to pass a subset of $_SERVER variables, which we then attempt * to use to guess parts of the URL. */ class IEUrlExtension { /** * Check a subset of $_SERVER (or the whole of $_SERVER if you like) * to see if it indicates that the request was sent with a bad file * extension. Returns true if the request should be denied or modified, * false otherwise. The relevant $_SERVER elements are: * * - SERVER_SOFTWARE * - REQUEST_URI * - QUERY_STRING * - PATH_INFO * * If the a variable is unset in $_SERVER, it should be unset in $vars. * * @param array $vars A subset of $_SERVER. * @param array $extWhitelist Extensions which are allowed, assumed harmless. * @return bool */ public static function areServerVarsBad( $vars, $extWhitelist = [] ) { // Check QUERY_STRING or REQUEST_URI if ( isset( $vars['SERVER_SOFTWARE'] ) && isset( $vars['REQUEST_URI'] ) && self::haveUndecodedRequestUri( $vars['SERVER_SOFTWARE'] ) ) { $urlPart = $vars['REQUEST_URI']; } elseif ( isset( $vars['QUERY_STRING'] ) ) { $urlPart = $vars['QUERY_STRING']; } else { $urlPart = ''; } if ( self::isUrlExtensionBad( $urlPart, $extWhitelist ) ) { return true; } // Some servers have PATH_INFO but not REQUEST_URI, so we check both // to be on the safe side. if ( isset( $vars['PATH_INFO'] ) && self::isUrlExtensionBad( $vars['PATH_INFO'], $extWhitelist ) ) { return true; } // All checks passed return false; } /** * Given a right-hand portion of a URL, determine whether IE would detect * a potentially harmful file extension. * * @param string $urlPart The right-hand portion of a URL * @param array $extWhitelist An array of file extensions which may occur in this * URL, and which should be allowed. * @return bool */ public static function isUrlExtensionBad( $urlPart, $extWhitelist = [] ) { if ( strval( $urlPart ) === '' ) { return false; } $extension = self::findIE6Extension( $urlPart ); if ( strval( $extension ) === '' ) { // No extension or empty extension return false; } if ( in_array( $extension, [ 'php', 'php5' ] ) ) { // Script extension, OK return false; } if ( in_array( $extension, $extWhitelist ) ) { // Whitelisted extension return false; } if ( !preg_match( '/^[a-zA-Z0-9_-]+$/', $extension ) ) { // Non-alphanumeric extension, unlikely to be registered. // The regex above is known to match all registered file extensions // in a default Windows XP installation. It's important to allow // extensions with ampersands and percent signs, since that reduces // the number of false positives substantially. return false; } // Possibly bad extension return true; } /** * Returns a variant of $url which will pass isUrlExtensionBad() but has the * same GET parameters, or false if it can't figure one out. * @param string $url * @param array $extWhitelist * @return bool|string */ public static function fixUrlForIE6( $url, $extWhitelist = [] ) { $questionPos = strpos( $url, '?' ); if ( $questionPos === false ) { $beforeQuery = $url . '?'; $query = ''; } elseif ( $questionPos === strlen( $url ) - 1 ) { $beforeQuery = $url; $query = ''; } else { $beforeQuery = substr( $url, 0, $questionPos + 1 ); $query = substr( $url, $questionPos + 1 ); } // Multiple question marks cause problems. Encode the second and // subsequent question mark. $query = str_replace( '?', '%3E', $query ); // Append an invalid path character so that IE6 won't see the end of the // query string as an extension $query .= '&*'; // Put the URL back together $url = $beforeQuery . $query; if ( self::isUrlExtensionBad( $url, $extWhitelist ) ) { // Avoid a redirect loop return false; } return $url; } /** * Determine what extension IE6 will infer from a certain query string. * If the URL has an extension before the question mark, IE6 will use * that and ignore the query string, but per the comment at * isPathInfoBad() we don't have a reliable way to determine the URL, * so isPathInfoBad() just passes in the query string for $url. * All entry points have safe extensions (php, php5) anyway, so * checking the query string is possibly overly paranoid but never * insecure. * * The criteria for finding an extension are as follows: * - a possible extension is a dot followed by one or more characters not * in <>\"/:|?.# * - if we find a possible extension followed by the end of the string or * a #, that's our extension * - if we find a possible extension followed by a ?, that's our extension * - UNLESS it's exe, dll or cgi, in which case we ignore it and continue * searching for another possible extension * - if we find a possible extension followed by a dot or another illegal * character, we ignore it and continue searching * * @param string $url * @return mixed Detected extension (string), or false if none found */ public static function findIE6Extension( $url ) { $pos = 0; $hashPos = strpos( $url, '#' ); if ( $hashPos !== false ) { $urlLength = $hashPos; } else { $urlLength = strlen( $url ); } $remainingLength = $urlLength; while ( $remainingLength > 0 ) { // Skip ahead to the next dot $pos += strcspn( $url, '.', $pos, $remainingLength ); if ( $pos >= $urlLength ) { // End of string, we're done return false; } // We found a dot. Skip past it $pos++; $remainingLength = $urlLength - $pos; // Check for illegal characters in our prospective extension, // or for another dot $nextPos = $pos + strcspn( $url, "<>\\\"/:|?*.", $pos, $remainingLength ); if ( $nextPos >= $urlLength ) { // No illegal character or next dot // We have our extension return substr( $url, $pos, $urlLength - $pos ); } if ( $url[$nextPos] === '?' ) { // We've found a legal extension followed by a question mark // If the extension is NOT exe, dll or cgi, return it $extension = substr( $url, $pos, $nextPos - $pos ); if ( strcasecmp( $extension, 'exe' ) && strcasecmp( $extension, 'dll' ) && strcasecmp( $extension, 'cgi' ) ) { return $extension; } // Else continue looking } // We found an illegal character or another dot // Skip to that character and continue the loop $pos = $nextPos; $remainingLength = $urlLength - $pos; } return false; } /** * When passed the value of $_SERVER['SERVER_SOFTWARE'], this function * returns true if that server is known to have a REQUEST_URI variable * with %2E not decoded to ".". On such a server, it is possible to detect * whether the script filename has been obscured. * * The function returns false if the server is not known to have this * behavior. Microsoft IIS in particular is known to decode escaped script * filenames. * * SERVER_SOFTWARE typically contains either a plain string such as "Zeus", * or a specification in the style of a User-Agent header, such as * "Apache/1.3.34 (Unix) mod_ssl/2.8.25 OpenSSL/0.9.8a PHP/4.4.2" * * @param string $serverSoftware * @return bool */ public static function haveUndecodedRequestUri( $serverSoftware ) { static $whitelist = [ 'Apache', 'Zeus', 'LiteSpeed' ]; if ( preg_match( '/^(.*?)($|\/| )/', $serverSoftware, $m ) ) { return in_array( $m[1], $whitelist ); } else { return false; } } }