Skip to content
This repository was archived by the owner on Sep 10, 2021. It is now read-only.

Commit cec93d1

Browse files
committed
BUG: refs #981. Fix security vulnerability in web API upload method
1 parent f321118 commit cec93d1

File tree

4 files changed

+60
-31
lines changed

4 files changed

+60
-31
lines changed

core/AppController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public function preDispatch()
118118
}
119119
else
120120
{
121-
$auth = $this->User->legacyAuthenticate($userDao, '', '', $tmp[1]);
121+
$auth = $userModel->legacyAuthenticate($userDao, '', '', $tmp[1]);
122122
}
123123
// if authenticated, set the session user to be this user
124124
if($auth)

modules/api/controllers/components/ApiComponent.php

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -344,40 +344,45 @@ function uploadGeneratetoken($args)
344344
$existingBitstream = $bitstreamModel->getByChecksum($args['checksum']);
345345
if($existingBitstream)
346346
{
347-
$revision = $itemModel->getLastRevision($item);
348-
349-
if($revision == false)
347+
// User must have read access to the existing bitstream if they are circumventing the upload.
348+
// Otherwise an attacker could spoof the checksum and read a private bitstream with a known checksum.
349+
if($itemModel->policyCheck($existingBitstream->getItemrevision()->getItem(), $userDao, MIDAS_POLICY_READ))
350350
{
351-
// Create new revision if none exists yet
352-
Zend_Loader::loadClass('ItemRevisionDao', BASE_PATH.'/core/models/dao');
353-
$revision = new ItemRevisionDao();
354-
$revision->setChanges('Initial revision');
355-
$revision->setUser_id($userDao->getKey());
356-
$revision->setDate(date('c'));
357-
$revision->setLicenseId(null);
358-
$itemModel->addRevision($item, $revision);
359-
}
351+
$revision = $itemModel->getLastRevision($item);
360352

361-
$siblings = $revision->getBitstreams();
362-
foreach($siblings as $sibling)
363-
{
364-
if($sibling->getName() == $args['filename'])
353+
if($revision == false)
354+
{
355+
// Create new revision if none exists yet
356+
Zend_Loader::loadClass('ItemRevisionDao', BASE_PATH.'/core/models/dao');
357+
$revision = new ItemRevisionDao();
358+
$revision->setChanges('Initial revision');
359+
$revision->setUser_id($userDao->getKey());
360+
$revision->setDate(date('c'));
361+
$revision->setLicenseId(null);
362+
$itemModel->addRevision($item, $revision);
363+
}
364+
365+
$siblings = $revision->getBitstreams();
366+
foreach($siblings as $sibling)
365367
{
366-
// already have a file with this name. don't add new record.
367-
return array('token' => '');
368+
if($sibling->getName() == $args['filename'])
369+
{
370+
// already have a file with this name. don't add new record.
371+
return array('token' => '');
372+
}
368373
}
374+
Zend_Loader::loadClass('BitstreamDao', BASE_PATH.'/core/models/dao');
375+
$bitstream = new BitstreamDao();
376+
$bitstream->setChecksum($args['checksum']);
377+
$bitstream->setName($args['filename']);
378+
$bitstream->setSizebytes($existingBitstream->getSizebytes());
379+
$bitstream->setPath($existingBitstream->getPath());
380+
$bitstream->setAssetstoreId($existingBitstream->getAssetstoreId());
381+
$bitstream->setMimetype($existingBitstream->getMimetype());
382+
$revisionModel = MidasLoader::loadModel('ItemRevision');
383+
$revisionModel->addBitstream($revision, $bitstream);
384+
return array('token' => '');
369385
}
370-
Zend_Loader::loadClass('BitstreamDao', BASE_PATH.'/core/models/dao');
371-
$bitstream = new BitstreamDao();
372-
$bitstream->setChecksum($args['checksum']);
373-
$bitstream->setName($args['filename']);
374-
$bitstream->setSizebytes($existingBitstream->getSizebytes());
375-
$bitstream->setPath($existingBitstream->getPath());
376-
$bitstream->setAssetstoreId($existingBitstream->getAssetstoreId());
377-
$bitstream->setMimetype($existingBitstream->getMimetype());
378-
$revisionModel = MidasLoader::loadModel('ItemRevision');
379-
$revisionModel->addBitstream($revision, $bitstream);
380-
return array('token' => '');
381386
}
382387
}
383388
//we don't already have this content, so create the token

modules/api/tests/controllers/ApiCallItemMethodsTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ public function testBitstreamUpload()
226226
*
227227
*22 generatetoken passing in folderid and a checksum of an existing bitstream,
228228
* and passing Public explicitly
229+
*25 generatetoken passing in folderid and checksum of an existing bitstream, but the user
230+
* has no read access to the already existing item, so exception is thrown
229231
*
230232
*/
231233

@@ -415,6 +417,28 @@ public function testBitstreamUpload()
415417
// delete the newly created item
416418
$this->Item->delete($itemDao);
417419

420+
// 25
421+
// Just like #22, but we want to make sure the clone existing bitstream behavior
422+
// doesn't happen when the user doesn't have read access to the item with the given checksum.
423+
// Otherwise for an attacker, having the checksum of a bitstream would allow them to exploit
424+
// upload.generatetoken to gain read access to that bitstream, even if they didn't have
425+
// read access on it.
426+
$existingItem = $this->Item->load(1001); // this should be the item with the existing bitstream in it
427+
$policy = $this->Itempolicyuser->getPolicy($usersFile[0], $existingItem);
428+
$this->Itempolicyuser->delete($policy); // delete permission for the user on the item
429+
$this->resetAll();
430+
$this->params['token'] = $this->_loginAsNormalUser();
431+
$this->params['method'] = 'midas.upload.generatetoken';
432+
$this->params['filename'] = 'some_new_file.txt';
433+
$this->params['checksum'] = $md5;
434+
$this->params['folderid'] = '1000';
435+
$this->params['itemprivacy'] = 'Public';
436+
$resp = $this->_callJsonApi();
437+
$this->_assertStatusOk($resp);
438+
$token = $resp->data->token;
439+
$this->assertNotEquals($token, '', 'User needs read access to existing bitstream to clone it');
440+
$this->Itempolicyuser->createPolicy($usersFile[0], $existingItem, $policy->getPolicy()); //restore policy state
441+
418442
// 7
419443
// generate upload token
420444
// when we generate an upload token for a newly created item with a

modules/api/tests/controllers/ApiCallMethodsTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function setUp()
2727
$this->setupDatabase(array('default')); //core dataset
2828
$this->setupDatabase(array('default'), 'api'); // module dataset
2929
$this->enabledModules = array('api');
30-
$this->_models = array('User', 'Folder', 'Item', 'ItemRevision', 'Assetstore', 'Bitstream');
30+
$this->_models = array('User', 'Folder', 'Item', 'ItemRevision', 'Assetstore', 'Bitstream', 'Itempolicyuser');
3131
$this->_daos = array();
3232

3333
parent::setUp();

0 commit comments

Comments
 (0)