c36453c3280a991e0d79ae01e68a91855b6cd6fa
[yaffs-website] / web / core / tests / Drupal / Tests / Component / PhpStorage / MTimeProtectedFileStorageBase.php
1 <?php
2
3 namespace Drupal\Tests\Component\PhpStorage;
4
5 use Drupal\Component\Utility\Crypt;
6
7 /**
8  * Base test class for MTime protected storage.
9  */
10 abstract class MTimeProtectedFileStorageBase extends PhpStorageTestBase {
11
12   /**
13    * The PHP storage class to test.
14    *
15    * This should be overridden by extending classes.
16    */
17   protected $storageClass;
18
19   /**
20    * The secret string to use for file creation.
21    *
22    * @var string
23    */
24   protected $secret;
25
26   /**
27    * Test settings to pass to storage instances.
28    *
29    * @var array
30    */
31   protected $settings;
32
33   /**
34    * {@inheritdoc}
35    */
36   protected function setUp() {
37     parent::setUp();
38
39     $this->secret = $this->randomMachineName();
40
41     $this->settings = [
42       'directory' => $this->directory,
43       'bin' => 'test',
44       'secret' => $this->secret,
45     ];
46   }
47
48   /**
49    * Tests basic load/save/delete operations.
50    *
51    * @covers ::load
52    * @covers ::save
53    * @covers ::delete
54    * @covers ::exists
55    */
56   public function testCRUD() {
57     $php = new $this->storageClass($this->settings);
58     $this->assertCRUD($php);
59   }
60
61   /**
62    * Tests the security of the MTimeProtectedFileStorage implementation.
63    *
64    * We test two attacks: first changes the file mtime, then the directory
65    * mtime too.
66    *
67    * We need to delay over 1 second for mtime test.
68    * @medium
69    */
70   public function testSecurity() {
71     $php = new $this->storageClass($this->settings);
72     $name = 'simpletest.php';
73     $php->save($name, '<?php');
74     $expected_root_directory = $this->directory . '/test';
75     if (substr($name, -4) === '.php') {
76       $expected_directory = $expected_root_directory . '/' . substr($name, 0, -4);
77     }
78     else {
79       $expected_directory = $expected_root_directory . '/' . $name;
80     }
81     $directory_mtime = filemtime($expected_directory);
82     $expected_filename = $expected_directory . '/' . Crypt::hmacBase64($name, $this->secret . $directory_mtime) . '.php';
83
84     // Ensure the file exists and that it and the containing directory have
85     // minimal permissions. fileperms() can return high bits unrelated to
86     // permissions, so mask with 0777.
87     $this->assertTrue(file_exists($expected_filename));
88     $this->assertSame(fileperms($expected_filename) & 0777, 0444);
89     $this->assertSame(fileperms($expected_directory) & 0777, 0777);
90
91     // Ensure the root directory for the bin has a .htaccess file denying web
92     // access.
93     $this->assertSame(file_get_contents($expected_root_directory . '/.htaccess'), call_user_func([$this->storageClass, 'htaccessLines']));
94
95     // Ensure that if the file is replaced with an untrusted one (due to another
96     // script's file upload vulnerability), it does not get loaded. Since mtime
97     // granularity is 1 second, we cannot prevent an attack that happens within
98     // a second of the initial save().
99     sleep(1);
100     for ($i = 0; $i < 2; $i++) {
101       $php = new $this->storageClass($this->settings);
102       $GLOBALS['hacked'] = FALSE;
103       $untrusted_code = "<?php\n" . '$GLOBALS["hacked"] = TRUE;';
104       chmod($expected_directory, 0700);
105       chmod($expected_filename, 0700);
106       if ($i) {
107         // Now try to write the file in such a way that the directory mtime
108         // changes and invalidates the hash.
109         file_put_contents($expected_filename . '.tmp', $untrusted_code);
110         rename($expected_filename . '.tmp', $expected_filename);
111       }
112       else {
113         // On the first try do not change the directory mtime but the filemtime
114         // is now larger than the directory mtime.
115         file_put_contents($expected_filename, $untrusted_code);
116       }
117       chmod($expected_filename, 0400);
118       chmod($expected_directory, 0100);
119       $this->assertSame(file_get_contents($expected_filename), $untrusted_code);
120       $this->assertSame($php->exists($name), $this->expected[$i]);
121       $this->assertSame($php->load($name), $this->expected[$i]);
122       $this->assertSame($GLOBALS['hacked'], $this->expected[$i]);
123     }
124     unset($GLOBALS['hacked']);
125   }
126
127 }