summaryrefslogtreecommitdiff
path: root/modules/file/tests/file.test
diff options
context:
space:
mode:
Diffstat (limited to 'modules/file/tests/file.test')
-rw-r--r--modules/file/tests/file.test138
1 files changed, 138 insertions, 0 deletions
diff --git a/modules/file/tests/file.test b/modules/file/tests/file.test
index 80433954b..6d7cb4bc4 100644
--- a/modules/file/tests/file.test
+++ b/modules/file/tests/file.test
@@ -218,6 +218,30 @@ class FileFieldTestCase extends DrupalWebTestCase {
$message = isset($message) ? $message : format_string('File %file is permanent.', array('%file' => $file->uri));
$this->assertTrue($file->status == FILE_STATUS_PERMANENT, $message);
}
+
+ /**
+ * Creates a temporary file, for a specific user.
+ *
+ * @param string $data
+ * A string containing the contents of the file.
+ * @param int $uid
+ * The user ID of the file owner.
+ *
+ * @return object
+ * A file object, or FALSE on error.
+ */
+ function createTemporaryFile($data, $uid = NULL) {
+ $file = file_save_data($data, NULL, NULL);
+
+ if ($file) {
+ $file->uid = isset($uid) ? $uid : $this->admin_user->uid;
+ // Change the file status to be temporary.
+ $file->status = NULL;
+ return file_save($file);
+ }
+
+ return $file;
+ }
}
/**
@@ -527,6 +551,120 @@ class FileFieldWidgetTestCase extends FileFieldTestCase {
}
/**
+ * Tests exploiting the temporary file removal of another user using fid.
+ */
+ function testTemporaryFileRemovalExploit() {
+ // Create a victim user.
+ $victim_user = $this->drupalCreateUser();
+
+ // Create an attacker user.
+ $attacker_user = $this->drupalCreateUser(array(
+ 'access content',
+ 'create page content',
+ 'edit any page content',
+ ));
+
+ // Log in as the attacker user.
+ $this->drupalLogin($attacker_user);
+
+ // Perform tests using the newly created users.
+ $this->doTestTemporaryFileRemovalExploit($victim_user->uid, $attacker_user->uid);
+ }
+
+ /**
+ * Tests exploiting the temporary file removal for anonymous users using fid.
+ */
+ public function testTemporaryFileRemovalExploitAnonymous() {
+ // Set up an anonymous victim user.
+ $victim_uid = 0;
+
+ // Set up an anonymous attacker user.
+ $attacker_uid = 0;
+
+ // Set up permissions for anonymous attacker user.
+ user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array(
+ 'access content' => TRUE,
+ 'create page content' => TRUE,
+ 'edit any page content' => TRUE,
+ ));
+
+ // In order to simulate being the anonymous attacker user, we need to log
+ // out here since setUp() has logged in the admin.
+ $this->drupalLogout();
+
+ // Perform tests using the newly set up users.
+ $this->doTestTemporaryFileRemovalExploit($victim_uid, $attacker_uid);
+ }
+
+ /**
+ * Helper for testing exploiting the temporary file removal using fid.
+ *
+ * @param int $victim_uid
+ * The victim user ID.
+ * @param int $attacker_uid
+ * The attacker user ID.
+ */
+ protected function doTestTemporaryFileRemovalExploit($victim_uid, $attacker_uid) {
+ // Use 'page' instead of 'article', so that the 'article' image field does
+ // not conflict with this test. If in the future the 'page' type gets its
+ // own default file or image field, this test can be made more robust by
+ // using a custom node type.
+ $type_name = 'page';
+ $field_name = 'test_file_field';
+ $this->createFileField($field_name, $type_name);
+
+ $test_file = $this->getTestFile('text');
+ foreach (array('nojs', 'js') as $type) {
+ // Create a temporary file owned by the anonymous victim user. This will be
+ // as if they had uploaded the file, but not saved the node they were
+ // editing or creating.
+ $victim_tmp_file = $this->createTemporaryFile('some text', $victim_uid);
+ $victim_tmp_file = file_load($victim_tmp_file->fid);
+ $this->assertTrue($victim_tmp_file->status != FILE_STATUS_PERMANENT, 'New file saved to disk is temporary.');
+ $this->assertFalse(empty($victim_tmp_file->fid), 'New file has a fid');
+ $this->assertEqual($victim_uid, $victim_tmp_file->uid, 'New file belongs to the victim user');
+
+ // Have attacker create a new node with a different uploaded file and
+ // ensure it got uploaded successfully.
+ // @todo Can we test AJAX? See https://www.drupal.org/node/2538260
+ $edit = array(
+ 'title' => $type . '-title',
+ );
+
+ // Attach a file to a node.
+ $langcode = LANGUAGE_NONE;
+ $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($test_file->uri);
+ $this->drupalPost("node/add/$type_name", $edit, 'Save');
+ $node = $this->drupalGetNodeByTitle($edit['title']);
+ $node_file = file_load($node->{$field_name}[$langcode][0]['fid']);
+ $this->assertFileExists($node_file, 'New file saved to disk on node creation.');
+ $this->assertEqual($attacker_uid, $node_file->uid, 'New file belongs to the attacker.');
+
+ // Ensure the file can be downloaded.
+ $this->drupalGet(file_create_url($node_file->uri));
+ $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
+
+ // "Click" the remove button (emulating either a nojs or js submission).
+ // In this POST request, the attacker "guesses" the fid of the victim's
+ // temporary file and uses that to remove this file.
+ $this->drupalGet('node/' . $node->nid . '/edit');
+ switch ($type) {
+ case 'nojs':
+ $this->drupalPost(NULL, array("{$field_name}[$langcode][0][fid]" => (string) $victim_tmp_file->fid), 'Remove');
+ break;
+ case 'js':
+ $button = $this->xpath('//input[@type="submit" and @value="Remove"]');
+ $this->drupalPostAJAX(NULL, array("{$field_name}[$langcode][0][fid]" => (string) $victim_tmp_file->fid), array((string) $button[0]['name'] => (string) $button[0]['value']));
+ break;
+ }
+
+ // The victim's temporary file should not be removed by the attacker's
+ // POST request.
+ $this->assertFileExists($victim_tmp_file);
+ }
+ }
+
+ /**
* Tests upload and remove buttons for multiple multi-valued File fields.
*/
function testMultiValuedWidget() {