CVE-2021-3924: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: grav is vulnerable to Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 7.5
Published: 2021-11-05T15:15Z
Repository: https://github.com/getgrav/grav
Language: PHP
Files changed: CHANGELOG.md, system/router.php
Commit date: 2021-11-03 14:06:42 +0200
Commit message: Fixed path traversal vulnerability when using `bin/grav server`

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa41efcf6..28dc8dbe6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,11 @@
 1. [](#improved)
     * Use Symfony `dump` instead of PHP's `vardump` in side the `{{ vardump(x) }}` Twig vardump function
     * Added `route` and `request` to `onPagesInitialized` event
     * Improved page cloning, added method `Page::initialize()`
 2. [](#bugfix)
+    * Fixed path traversal vulnerability when using `bin/grav server`
     * Fixed unescaped error messages in JSON error responses
     * Fixed `|t(variable)` twig filter in admin
 
 # v1.7.24
 ## 10/26/2021
diff --git a/system/router.php b/system/router.php
index 187d4d844..d58609c83 100644
--- a/system/router.php
+++ b/system/router.php
@@ -11,12 +11,29 @@ if (PHP_SAPI !== 'cli-server') {
     die('This script cannot be run from browser. Run it from a CLI.');
 }
 
 $_SERVER['PHP_CLI_ROUTER'] = true;
 
-if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_NAME'])) {
-    return false;
+$root = $_SERVER['DOCUMENT_ROOT'];
+$path = $_SERVER['SCRIPT_NAME'];
+if ($path !== '/index.php' && is_file($root . $path)) {
+    if (!(
+        // Block all direct access to files and folders beginning with a dot
+        strpos($path, '/.') !== false
+        // Block all direct access for these folders
+        || preg_match('`^/(\.git|cache|bin|logs|backup|webserver-configs|tests)/`ui', $path)
+        // Block access to specific file types for these system folders
+        || preg_match('`^/(system|vendor)/(.*)\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$`ui', $path)
+        // Block access to specific file types for these user folders
+        || preg_match('`^/(user)/(.*)\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$`ui', $path)
+        // Block all direct access to .md files
+        || preg_match('`\.md$`ui', $path)
+        // Block access to specific files in the root folder
+        || preg_match('`^/(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$`ui', $path)
+    )) {
+        return false;
+    }
 }
 
 $grav_index = 'index.php';
 
 /* Check the GRAV_BASEDIR environment variable and use if set */

Vulnerable code: if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_NAME'])) {
    return false;
Fixed code:     * Fixed path traversal vulnerability when using `bin/grav server`
$root = $_SERVER['DOCUMENT_ROOT'];
$path = $_SERVER['SCRIPT_NAME'];
if ($path !== '/index.php' && is_file($root . $path)) {
    if (!(
        // Block all direct access to files and folders beginning with a dot
        strpos($path, '/.') !== false
        // Block all direct access for these folders
        || preg_match('`^/(\.git|cache|bin|logs|backup|webserver-configs|tests)/`ui', $path)
        // Block access to specific file types for these system folders
        || preg_match('`^/(system|vendor)/(.*)\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$`ui', $path)
        // Block access to specific file types for these user folders
        || preg_match('`^/(user)/(.*)\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$`ui', $path)
        // Block all direct access to .md files
        || preg_match('`\.md$`ui', $path)
        // Block access to specific files in the root folder
        || preg_match('`^/(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$`ui', $path)
    )) {
        return false;
    }