MTimeProtectedFileStorage.php 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. <?php
  2. namespace Drupal\Component\PhpStorage;
  3. /**
  4. * Stores PHP code in files with securely hashed names.
  5. *
  6. * The goal of this class is to ensure that if a PHP file is replaced with
  7. * an untrusted one, it does not get loaded. Since mtime granularity is 1
  8. * second, we cannot prevent an attack that happens within one second of the
  9. * initial save(). However, it is very unlikely for an attacker exploiting an
  10. * upload or file write vulnerability to also know when a legitimate file is
  11. * being saved, discover its hash, undo its file permissions, and override the
  12. * file with an upload all within a single second. Being able to accomplish
  13. * that would indicate a site very likely vulnerable to many other attack
  14. * vectors.
  15. *
  16. * Each file is stored in its own unique containing directory. The hash is
  17. * based on the virtual file name, the containing directory's mtime, and a
  18. * cryptographically hard to guess secret string. Thus, even if the hashed file
  19. * name is discovered and replaced by an untrusted file (e.g., via a
  20. * move_uploaded_file() invocation by a script that performs insufficient
  21. * validation), the directory's mtime gets updated in the process, invalidating
  22. * the hash and preventing the untrusted file from getting loaded. Also, the
  23. * file mtime will be checked providing security against overwriting in-place,
  24. * at the cost of an additional system call for every load() and exists().
  25. *
  26. * The containing directory is created with the same name as the virtual file
  27. * name (slashes replaced with hashmarks) to assist with debugging, since the
  28. * file itself is stored with a name that's meaningless to humans.
  29. */
  30. class MTimeProtectedFileStorage extends MTimeProtectedFastFileStorage {
  31. /**
  32. * {@inheritdoc}
  33. */
  34. public function load($name) {
  35. if (($filename = $this->checkFile($name)) !== FALSE) {
  36. // Inline parent::load() to avoid an expensive getFullPath() call.
  37. return (@include_once $filename) !== FALSE;
  38. }
  39. return FALSE;
  40. }
  41. /**
  42. * {@inheritdoc}
  43. */
  44. public function exists($name) {
  45. return $this->checkFile($name) !== FALSE;
  46. }
  47. /**
  48. * Determines whether a protected file exists and sets the filename too.
  49. *
  50. * @param string $name
  51. * The virtual file name. Can be a relative path.
  52. *
  53. * @return string|false
  54. * The full path where the file is if it is valid, FALSE otherwise.
  55. */
  56. protected function checkFile($name) {
  57. $filename = $this->getFullPath($name, $directory, $directory_mtime);
  58. return file_exists($filename) && filemtime($filename) <= $directory_mtime ? $filename : FALSE;
  59. }
  60. /**
  61. * {@inheritdoc}
  62. */
  63. public function getPath($name) {
  64. return $this->checkFile($name);
  65. }
  66. }