MarkAHershberger has uploaded a new change for review.
https://gerrit.wikimedia.org/r/64857
Change subject: Chunked uploads allow arbitrary data to be dropped on the server
......................................................................
Chunked uploads allow arbitrary data to be dropped on the server
Bug: 48306
Change-Id: Icd8e799aaa2cca06495c690187283dd2913a17e3
---
M RELEASE-NOTES-1.21
M includes/AutoLoader.php
M includes/api/ApiUpload.php
M includes/upload/UploadBase.php
M includes/upload/UploadFromChunks.php
M includes/upload/UploadFromStash.php
M includes/upload/UploadStash.php
7 files changed, 112 insertions(+), 33 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core
refs/changes/57/64857/1
diff --git a/RELEASE-NOTES-1.21 b/RELEASE-NOTES-1.21
index ef82ac4..d1fb8cb 100644
--- a/RELEASE-NOTES-1.21
+++ b/RELEASE-NOTES-1.21
@@ -124,6 +124,7 @@
quotmarks, order and whitespace in the attribute list).
=== Bug fixes in 1.21 ===
+* (bug 48306) Chunked uploads allow arbitrary data to be dropped on the server
* (bug 47271) $wgContentHandlerUseDB should be set to false during the upgrade
* (bug 46084) Sanitize $limitReport before outputting.
* (bug 46859) Disable external entities in XMLReader.
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 4248178..3555e2c 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -997,6 +997,7 @@
'UnwatchedpagesPage' => 'includes/specials/SpecialUnwatchedpages.php',
'UploadChunkFileException' => 'includes/upload/UploadFromChunks.php',
'UploadChunkZeroLengthFileException' =>
'includes/upload/UploadFromChunks.php',
+ 'UploadChunkVerificationException' =>
'includes/upload/UploadFromChunks.php',
'UploadForm' => 'includes/specials/SpecialUpload.php',
'UploadSourceField' => 'includes/specials/SpecialUpload.php',
'UserrightsPage' => 'includes/specials/SpecialUserrights.php',
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index 2dcf392..98833ab 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -195,7 +195,12 @@
$chunkPath = $request->getFileTempname( 'chunk' );
$chunkSize = $request->getUpload( 'chunk' )->getSize();
if ( $this->mParams['offset'] == 0 ) {
- $filekey = $this->performStash();
+ try {
+ $filekey = $this->performStash();
+ } catch ( MWException $e ) {
+ // FIXME: Error handling here is
wrong/different from rest of this
+ $this->dieUsage( $e->getMessage(),
'stashfailed' );
+ }
} else {
$filekey = $this->mParams['filekey'];
/** @var $status Status */
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index 1b4d411..5a82362 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -355,6 +355,8 @@
/**
* Verify the mime type
*
+ * @note Only checks that it is not an evil mime. The does it have
+ * correct extension given its mime type check is in verifyFile.
* @param string $mime representing the mime
* @return mixed true if the file is verified, an array otherwise
*/
@@ -367,12 +369,6 @@
if ( $this->checkFileExtension( $mime,
$wgMimeTypeBlacklist ) ) {
wfProfileOut( __METHOD__ );
return array( 'filetype-badmime', $mime );
- }
-
- # XXX: Missing extension will be caught by
validateName() via getTitle()
- if ( $this->mFinalExtension != '' &&
!$this->verifyExtension( $mime, $this->mFinalExtension ) ) {
- wfProfileOut( __METHOD__ );
- return array( 'filetype-mime-mismatch',
$this->mFinalExtension, $mime );
}
# Check IE type
@@ -401,6 +397,56 @@
* @return mixed true of the file is verified, array otherwise.
*/
protected function verifyFile() {
+ global $wgVerifyMimeType;
+ wfProfileIn( __METHOD__ );
+
+ $status = $this->verifyPartialFile();
+ if ( $status !== true ) {
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ if ( $wgVerifyMimeType ) {
+ $this->mFileProps = FSFile::getPropsFromPath(
$this->mTempPath, $this->mFinalExtension );
+ $mime = $this->mFileProps['file-mime'];
+
+ # XXX: Missing extension will be caught by
validateName() via getTitle()
+ if ( $this->mFinalExtension != '' &&
!$this->verifyExtension( $mime, $this->mFinalExtension ) ) {
+ wfProfileOut( __METHOD__ );
+ return array( 'filetype-mime-mismatch',
$this->mFinalExtension, $mime );
+ }
+ }
+
+ $handler = MediaHandler::getHandler( $mime );
+ if ( $handler ) {
+ $handlerStatus = $handler->verifyUpload(
$this->mTempPath );
+ if ( !$handlerStatus->isOK() ) {
+ $errors = $handlerStatus->getErrorsArray();
+ wfProfileOut( __METHOD__ );
+ return reset( $errors );
+ }
+ }
+
+ wfRunHooks( 'UploadVerifyFile', array( $this, $mime, &$status )
);
+ if ( $status !== true ) {
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ wfDebug( __METHOD__ . ": all clear; passing.\n" );
+ wfProfileOut( __METHOD__ );
+ return true;
+ }
+
+ /**
+ * A verification routine suitable for partial files
+ *
+ * Runs the blacklist checks, but not any checks that may
+ * assume the entire file is present.
+ *
+ * @return Mixed true for valid or array with error message key.
+ */
+ protected function verifyPartialFile() {
global $wgAllowJavaUploads, $wgDisableUploadScriptChecks;
wfProfileIn( __METHOD__ );
@@ -459,23 +505,6 @@
return array( 'uploadvirus', $virus );
}
- $handler = MediaHandler::getHandler( $mime );
- if ( $handler ) {
- $handlerStatus = $handler->verifyUpload(
$this->mTempPath );
- if ( !$handlerStatus->isOK() ) {
- $errors = $handlerStatus->getErrorsArray();
- wfProfileOut( __METHOD__ );
- return reset( $errors );
- }
- }
-
- wfRunHooks( 'UploadVerifyFile', array( $this, $mime, &$status )
);
- if ( $status !== true ) {
- wfProfileOut( __METHOD__ );
- return $status;
- }
-
- wfDebug( __METHOD__ . ": all clear; passing.\n" );
wfProfileOut( __METHOD__ );
return true;
}
diff --git a/includes/upload/UploadFromChunks.php
b/includes/upload/UploadFromChunks.php
index e784e51..4b331e9 100644
--- a/includes/upload/UploadFromChunks.php
+++ b/includes/upload/UploadFromChunks.php
@@ -70,6 +70,8 @@
// Stash file is the called on creating a new chunk session:
$this->mChunkIndex = 0;
$this->mOffset = 0;
+
+ $this->verifyChunk();
// Create a local stash target
$this->mLocalFile = parent::stashFile();
// Update the initial file offset ( based on file size )
@@ -131,9 +133,18 @@
return $status;
}
wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount
seconds.\n" );
+
+ $this->mTempPath = $tmpPath; // file system path
+ $this->mFileSize = filesize( $this->mTempPath ); //Since this
was set for the last chunk previously
+ $ret = $this->verifyUpload();
+ if ( $ret['status'] !== UploadBase::OK ) {
+ wfDebugLog( 'fileconcatenate', "Verification failed for
chunked upload" );
+ $status->fatal( $this->getVerificationErrorCode(
$ret['status'] ) );
+ return $status;
+ }
+
// Update the mTempPath and mLocalFile
// ( for FileUpload or normal Stash to take over )
- $this->mTempPath = $tmpPath; // file system path
$tStart = microtime( true );
$this->mLocalFile = parent::stashFile( $this->user );
$tAmount = microtime( true ) - $tStart;
@@ -189,6 +200,15 @@
if ( $preAppendOffset == $offset ) {
// Update local chunk index for the current
chunk
$this->mChunkIndex++;
+ try {
+ # For some reason mTempPath is set to
first part
+ $oldTemp = $this->mTempPath;
+ $this->mTempPath = $chunkPath;
+ $this->verifyChunk();
+ $this->mTempPath = $oldTemp;
+ } catch ( UploadChunkVerificationException $e )
{
+ return Status::newFatal(
$e->getMessage() );
+ }
$status = $this->outputChunk( $chunkPath );
if( $status->isGood() ) {
// Update local offset:
@@ -312,7 +332,26 @@
}
return $this->mFileKey . '.' . $index;
}
+
+ /**
+ * Verify that the chunk isn't really an evil html file
+ *
+ * @throws UploadChunkVerificationException
+ */
+ private function verifyChunk() {
+ // Rest mDesiredDestName here so we verify the name as if it
were mFileKey
+ $oldDesiredDestName = $this->mDesiredDestName;
+ $this->mDesiredDestName = $this->mFileKey;
+ $this->mTitle = false;
+ $res = $this->verifyPartialFile();
+ $this->mDesiredDestName = $oldDesiredDestName;
+ $this->mTitle = false;
+ if( is_array( $res ) ) {
+ throw new UploadChunkVerificationException( $res[0] );
+ }
+ }
}
class UploadChunkZeroLengthFileException extends MWException {};
class UploadChunkFileException extends MWException {};
+class UploadChunkVerificationException extends MWException {};
diff --git a/includes/upload/UploadFromStash.php
b/includes/upload/UploadFromStash.php
index fd2416d..c82103c 100644
--- a/includes/upload/UploadFromStash.php
+++ b/includes/upload/UploadFromStash.php
@@ -137,14 +137,9 @@
return $this->mFileProps['sha1'];
}
- /**
- * File has been previously verified so no need to do so again.
- *
- * @return bool
+ /*
+ * protected function verifyFile() inherited
*/
- protected function verifyFile() {
- return true;
- }
/**
* Stash the file.
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
index cfa3879..089bd8b 100644
--- a/includes/upload/UploadStash.php
+++ b/includes/upload/UploadStash.php
@@ -422,6 +422,7 @@
* @return string
*/
public static function getExtensionForPath( $path ) {
+ global $wgFileBlacklist;
// Does this have an extension?
$n = strrpos( $path, '.' );
$extension = null;
@@ -441,7 +442,15 @@
throw new UploadStashFileException( "extension is null"
);
}
- return File::normalizeExtension( $extension );
+ $extension = File::normalizeExtension( $extension );
+ if ( in_array( $extension, $wgFileBlacklist ) ) {
+ // The file should already be checked for being evil.
+ // However, if somehow we got here, we definitely
+ // don't want to give it an extension of .php and
+ // put it in a web accesible directory.
+ return '';
+ }
+ return $extension;
}
/**
--
To view, visit https://gerrit.wikimedia.org/r/64857
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Icd8e799aaa2cca06495c690187283dd2913a17e3
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: REL1_21
Gerrit-Owner: MarkAHershberger <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits