You are here

public function FileUploadTest::testFileUploadMaliciousExtension in JSON:API 8.2

Tests using the file upload POST route with malicious extensions.

File

tests/src/Functional/FileUploadTest.php, line 577

Class

FileUploadTest
Tests binary data file upload route.

Namespace

Drupal\Tests\jsonapi\Functional

Code

public function testFileUploadMaliciousExtension() {

  // Allow all file uploads but system.file::allow_insecure_uploads is set to
  // FALSE.
  $this->field
    ->setSetting('file_extensions', '')
    ->save();
  $this
    ->rebuildAll();
  $this
    ->setUpAuthorization('POST');
  $this
    ->config('jsonapi.settings')
    ->set('read_only', FALSE)
    ->save(TRUE);
  $uri = Url::fromUri('base:' . static::$postUri);
  $php_string = '<?php print "Drupal"; ?>';

  // Test using a masked exploit file.
  $response = $this
    ->fileRequest($uri, $php_string, [
    'Content-Disposition' => 'filename="example.php"',
  ]);

  // The filename is not munged because .txt is added and it is a known
  // extension to apache.
  $expected = $this
    ->getExpectedDocument(1, 'example.php.txt', TRUE);

  // Override the expected filesize.
  $expected['data']['attributes']['filesize'] = strlen($php_string);
  $this
    ->assertResponseData($expected, $response);
  $this
    ->assertTrue(file_exists('public://foobar/example.php.txt'));

  // Add php as an allowed format. Allow insecure uploads still being FALSE
  // should still not allow this. So it should still have a .txt extension
  // appended even though it is not in the list of allowed extensions.
  $this->field
    ->setSetting('file_extensions', 'php')
    ->save();
  $this
    ->rebuildAll();
  $response = $this
    ->fileRequest($uri, $php_string, [
    'Content-Disposition' => 'filename="example_2.php"',
  ]);
  $expected = $this
    ->getExpectedDocument(2, 'example_2.php.txt', TRUE);

  // Override the expected filesize.
  $expected['data']['attributes']['filesize'] = strlen($php_string);
  $this
    ->assertResponseData($expected, $response);
  $this
    ->assertTrue(file_exists('public://foobar/example_2.php.txt'));
  $this
    ->assertFalse(file_exists('public://foobar/example_2.php'));

  // Allow .doc file uploads and ensure even a mis-configured apache will not
  // fallback to php because the filename will be munged.
  $this->field
    ->setSetting('file_extensions', 'doc')
    ->save();
  $this
    ->rebuildAll();

  // Test using a masked exploit file.
  $response = $this
    ->fileRequest($uri, $php_string, [
    'Content-Disposition' => 'filename="example_3.php.doc"',
  ]);

  // The filename is munged.
  $expected = $this
    ->getExpectedDocument(3, 'example_3.php_.doc', TRUE);

  // Override the expected filesize.
  $expected['data']['attributes']['filesize'] = strlen($php_string);

  // The file mime should be 'application/msword'.
  $expected['data']['attributes']['filemime'] = 'application/msword';
  $this
    ->assertResponseData($expected, $response);
  $this
    ->assertTrue(file_exists('public://foobar/example_3.php_.doc'));
  $this
    ->assertFalse(file_exists('public://foobar/example_3.php.doc'));

  // Now allow insecure uploads.
  \Drupal::configFactory()
    ->getEditable('system.file')
    ->set('allow_insecure_uploads', TRUE)
    ->save();

  // Allow all file uploads. This is very insecure.
  $this->field
    ->setSetting('file_extensions', '')
    ->save();
  $this
    ->rebuildAll();
  $response = $this
    ->fileRequest($uri, $php_string, [
    'Content-Disposition' => 'filename="example_4.php"',
  ]);
  $expected = $this
    ->getExpectedDocument(4, 'example_4.php', TRUE);

  // Override the expected filesize.
  $expected['data']['attributes']['filesize'] = strlen($php_string);

  // The file mime should also now be PHP.
  $expected['data']['attributes']['filemime'] = 'application/x-httpd-php';
  $this
    ->assertResponseData($expected, $response);
  $this
    ->assertTrue(file_exists('public://foobar/example_4.php'));
}