diff options
Diffstat (limited to 'includes')
-rw-r--r-- | includes/DefaultSettings.php | 2 | ||||
-rw-r--r-- | includes/EditPage.php | 21 | ||||
-rw-r--r-- | includes/User.php | 22 | ||||
-rw-r--r-- | includes/Wiki.php | 13 | ||||
-rw-r--r-- | includes/actions/InfoAction.php | 1 | ||||
-rw-r--r-- | includes/api/ApiFormatBase.php | 18 | ||||
-rw-r--r-- | includes/db/DatabaseMysqli.php | 13 | ||||
-rw-r--r-- | includes/db/DatabaseOracle.php | 32 | ||||
-rw-r--r-- | includes/db/DatabasePostgres.php | 16 | ||||
-rw-r--r-- | includes/installer/WebInstallerPage.php | 9 | ||||
-rw-r--r-- | includes/specials/SpecialChangePassword.php | 16 | ||||
-rw-r--r-- | includes/specials/SpecialPrefixindex.php | 1 | ||||
-rw-r--r-- | includes/templates/Userlogin.php | 13 | ||||
-rw-r--r-- | includes/upload/UploadBase.php | 71 |
14 files changed, 224 insertions, 24 deletions
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index e9b4f490..32ad2db3 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -63,7 +63,7 @@ $wgConf = new SiteConfiguration; * MediaWiki version number * @since 1.2 */ -$wgVersion = '1.22.2'; +$wgVersion = '1.22.6'; /** * Name of the site. It must be changed in LocalSettings.php diff --git a/includes/EditPage.php b/includes/EditPage.php index 530e2674..16d9a5a4 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -2049,10 +2049,27 @@ class EditPage { } # Try to add a custom edit intro, or use the standard one if this is not possible. if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) { + $helpLink = wfExpandUrl( Skin::makeInternalOrExternalUrl( + wfMessage( 'helppage' )->inContentLanguage()->text() + ) ); if ( $wgUser->isLoggedIn() ) { - $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletext\">\n$1\n</div>", 'newarticletext' ); + $wgOut->wrapWikiMsg( + // Suppress the external link icon, consider the help url an internal one + "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>", + array( + 'newarticletext', + $helpLink + ) + ); } else { - $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletextanon\">\n$1\n</div>", 'newarticletextanon' ); + $wgOut->wrapWikiMsg( + // Suppress the external link icon, consider the help url an internal one + "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>", + array( + 'newarticletextanon', + $helpLink + ) + ); } } # Give a notice if the user is editing a deleted/moved page... diff --git a/includes/User.php b/includes/User.php index 12912e1c..62324043 100644 --- a/includes/User.php +++ b/includes/User.php @@ -984,7 +984,8 @@ class User { # Get the token from DB/cache and clean it up to remove garbage padding. # This deals with historical problems with bugs and the default column value. $token = rtrim( $proposedUser->getToken( false ) ); // correct token - $passwordCorrect = ( strlen( $token ) && $token === $request->getCookie( 'Token' ) ); + // Make comparison in constant time (bug 61346) + $passwordCorrect = strlen( $token ) && $this->compareSecrets( $token, $request->getCookie( 'Token' ) ); $from = 'cookie'; } else { // No session or persistent login cookie @@ -1004,6 +1005,25 @@ class User { } /** + * A comparison of two strings, not vulnerable to timing attacks + * @param string $answer the secret string that you are comparing against. + * @param string $test compare this string to the $answer. + * @return bool True if the strings are the same, false otherwise + */ + protected function compareSecrets( $answer, $test ) { + if ( strlen( $answer ) !== strlen( $test ) ) { + $passwordCorrect = false; + } else { + $result = 0; + for ( $i = 0; $i < strlen( $answer ); $i++ ) { + $result |= ord( $answer{$i} ) ^ ord( $test{$i} ); + } + $passwordCorrect = ( $result == 0 ); + } + return $passwordCorrect; + } + + /** * Load user and user_group data from the database. * $this->mId must be set, this is how the user is identified. * diff --git a/includes/Wiki.php b/includes/Wiki.php index ae75bf33..074ec1ab 100644 --- a/includes/Wiki.php +++ b/includes/Wiki.php @@ -653,12 +653,19 @@ class MediaWiki { } if ( !wfShellExecDisabled() && is_executable( $wgPhpCli ) ) { - // Start a background process to run some of the jobs. - // This will be asynchronous on *nix though not on Windows. + // Start a background process to run some of the jobs wfProfileIn( __METHOD__ . '-exec' ); $retVal = 1; $cmd = wfShellWikiCmd( "$IP/maintenance/runJobs.php", array( '--maxjobs', $n ) ); - wfShellExec( "$cmd &", $retVal ); + $cmd .= " >" . wfGetNull() . " 2>&1"; // don't hang PHP on pipes + if ( wfIsWindows() ) { + // Using START makes this async and also works around a bug where using + // wfShellExec() with a quoted script name causes a filename syntax error. + $cmd = "START /B \"bg\" $cmd"; + } else { + $cmd = "$cmd &"; + } + wfShellExec( $cmd, $retVal ); wfProfileOut( __METHOD__ . '-exec' ); } else { try { diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php index 7fc90339..0a16f4a8 100644 --- a/includes/actions/InfoAction.php +++ b/includes/actions/InfoAction.php @@ -261,6 +261,7 @@ class InfoAction extends FormlessAction { $sortKey = $pageProperties['defaultsort']; } + $sortKey = htmlspecialchars( $sortKey ); $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-default-sort' ), $sortKey ); // Page length (in bytes) diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index b89fb3a7..70495439 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -272,17 +272,33 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>, // encode all comments or tags as safe blue strings $text = str_replace( '<', '<span style="color:blue;"><', $text ); $text = str_replace( '>', '></span>', $text ); + // identify requests to api.php - $text = preg_replace( "#api\\.php\\?[^ <\n\t]+#", '<a href="\\0">\\0</a>', $text ); + $text = preg_replace( '#^(\s*)(api\.php\?[^ <\n\t]+)$#m', '\1<a href="\2">\2</a>', $text ); if ( $this->mHelp ) { // make strings inside * bold $text = preg_replace( "#\\*[^<>\n]+\\*#", '<b>\\0</b>', $text ); } + + // Armor links (bug 61362) + $masked = array(); + $text = preg_replace_callback( '#<a .*?</a>#', function ( $matches ) use ( &$masked ) { + $sha = sha1( $matches[0] ); + $masked[$sha] = $matches[0]; + return "<$sha>"; + }, $text ); + // identify URLs $protos = wfUrlProtocolsWithoutProtRel(); // This regex hacks around bug 13218 (" included in the URL) $text = preg_replace( "#(((?i)$protos).*?)(")?([ \\'\"<>\n]|<|>|")#", '<a href="\\1">\\1</a>\\3\\4', $text ); + // Unarmor links + $text = preg_replace_callback( '#<([0-9a-f]{40})>#', function ( $matches ) use ( &$masked ) { + $sha = $matches[1]; + return isset( $masked[$sha] ) ? $masked[$sha] : $matches[0]; + }, $text ); + /** * Temporary fix for bad links in help messages. As a special case, * XML-escaped metachars are de-escaped one level in the help message diff --git a/includes/db/DatabaseMysqli.php b/includes/db/DatabaseMysqli.php index 7761abe9..0ec54314 100644 --- a/includes/db/DatabaseMysqli.php +++ b/includes/db/DatabaseMysqli.php @@ -51,6 +51,17 @@ class DatabaseMysqli extends DatabaseMysqlBase { . " have you compiled PHP with the --with-mysqli option?\n" ); } + // Other than mysql_connect, mysqli_real_connect expects an explicit port + // parameter. So we need to parse the port out of $realServer + $port = null; + $hostAndPort = IP::splitHostAndPort( $realServer ); + if ( $hostAndPort ) { + $realServer = $hostAndPort[0]; + if ( $hostAndPort[1] ) { + $port = $hostAndPort[1]; + } + } + $connFlags = 0; if ( $this->mFlags & DBO_SSL ) { $connFlags |= MYSQLI_CLIENT_SSL; @@ -70,7 +81,7 @@ class DatabaseMysqli extends DatabaseMysqlBase { usleep( 1000 ); } if ( $mysqli->real_connect( $realServer, $this->mUser, - $this->mPassword, $this->mDBname, null, null, $connFlags ) ) + $this->mPassword, $this->mDBname, $port, null, $connFlags ) ) { return $mysqli; } diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php index 32d4d984..fb2d4359 100644 --- a/includes/db/DatabaseOracle.php +++ b/includes/db/DatabaseOracle.php @@ -551,8 +551,12 @@ class DatabaseOracle extends DatabaseBase { } else { $first = false; } - - $sql .= $this->fieldBindStatement( $table, $col, $val ); + if ( $this->isQuotedIdentifier( $val ) ) { + $sql .= $this->removeIdentifierQuotes( $val ); + unset( $row[$col] ); + } else { + $sql .= $this->fieldBindStatement( $table, $col, $val ); + } } $sql .= ')'; @@ -679,6 +683,30 @@ class DatabaseOracle extends DatabaseBase { return $retval; } + public function upsert( $table, array $rows, array $uniqueIndexes, array $set, + $fname = __METHOD__ + ) { + if ( !count( $rows ) ) { + return true; // nothing to do + } + + if ( !is_array( reset( $rows ) ) ) { + $rows = array( $rows ); + } + + $sequenceData = $this->getSequenceData( $table ); + if ( $sequenceData !== false ) { + // add sequence column to each list of columns, when not set + foreach ( $rows as &$row ) { + if ( !isset( $row[$sequenceData['column']] ) ) { + $row[$sequenceData['column']] = $this->addIdentifierQuotes('GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')'); + } + } + } + + return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname ); + } + function tableName( $name, $format = 'quoted' ) { /* Replace reserved words with better ones diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php index aed35f10..0bd966ba 100644 --- a/includes/db/DatabasePostgres.php +++ b/includes/db/DatabasePostgres.php @@ -729,13 +729,15 @@ __INDEXATTR__; * so causes a DB error. This wrapper checks which tables can be locked and adjusts it accordingly. */ function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, $options = array(), $join_conds = array() ) { - $forUpdateKey = array_search( 'FOR UPDATE', $options ); - if ( $forUpdateKey !== false && $join_conds ) { - unset( $options[$forUpdateKey] ); - - foreach ( $join_conds as $table => $join_cond ) { - if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) { - $options['FOR UPDATE'][] = $table; + if ( is_array( $options ) ) { + $forUpdateKey = array_search( 'FOR UPDATE', $options ); + if ( $forUpdateKey !== false && $join_conds ) { + unset( $options[$forUpdateKey] ); + + foreach ( $join_conds as $table_cond => $join_cond ) { + if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) { + $options['FOR UPDATE'][] = $table_cond; + } } } } diff --git a/includes/installer/WebInstallerPage.php b/includes/installer/WebInstallerPage.php index ad399133..d3b550fe 100644 --- a/includes/installer/WebInstallerPage.php +++ b/includes/installer/WebInstallerPage.php @@ -955,11 +955,16 @@ class WebInstaller_Options extends WebInstallerPage { LinkCache::singleton()->useDatabase( false ); foreach ( $extensions as $ext ) { + if ( isset( $ext['descriptionmsg'] ) ) { + $desc = wfMessage( $ext['descriptionmsg'] )->useDatabase( false )->parse(); + } else { + $desc = ''; + } $extHtml .= $this->parent->getCheckBox( array( 'var' => "ext-{$ext['name']}", 'rawtext' => "<b>{$ext['name']}</b>: " . - wfMessage( $ext['descriptionmsg'] )->useDatabase( false )->parse(), - ) ); + $desc, + ) ); } diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php index c54b5575..a75e7e83 100644 --- a/includes/specials/SpecialChangePassword.php +++ b/includes/specials/SpecialChangePassword.php @@ -52,6 +52,11 @@ class SpecialChangePassword extends UnlistedSpecialPage { $this->mDomain = $request->getVal( 'wpDomain' ); $user = $this->getUser(); + + if ( !$user->isLoggedIn() && !LoginForm::getLoginToken() ) { + LoginForm::setLoginToken(); + } + if ( !$request->wasPosted() && !$user->isLoggedIn() ) { $this->error( $this->msg( 'resetpass-no-info' )->text() ); @@ -81,6 +86,14 @@ class SpecialChangePassword extends UnlistedSpecialPage { return; } + if ( !$user->isLoggedIn() + && $request->getVal( 'wpLoginOnChangeToken' ) !== LoginForm::getLoginToken() + ) { + // Potential CSRF (bug 62497) + $this->error( $this->msg( 'sessionfailure' )->text() ); + return false; + } + $this->attemptReset( $this->mNewpass, $this->mRetype ); if ( $user->isLoggedIn() ) { @@ -157,6 +170,9 @@ class SpecialChangePassword extends UnlistedSpecialPage { 'wpName' => $this->mUserName, 'wpDomain' => $this->mDomain, ) + $this->getRequest()->getValues( 'returnto', 'returntoquery' ); + if ( !$user->isLoggedIn() ) { + $hiddenFields['wpLoginOnChangeToken'] = LoginForm::getLoginToken(); + } $hiddenFieldsStr = ''; foreach ( $hiddenFields as $fieldname => $fieldvalue ) { $hiddenFieldsStr .= Html::hidden( $fieldname, $fieldvalue ) . "\n"; diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php index 28d07ffc..0d065b09 100644 --- a/includes/specials/SpecialPrefixindex.php +++ b/includes/specials/SpecialPrefixindex.php @@ -264,6 +264,7 @@ class SpecialPrefixindex extends SpecialAllpages { 'from' => $s->page_title, 'prefix' => $prefix, 'hideredirects' => $this->hideRedirects, + 'stripprefix' => $this->stripPrefix, ); if ( $namespace || $prefix == '' ) { diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php index 5eb60948..9aedd3c7 100644 --- a/includes/templates/Userlogin.php +++ b/includes/templates/Userlogin.php @@ -154,9 +154,18 @@ class UserloginTemplate extends BaseTemplate { ) ); ?> </div> - <div id="mw-userlogin-help"> - <?php echo $this->getMsg( 'userlogin-helplink' )->parse(); ?> + <?php + echo Html::element( + 'a', + array( + 'href' => Skin::makeInternalOrExternalUrl( + wfMessage( 'helplogin-url' )->inContentLanguage()->text() + ), + ), + $this->getMsg( 'userlogin-helplink2' )->text() + ); + ?> </div> <?php if ( $this->haveData( 'createOrLoginHref' ) ) { ?> <?php if ( $this->data['loggedin'] ) { ?> diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index 916ad6c1..c0c37b3f 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -44,7 +44,7 @@ abstract class UploadBase { protected $mFilteredName, $mFinalExtension; protected $mLocalFile, $mFileSize, $mFileProps; protected $mBlackListedExtensions; - protected $mJavaDetected; + protected $mJavaDetected, $mSVGNSError; protected static $safeXmlEncodings = array( 'UTF-8', 'ISO-8859-1', 'ISO-8859-2', 'UTF-16', 'UTF-32' ); @@ -1162,6 +1162,7 @@ abstract class UploadBase { * @return bool */ protected function detectScriptInSvg( $filename ) { + $this->mSVGNSError = false; $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ), @@ -1172,6 +1173,9 @@ abstract class UploadBase { // Invalid xml (bug 58553) return array( 'uploadinvalidxml' ); } elseif ( $check->filterMatch ) { + if ( $this->mSVGNSError ) { + return array( 'uploadscriptednamespace', $this->mSVGNSError ); + } return array( 'uploadscripted' ); } return false; @@ -1198,7 +1202,51 @@ abstract class UploadBase { * @return bool */ public function checkSvgScriptCallback( $element, $attribs ) { - $strippedElement = $this->stripXmlNamespace( $element ); + list( $namespace, $strippedElement ) = $this->splitXmlNamespace( $element ); + + static $validNamespaces = array( + '', + 'adobe:ns:meta/', + 'http://creativecommons.org/ns#', + 'http://inkscape.sourceforge.net/dtd/sodipodi-0.dtd', + 'http://ns.adobe.com/adobeillustrator/10.0/', + 'http://ns.adobe.com/adobesvgviewerextensions/3.0/', + 'http://ns.adobe.com/extensibility/1.0/', + 'http://ns.adobe.com/flows/1.0/', + 'http://ns.adobe.com/illustrator/1.0/', + 'http://ns.adobe.com/imagereplacement/1.0/', + 'http://ns.adobe.com/pdf/1.3/', + 'http://ns.adobe.com/photoshop/1.0/', + 'http://ns.adobe.com/saveforweb/1.0/', + 'http://ns.adobe.com/variables/1.0/', + 'http://ns.adobe.com/xap/1.0/', + 'http://ns.adobe.com/xap/1.0/g/', + 'http://ns.adobe.com/xap/1.0/g/img/', + 'http://ns.adobe.com/xap/1.0/mm/', + 'http://ns.adobe.com/xap/1.0/rights/', + 'http://ns.adobe.com/xap/1.0/stype/dimensions#', + 'http://ns.adobe.com/xap/1.0/stype/font#', + 'http://ns.adobe.com/xap/1.0/stype/manifestitem#', + 'http://ns.adobe.com/xap/1.0/stype/resourceevent#', + 'http://ns.adobe.com/xap/1.0/stype/resourceref#', + 'http://ns.adobe.com/xap/1.0/t/pg/', + 'http://purl.org/dc/elements/1.1/', + 'http://purl.org/dc/elements/1.1', + 'http://schemas.microsoft.com/visio/2003/svgextensions/', + 'http://sodipodi.sourceforge.net/dtd/sodipodi-0.dtd', + 'http://web.resource.org/cc/', + 'http://www.freesoftware.fsf.org/bkchem/cdml', + 'http://www.inkscape.org/namespaces/inkscape', + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'http://www.w3.org/2000/svg', + ); + + if ( !in_array( $namespace, $validNamespaces ) ) { + wfDebug( __METHOD__ . ": Non-svg namespace '$namespace' in uploaded file.\n" ); + // @TODO return a status object to a closure in XmlTypeCheck, for MW1.21+ + $this->mSVGNSError = $namespace; + return true; + } /* * check for elements that can contain javascript @@ -1220,6 +1268,12 @@ abstract class UploadBase { return true; } + # Block iframes, in case they pass the namespace check + if ( $strippedElement == 'iframe' ) { + wfDebug( __METHOD__ . ": iframe in uploaded file.\n" ); + return true; + } + foreach ( $attribs as $attrib => $value ) { $stripped = $this->stripXmlNamespace( $attrib ); $value = strtolower( $value ); @@ -1294,6 +1348,19 @@ abstract class UploadBase { } /** + * Divide the element name passed by the xml parser to the callback into URI and prifix. + * @param $name string + * @return array containing the namespace URI and prefix + */ + private static function splitXmlNamespace( $element ) { + // 'http://www.w3.org/2000/svg:script' -> array( 'http://www.w3.org/2000/svg', 'script' ) + $parts = explode( ':', strtolower( $element ) ); + $name = array_pop( $parts ); + $ns = implode( ':', $parts ); + return array( $ns, $name ); + } + + /** * @param $name string * @return string */ |