CVE-2022-31019 (published 2022-06-09T13:15Z)

Vapor is a server-side Swift HTTP web framework. When using automatic content decoding an attacker can craft a request body that can make the server crash with the following request: `curl -d "array[_0][0][array][_0][0][array]$(for f in $(seq 1100); do echo -n \'[_0][0][array]\'; done)[string][_0]=hello%20world" http://localhost:8080/foo`. The issue is unbounded, attacker controlled stack growth which will at some point lead to a stack overflow and a process crash. This issue has been fixed in version 4.61.1.

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

Weakness information:
cwe_id: CWE-674
cwe_name: Uncontrolled Recursion
description: The product does not properly control the amount of recursion that takes place,  consuming excessive resources, such as allocated memory or the program stack.
extended_description: 
        
      
      

Fix information:
repo_url: https://github.com/vapor/vapor
commit_details: repo_url: https://github.com/vapor/vapor
author: Tim Condon
author_date: 2022-06-06 17:36:34+01:00
msg: Merge pull request from GHSA-qvxg-wjxc-r4gg

* Add test for crash

* The sensible option

* Crude fix

* Throw an error if we hit a nesting limit

* Catch error in test
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: URLEncodedFormData.swift
change_type: ModificationType.MODIFY
diff: @@ -54,6 +54,7 @@ enum URLQueryFragment: ExpressibleByStringLiteral, Equatable {
 internal struct URLEncodedFormData: ExpressibleByArrayLiteral, ExpressibleByStringLiteral, ExpressibleByDictionaryLiteral, Equatable {
     var values: [URLQueryFragment]
     var children: [String: URLEncodedFormData]
+    let maxRecursionDepth = 100
     
     var hasOnlyValues: Bool {
         return children.count == 0
@@ -90,7 +91,10 @@ internal struct URLEncodedFormData: ExpressibleByArrayLiteral, ExpressibleByStri
         self.children = Dictionary(uniqueKeysWithValues: dictionaryLiteral)
     }
         
-    mutating func set(value: URLQueryFragment, forPath path: [String]) {
+    mutating func set(value: URLQueryFragment, forPath path: [String], recursionDepth: Int) throws {
+        guard recursionDepth <= maxRecursionDepth else {
+            throw URLEncodedFormError.reachedNestingLimit
+        }
         guard let firstElement = path.first else {
             self.values.append(value)
             return
@@ -101,7 +105,7 @@ internal struct URLEncodedFormData: ExpressibleByArrayLiteral, ExpressibleByStri
         } else {
             child = []
         }
-        child.set(value: value, forPath: Array(path[1...]))
+        try child.set(value: value, forPath: Array(path[1...]), recursionDepth: recursionDepth + 1)
         self.children[firstElement] = child
     }
 }

programming_language: Swift
filename: URLEncodedFormError.swift
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,7 @@
 /// Errors thrown while encoding/decoding `application/x-www-form-urlencoded` data.
 enum URLEncodedFormError: Error {
     case malformedKey(key: Substring)
+    case reachedNestingLimit
 }
 
 extension URLEncodedFormError: AbortError {
@@ -12,6 +13,8 @@ extension URLEncodedFormError: AbortError {
         switch self {
         case .malformedKey(let path):
             return "Malformed form-urlencoded key encountered: \(path)"
+        case .reachedNestingLimit:
+            return "The data supplied is too nested"
         }
     }
 }

programming_language: Swift
filename: URLEncodedFormParser.swift
change_type: ModificationType.MODIFY
diff: @@ -14,11 +14,11 @@ internal struct URLEncodedFormParser {
             switch kv.count {
             case 1:
                 let value = String(kv[0])
-                result.set(value: .urlEncoded(value), forPath: [])
+                try result.set(value: .urlEncoded(value), forPath: [], recursionDepth: 0)
             case 2:
                 let key = kv[0]
                 let value = String(kv[1])
-                result.set(value: .urlEncoded(value), forPath: try parseKey(key: Substring(key)))
+                try result.set(value: .urlEncoded(value), forPath: try parseKey(key: Substring(key)), recursionDepth: 0)
             default:
                 //Empty `&&`
                 continue

programming_language: Swift
filename: URLEncodedFormTests.swift
change_type: ModificationType.MODIFY
diff: @@ -574,6 +574,15 @@ final class URLEncodedFormTests: XCTestCase {
         ])
         XCTAssertEqual(data, "test=%26%3B!$'(),/:%3D%3F@~")
     }
+    
+    func testHeavilyNestedArray() throws {
+        var body = "x"
+        body += String(repeating: "[]", count: 80000)
+        body += "=y"
+        struct Foo: Content {}
+        XCTAssertThrowsError(try URLEncodedFormDecoder().decode(Foo.self, from: body))
+        XCTAssert(true, "We should not have crashed")
+    }
 }
 
 private struct User: Codable, Equatable {

programming_language: Swift