diff options
author | Reedy <reedy@wikimedia.org> | 2012-03-22 22:14:10 +0000 |
---|---|---|
committer | Reedy <reedy@wikimedia.org> | 2012-03-22 22:14:10 +0000 |
commit | 34cd6b23ff2115232dd66ef9b8404c30767a570d (patch) | |
tree | b6d7a294398244a363a90b5598800ebba03009a8 | |
parent | 39db773837c064c558bf489f64247b18d3949a89 (diff) | |
download | mediawikicore-34cd6b23ff2115232dd66ef9b8404c30767a570d.tar.gz mediawikicore-34cd6b23ff2115232dd66ef9b8404c30767a570d.zip |
* (bug 34212) ApiBlock/ApiUnblock allow action to take place without a token parameter present1.19.0beta2
* (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: I0a4d7d2cc19ab3af018604037be150bda5187434
-rw-r--r-- | RELEASE-NOTES-1.19 | 7 | ||||
-rw-r--r-- | includes/DefaultSettings.php | 9 | ||||
-rw-r--r-- | includes/OutputPage.php | 11 | ||||
-rw-r--r-- | includes/api/ApiMain.php | 2 | ||||
-rw-r--r-- | includes/resourceloader/ResourceLoader.php | 51 | ||||
-rw-r--r-- | includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php | 29 | ||||
-rw-r--r-- | includes/resourceloader/ResourceLoaderUserOptionsModule.php | 30 | ||||
-rw-r--r-- | includes/specials/SpecialUpload.php | 9 |
8 files changed, 49 insertions, 99 deletions
diff --git a/RELEASE-NOTES-1.19 b/RELEASE-NOTES-1.19 index 6c869d443c5e..1ec2d4087f3d 100644 --- a/RELEASE-NOTES-1.19 +++ b/RELEASE-NOTES-1.19 @@ -29,6 +29,12 @@ production. core parser functions which operate on strings, such as padleft. * (bug 18295) Don't expose strip markers when a tag appears inside a link inside a heading. +* (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. + === Configuration changes in 1.19 === * Removed SkinTemplateSetupPageCss hook; use BeforePageDisplay instead. @@ -43,6 +49,7 @@ production. * (bug 32239) Removed $wgEnableTooltipsAndAccesskeys. * Removed $wgVectorShowVariantName. * Removed $wgExtensionAliasesFiles. Use $wgExtensionMessagesFiles. +* Removed $wgResourceLoaderInlinePrivateModules , now always enabled. === New features in 1.19 === * (bug 19838) Add ability to get all interwiki prefixes also if the interwiki diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index d75496b24a37..93d2b81cfb15 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -33,7 +33,7 @@ $wgConf = new SiteConfiguration; /** @endcond */ /** MediaWiki version number */ -$wgVersion = '1.19.0beta1'; +$wgVersion = '1.19.0beta2'; /** Name of the site. It must be changed in LocalSettings.php */ $wgSitename = 'MediaWiki'; @@ -2580,13 +2580,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 003aee7cf437..a91d54657a64 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2505,7 +2505,7 @@ $templates * @return string html <script> and <style> tags */ protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) { - global $wgResourceLoaderUseESI, $wgResourceLoaderInlinePrivateModules; + global $wgResourceLoaderUseESI; if ( !count( $modules ) ) { return ''; @@ -2584,10 +2584,11 @@ $templates continue; } - // Support inlining of private modules if configured as such. 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' && $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 == ResourceLoaderModule::TYPE_STYLES ) { $links .= Html::inlineStyle( $resourceLoader->makeModuleResponse( $context, $modules ) diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 5fedf8df0561..761f0fe3a665 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -595,7 +595,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 55fbddc401b2..9175b10de27b 100644 --- a/includes/resourceloader/ResourceLoader.php +++ b/includes/resourceloader/ResourceLoader.php @@ -173,7 +173,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__ ); @@ -430,13 +430,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; @@ -448,12 +455,11 @@ 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 ); @@ -462,22 +468,18 @@ class ResourceLoader { * @var $module ResourceLoaderModule */ 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() ); } } wfProfileOut( __METHOD__.'-getModifiedTime' ); // Send content type and cache related headers - $this->sendResponseHeaders( $context, $mtime, $private ); + $this->sendResponseHeaders( $context, $mtime ); // If there's an If-Modified-Since header, respond with a 304 appropriately if ( $this->tryRespondLastModified( $context, $mtime ) ) { @@ -489,20 +491,20 @@ 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 ob_end_clean(); echo $response; - // Save response to file cache unless there are private modules or errors - if ( isset( $fileCache ) && !$private && !$exceptions && !$missing ) { + // Save response to file cache unless there are errors + if ( isset( $fileCache ) && !$errors && !$missing ) { // Cache single modules...and other requests if there are enough hits if ( ResourceFileCache::useFileCache( $context ) ) { if ( $fileCache->isCacheWorthy() ) { @@ -520,10 +522,9 @@ class ResourceLoader { * Send content type and last modified headers to the client. * @param $context ResourceLoaderContext * @param $mtime string TS_MW timestamp to use for last-modified - * @param $private bool True iff response contains any private modules * @return void */ - protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $private ) { + protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime ) { global $wgResourceLoaderMaxage; // If a version wasn't specified we need a shorter expiry time for updates // to propagate to clients quickly @@ -547,13 +548,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() ) ); } } @@ -650,6 +646,11 @@ class ResourceLoader { return false; // cache miss } + protected function makeComment( $text ) { + $encText = str_replace( '*/', '* /', $text ); + return "/*\n$encText\n*/\n"; + } + /** * Generates code for a response * @@ -674,7 +675,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(); @@ -753,7 +754,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/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php index b5a24354e019..02693d3ec9b3 100644 --- a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php +++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php @@ -44,30 +44,7 @@ class ResourceLoaderUserCSSPrefsModule 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() ); } /** @@ -75,10 +52,10 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule { * @return array */ 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/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php index 84932adddf8c..7b1622053563 100644 --- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php +++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php @@ -42,32 +42,9 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule { if ( isset( $this->modifiedTime[$hash] ) ) { return $this->modifiedTime[$hash]; } - + 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() ); } /** @@ -75,8 +52,9 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule { * @return string */ public function getScript( ResourceLoaderContext $context ) { + global $wgUser; return Xml::encodeJsCall( 'mw.user.options.set', - array( $this->contextUserOptions( $context ) ) ); + array( $wgUser->getOptions() ) ); } /** diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 3711a6ac15db..d6a76d02bd4b 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -111,14 +111,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 = $this->getUser()->matchEditToken( $token ); - } + $this->mTokenOk = $this->getUser()->matchEditToken( $token ); $this->uploadFormTextTop = ''; $this->uploadFormTextAfterSummary = ''; |