CVE-2022-25358: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: A ..%2F path traversal vulnerability exists in the path handler of awful-salmonella-tar before 0.0.4. Attackers can only list directories (not read files). This occurs because the safe-path? Scheme predicate is not used for directories.
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: 5.3
Published: 2022-02-18T22:15Z
Repository: https://github.com/mario-goulart/awful-salmonella-tar
Language: Other
Files changed: awful-salmonella-tar.scm
Commit date: 2022-02-16 19:55:22 +0100
Commit message: Fix path traversal vulnerability

This change fixes a path traversal vulnerability that would allow
attackers to navigate through the filesystem of the server (provided
execute access to directories for the user running the web server).
Attackers could only list the contents of directories -- not download
files.

The vulnerability was caused by the lack of a check for the validity
requested paths when handling directories, notably when `..%2F' (`../`
URL-encoded) was present in requested paths.

Background:

awful-samonella-tar is implemented using awful [0].  Awful is
implemented on top of spiffy [1], and overrides the
`(handle-not-found)` parameter to map URL paths to procedures.  Spiffy
takes some precautions regarding dealing with malicious paths when it
handles static files.  Code that uses spiffy to implement generation
of dynamic content (like awful does), must take their own precautions.

awful-salmonella-tar uses a procedure (`safe-path?') with a relatively
strict policy to allow access to files, but it was not being used to
validate access to directories, and that was causing the
vulnerability.

This change applies `safe-path?` to all requested paths.

Thanks to Chris Brannon for responsibly reporting this issue.

[0] https://wiki.call-cc.org/eggref/5/awful
[1] https://wiki.call-cc.org/eggref/5/spiffy

Diff: diff --git a/awful-salmonella-tar.scm b/awful-salmonella-tar.scm
index 83f44a2..b3cb706 100644
--- a/awful-salmonella-tar.scm
+++ b/awful-salmonella-tar.scm
@@ -229,32 +229,36 @@
 
     (define-page (irregex (string-append base-path ".*"))
       (lambda (req-path)
         (let ((fs-path (make-pathname (list (root-path)
                                             (salmonella-reports-dir))
-                                      req-path)))
-          (if (directory? fs-path)
-              (render-dir req-path fs-path)
-              (lambda ()
-                (let ((not-found (lambda ()
-                                   (send-status 'not-found))))
-                  (handle-exceptions exn
-                    (not-found)
-                    (cond ((equal? (pathname-strip-directory req-path)
-                                   (salmonella-report-dir))
-                           (redirect-to (string-append req-path "/")))
-                          ((equal? (pathname-strip-directory (string-chomp req-path "/"))
-                                   (salmonella-report-dir))
-                           (cond ((tar-get (make-pathname req-path (index-file)))
-                                  => send-file-from-cache)
-                                 (else (not-found))))
-                          ((tar-get req-path) => send-file-from-cache)
-                          (else
-                           (if (file-exists? (make-pathname (list (root-path)
-                                                                  (salmonella-reports-dir))
-                                                            req-path))
-                               (parameterize ((root-path (salmonella-reports-dir)))
-                                 (send-file req-path))
-                               (not-found)))))))))))
+                                      req-path))
+              (not-found (lambda ()
+                           (send-status 'not-found))))
+          (cond ((not (safe-path? req-path))
+                 (lambda ()
+                   (not-found)))
+                ((directory? fs-path)
+                 (render-dir req-path fs-path))
+                (else
+                 (lambda ()
+                   (handle-exceptions exn
+                     (not-found)
+                     (cond ((equal? (pathname-strip-directory req-path)
+                                    (salmonella-report-dir))
+                            (redirect-to (string-append req-path "/")))
+                           ((equal? (pathname-strip-directory (string-chomp req-path "/"))
+                                    (salmonella-report-dir))
+                            (cond ((tar-get (make-pathname req-path (index-file)))
+                                   => send-file-from-cache)
+                                  (else (not-found))))
+                           ((tar-get req-path) => send-file-from-cache)
+                           (else
+                            (if (file-exists? (make-pathname (list (root-path)
+                                                                   (salmonella-reports-dir))
+                                                             req-path))
+                                (parameterize ((root-path (salmonella-reports-dir)))
+                                  (send-file req-path))
+                                (not-found)))))))))))
     ) ;; end define-app
   ) ;; end awful-salmonella-tar
 ) ;; end module

Vulnerable code:                                       req-path)))
          (if (directory? fs-path)
              (render-dir req-path fs-path)
              (lambda ()
                (let ((not-found (lambda ()
                                   (send-status 'not-found))))
                  (handle-exceptions exn
                    (not-found)
                    (cond ((equal? (pathname-strip-directory req-path)
                                   (salmonella-report-dir))
                           (redirect-to (string-append req-path "/")))
                          ((equal? (pathname-strip-directory (string-chomp req-path "/"))
                                   (salmonella-report-dir))
                           (cond ((tar-get (make-pathname req-path (index-file)))
                                  => send-file-from-cache)
                                 (else (not-found))))
                          ((tar-get req-path) => send-file-from-cache)
                          (else
                           (if (file-exists? (make-pathname (list (root-path)
                                                                  (salmonella-reports-dir))
                                                            req-path))
                               (parameterize ((root-path (salmonella-reports-dir)))
                                 (send-file req-path))
                               (not-found)))))))))))
Fixed code:                                       req-path))
              (not-found (lambda ()
                           (send-status 'not-found))))
          (cond ((not (safe-path? req-path))
                 (lambda ()
                   (not-found)))
                ((directory? fs-path)
                 (render-dir req-path fs-path))
                (else
                 (lambda ()
                   (handle-exceptions exn
                     (not-found)
                     (cond ((equal? (pathname-strip-directory req-path)
                                    (salmonella-report-dir))
                            (redirect-to (string-append req-path "/")))
                           ((equal? (pathname-strip-directory (string-chomp req-path "/"))
                                    (salmonella-report-dir))
                            (cond ((tar-get (make-pathname req-path (index-file)))
                                   => send-file-from-cache)
                                  (else (not-found))))
                           ((tar-get req-path) => send-file-from-cache)
                           (else
                            (if (file-exists? (make-pathname (list (root-path)
                                                                   (salmonella-reports-dir))
                                                             req-path))
                                (parameterize ((root-path (salmonella-reports-dir)))
                                  (send-file req-path))
                                (not-found)))))))))))