diff options
author | Reedy <reedy@wikimedia.org> | 2012-03-22 21:26:08 +0000 |
---|---|---|
committer | Reedy <reedy@wikimedia.org> | 2012-03-22 21:26:08 +0000 |
commit | beb90dc07727046aee493fb6bcf0445cb23fe98d (patch) | |
tree | 634a4f8b7157617d4eca099026d695586aa8e12e | |
parent | 96c81c1b5be52b903aa1cd9c95e51c864c2db920 (diff) |
* (bug 34212) ApiBlock/ApiUnblock allow action to take place without a token parameter present1.17.3
* (bug 35317) CSRF in Special:Upload
Revert r56793, which removed the CSRF check for Special:Upload for normal file
uploads. Cross-site posting of file uploads without user interaction has been
possible since at least as early as Chrome 8 (late 2010) and Firefox 6 (mid
2011).
Commonist has used api.php since version 0.4.0 (April 2010), and the API
already requires an edit token, so Commonist 0.4.0+ is not affected by this
change.
* (bug 34907) Fix for CSRF vulnerability due to mw.user.tokens. Patch by Roan
Kattouw and Tim Starling.
* Filter out private modules early in ResourceLoader::makeResponse() and just
pretend they weren't specified. This means these modules cannot be loaded
through load.php . This filtering must not happen in makeModuleResponse(),
because that would break inlining.
* Force inlining of private modules in OutputPage::makeResourceLoaderLink(),
disregarding $wgResourceLoaderInlinePrivateModules
* Remove $wgResourceLoaderInlinePrivateModules
* Remove special treatment of private modules ($private) in
ResourceLoader::makeResponse() and sendResponseHeaders(), because we're not
allowing private modules to be loaded through here any more
* Remove identity checks in ResourceLoaderUserOptionsModule and
ResourceLoaderUserCSSPrefsModule, they didn't make a lot of sense before but
they're certainly useless now.
* Factored out error comment construction in ResourceLoader.php and stripped
comment terminations from exception messages. I didn't find an XSS
vulnerability but it looked scary.
Change-Id: I4dc1bc9a3522f5fb0de1d64afb36579f53bdd696
-rw-r--r-- | RELEASE-NOTES | 9 | ||||
-rw-r--r-- | includes/DefaultSettings.php | 9 | ||||
-rw-r--r-- | includes/OutputPage.php | 10 | ||||
-rw-r--r-- | includes/api/ApiMain.php | 2 | ||||
-rw-r--r-- | includes/resourceloader/ResourceLoader.php | 42 | ||||
-rw-r--r-- | includes/resourceloader/ResourceLoaderUserOptionsModule.php | 31 | ||||
-rw-r--r-- | includes/specials/SpecialUpload.php | 9 |
7 files changed, 43 insertions, 69 deletions
diff --git a/RELEASE-NOTES b/RELEASE-NOTES index e17998e8423c..c5781a440463 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -3,8 +3,8 @@ Security reminder: MediaWiki does not require PHP's register_globals setting since version 1.2.0. If you have it on, turn it '''off''' if you can. -== MediaWiki 1.17.2 == -2012-01-11 +== MediaWiki 1.17.3 == +2012-03-21 This a maintenance and security release of the MediaWiki 1.17 branch. @@ -39,6 +39,11 @@ Selected changes since MediaWiki 1.16 that may be of interest: * (bug 22555) Remove or skip strip markers from tag hooks like <nowiki> in core parser functions which operate on strings, such as padleft. +* (bug 34212) ApiBlock/ApiUnblock allow action to take place without a token + parameter present. +* (bug 34907) Fixed exposure of tokens through load.php that could have facilitated + CSRF attacks. +* (bug 35317) CSRF in Special:Upload. === Changes since 1.17.1 === * (bug 33117) prop=revisions allows deleted text to be exposed through cache pollution. diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 883f1b466ba4..92fcc16b6ea1 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -34,7 +34,7 @@ if ( !defined( 'MW_PHP4' ) ) { /** @endcond */ /** MediaWiki version number */ -$wgVersion = '1.17.2'; +$wgVersion = '1.17.3'; /** Name of the site. It must be changed in LocalSettings.php */ $wgSitename = 'MediaWiki'; @@ -2414,13 +2414,6 @@ $wgResourceLoaderMaxage = array( ); /** - * Whether to embed private modules inline with HTML output or to bypass - * caching and check the user parameter against $wgUser to prevent - * unauthorized access to private modules. - */ -$wgResourceLoaderInlinePrivateModules = true; - -/** * The default debug mode (on/off) for of ResourceLoader requests. This will still * be overridden when the debug URL parameter is used. */ diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 3a0be7bc3fbe..207af21de459 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2340,8 +2340,7 @@ class OutputPage { * @return string html <script> and <style> tags */ protected function makeResourceLoaderLink( Skin $skin, $modules, $only, $useESI = false ) { - global $wgUser, $wgLang, $wgLoadScript, $wgResourceLoaderUseESI, - $wgResourceLoaderInlinePrivateModules, $wgRequest; + global $wgUser, $wgLang, $wgLoadScript, $wgResourceLoaderUseESI, $wgRequest; // Lazy-load ResourceLoader // TODO: Should this be a static function of ResourceLoader instead? $baseQuery = array( @@ -2414,8 +2413,11 @@ class OutputPage { $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $modules ) ); - // Support inlining of private modules if configured as such - if ( $group === 'private' && $wgResourceLoaderInlinePrivateModules ) { + // Inline private modules. These can't be loaded through load.php for security + // reasons, see bug 34907. Note that these modules should be loaded from + // getHeadScripts() before the first loader call. Otherwise other modules can't + // properly use them as dependencies (bug 30914) + if ( $group === 'private' ) { if ( $only == 'styles' ) { $links .= Html::inlineStyle( $resourceLoader->makeModuleResponse( $context, $modules ) diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 9a71affad590..8a8b23e453b4 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -548,7 +548,7 @@ class ApiMain extends ApiBase { // Die if token required, but not provided (unless there is a gettoken parameter) $salt = $module->getTokenSalt(); - if ( $salt !== false && !isset( $moduleParams['gettoken'] ) ) { + if ( $salt !== false && !$moduleParams['gettoken'] ) { if ( !isset( $moduleParams['token'] ) ) { $this->dieUsageMsg( array( 'missingparam', 'token' ) ); } else { diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php index 191bc9f047ac..b6354f0f234e 100644 --- a/includes/resourceloader/ResourceLoader.php +++ b/includes/resourceloader/ResourceLoader.php @@ -161,7 +161,7 @@ class ResourceLoader { $cache->set( $key, $result ); } catch ( Exception $exception ) { // Return exception as a comment - $result = "/*\n{$exception->__toString()}\n*/\n"; + $result = $this->makeComment( $exception->__toString() ); } wfProfileOut( __METHOD__ ); @@ -306,13 +306,20 @@ class ResourceLoader { ob_start(); wfProfileIn( __METHOD__ ); - $exceptions = ''; + $errors = ''; // Split requested modules into two groups, modules and missing $modules = array(); $missing = array(); foreach ( $context->getModules() as $name ) { if ( isset( $this->moduleInfos[$name] ) ) { + $module = $this->getModule( $name ); + // Do not allow private modules to be loaded from the web. + // This is a security issue, see bug 34907. + if ( $module->getGroup() === 'private' ) { + $errors .= $this->makeComment( "Cannot show private module \"$name\"" ); + continue; + } $modules[$name] = $this->getModule( $name ); } else { $missing[] = $name; @@ -337,26 +344,21 @@ class ResourceLoader { $this->preloadModuleInfo( array_keys( $modules ), $context ); } catch( Exception $e ) { // Add exception to the output as a comment - $exceptions .= "/*\n{$e->__toString()}\n*/\n"; + $errors .= $this->makeComment( $e->__toString() ); } wfProfileIn( __METHOD__.'-getModifiedTime' ); - $private = false; // To send Last-Modified and support If-Modified-Since, we need to detect // the last modified time $mtime = wfTimestamp( TS_UNIX, $wgCacheEpoch ); foreach ( $modules as $module ) { try { - // Bypass Squid and other shared caches if the request includes any private modules - if ( $module->getGroup() === 'private' ) { - $private = true; - } // Calculate maximum modified time $mtime = max( $mtime, $module->getModifiedTime( $context ) ); } catch ( Exception $e ) { // Add exception to the output as a comment - $exceptions .= "/*\n{$e->__toString()}\n*/\n"; + $errors .= $this->makeComment( $e->__toString() ); } } @@ -373,13 +375,8 @@ class ResourceLoader { header( 'Cache-Control: private, no-cache, must-revalidate' ); header( 'Pragma: no-cache' ); } else { - if ( $private ) { - header( "Cache-Control: private, max-age=$maxage" ); - $exp = $maxage; - } else { - header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" ); - $exp = min( $maxage, $smaxage ); - } + header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" ); + $exp = min( $maxage, $smaxage ); header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) ); } @@ -418,12 +415,12 @@ class ResourceLoader { $response = $this->makeModuleResponse( $context, $modules, $missing ); // Prepend comments indicating exceptions - $response = $exceptions . $response; + $response = $errors . $response; // Capture any PHP warnings from the output buffer and append them to the // response in a comment if we're in debug mode. if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) { - $response = "/*\n$warnings\n*/\n" . $response; + $response = $this->makeComment( $warnings ) . $response; } // Remove the output buffer and output the response @@ -433,6 +430,11 @@ class ResourceLoader { wfProfileOut( __METHOD__ ); } + protected function makeComment( $text ) { + $encText = str_replace( '*/', '* /', $text ); + return "/*\n$encText\n*/\n"; + } + /** * Generates code for a response * @@ -457,7 +459,7 @@ class ResourceLoader { $blobs = MessageBlobStore::get( $this, $modules, $context->getLanguage() ); } catch ( Exception $e ) { // Add exception to the output as a comment - $exceptions .= "/*\n{$e->__toString()}\n*/\n"; + $exceptions .= $this->makeComment( $e->__toString() ); } } else { $blobs = array(); @@ -509,7 +511,7 @@ class ResourceLoader { } } catch ( Exception $e ) { // Add exception to the output as a comment - $exceptions .= "/*\n{$e->__toString()}\n*/\n"; + $exceptions .= $this->makeComment( $e->__toString() ); // Register module as missing $missing[] = $name; diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php index 61c769400d0c..fb33cd1f0c2f 100644 --- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php +++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php @@ -39,41 +39,20 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule { global $wgUser; - if ( $context->getUser() === $wgUser->getName() ) { - return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() ); - } else { - return 1; - } - } - - /** - * Fetch the context's user options, or if it doesn't match current user, - * the default options. - * - * @param $context ResourceLoaderContext: Context object - * @return Array: List of user options keyed by option name - */ - protected function contextUserOptions( ResourceLoaderContext $context ) { - global $wgUser; - - // Verify identity -- this is a private module - if ( $context->getUser() === $wgUser->getName() ) { - return $wgUser->getOptions(); - } else { - return User::getDefaultOptions(); - } + return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() ); } public function getScript( ResourceLoaderContext $context ) { + global $wgUser; return Xml::encodeJsCall( 'mediaWiki.user.options.set', - array( $this->contextUserOptions( $context ) ) ); + array( $wgUser->getOptions() ) ); } public function getStyles( ResourceLoaderContext $context ) { - global $wgAllowUserCssPrefs; + global $wgAllowUserCssPrefs, $wgUser; if ( $wgAllowUserCssPrefs ) { - $options = $this->contextUserOptions( $context ); + $options = $wgUser->getOptions(); // Build CSS rules $rules = array(); diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 893e4be2834f..96696ab3d229 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -109,14 +109,7 @@ class SpecialUpload extends SpecialPage { // If it was posted check for the token (no remote POST'ing with user credentials) $token = $request->getVal( 'wpEditToken' ); - if( $this->mSourceType == 'file' && $token == null ) { - // Skip token check for file uploads as that can't be faked via JS... - // Some client-side tools don't expect to need to send wpEditToken - // with their submissions, as that's new in 1.16. - $this->mTokenOk = true; - } else { - $this->mTokenOk = $wgUser->matchEditToken( $token ); - } + $this->mTokenOk = $wgUser->matchEditToken( $token ); $this->uploadFormTextTop = ''; $this->uploadFormTextAfterSummary = ''; |