CVE-2020-15230 (published 2020-10-02T19:15Z)

Vapor is a web framework for Swift. In Vapor before version 4.29.4, Attackers can access data at arbitrary filesystem paths on the same host as an application. Only applications using FileMiddleware are affected. This is fixed in version 4.29.4.

Severity: MEDIUM
CVSS v2 base score: 4.0 (AV:N/AC:L/Au:S/C:P/I:N/A:N)
CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
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.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/vapor/vapor
commit_details: repo_url: https://github.com/vapor/vapor
author: Tanner
author_date: 2020-09-30 19:28:20-04:00
msg: fix relative percent decoding in file middleware (#2500)
repository: repo_url: https://github.com/vapor/vapor
repo_name: vapor/vapor
description: 💧 A server-side Swift HTTP web framework.
repo_language: Swift
owner: vapor
file_changes: filename: FileMiddleware.swift
change_type: ModificationType.MODIFY
diff: @@ -13,8 +13,10 @@ public final class FileMiddleware: Middleware {
 
     /// See `Middleware`.
     public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
-        // make a copy of the path
-        var path = request.url.path
+        // make a copy of the percent-decoded path
+        guard var path = request.url.path.removingPercentEncoding else {
+            return request.eventLoop.makeFailedFuture(Abort(.badRequest))
+        }
 
         // path must be relative.
         while path.hasPrefix("/") {
@@ -27,7 +29,7 @@ public final class FileMiddleware: Middleware {
         }
 
         // create absolute file path
-        let filePath = self.publicDirectory + (path.removingPercentEncoding ?? path)
+        let filePath = self.publicDirectory + path
 
         // check if file exists and is not a directory
         var isDir: ObjCBool = false

programming_language: Swift
filename: FileTests.swift
change_type: ModificationType.MODIFY
diff: @@ -61,4 +61,19 @@ final class FileTests: XCTestCase {
             XCTAssertEqual(res.body.string, "<h1>Hello</h1>\n")
         }
     }
+
+    func testPercentDecodedRelativePath() throws {
+        let app = Application(.testing)
+        defer { app.shutdown() }
+
+        let path = #file.split(separator: "/").dropLast().joined(separator: "/")
+        app.middleware.use(FileMiddleware(publicDirectory: "/" + path))
+
+        try app.test(.GET, "%2e%2e/VaporTests/Utilities/foo.txt") { res in
+            XCTAssertEqual(res.status, .forbidden)
+        }.test(.GET, "Utilities/foo.txt") { res in
+            XCTAssertEqual(res.status, .ok)
+            XCTAssertEqual(res.body.string, "bar\n")
+        }
+    }
 }

programming_language: Swift
filename: foo.txt
change_type: ModificationType.ADD
diff: @@ -0,0 +1 @@
+bar

programming_language: Batchfile