From 599ff4f76534ea2872f40b41c4ffbbcca987d6a5 Mon Sep 17 00:00:00 2001 From: Gregor Martynus <39992+gr2m@users.noreply.github.com> Date: Fri, 14 Feb 2025 13:30:16 -0800 Subject: [PATCH] fix: ReDos regex vulnerability, reported by @DayShift (#515) --- src/parse.ts | 3 +- src/util/extract-url-variable-names.ts | 4 +- test/parse.test.ts | 102 +++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/parse.ts b/src/parse.ts index c63af78b..021f4c91 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -59,7 +59,8 @@ export function parse(options: EndpointDefaults): RequestOptions { if (url.endsWith("/graphql")) { if (options.mediaType.previews?.length) { const previewsFromAcceptHeader = - headers.accept.match(/[\w-]+(?=-preview)/g) || ([] as string[]); + headers.accept.match(/(? { diff --git a/src/util/extract-url-variable-names.ts b/src/util/extract-url-variable-names.ts index 85f232a1..2f2c2283 100644 --- a/src/util/extract-url-variable-names.ts +++ b/src/util/extract-url-variable-names.ts @@ -1,7 +1,7 @@ -const urlVariableRegex = /\{[^}]+\}/g; +const urlVariableRegex = /\{[^{}}]+\}/g; function removeNonChars(variableName: string) { - return variableName.replace(/^\W+|\W+$/g, "").split(/,/); + return variableName.replace(/(?:^\W+)|(?:(? { expect(input.headers.accept).toEqual("application/vnd.github.v3+json"); }); + + it("Test ReDoS - attack string #1", async () => { + const startTime = performance.now(); + try { + endpoint.parse({ + method: "POST", + url: "/graphql", // Ensure that the URL ends with "/graphql" + headers: { + accept: "" + "A".repeat(100000) + "-", // Pass in the attack string + "content-type": "text/plain", + "user-agent": "Your User Agent String Here", + }, + mediaType: { + previews: ["test-preview"], // Ensure that mediaType.previews exists and has values + format: "raw", // Optional media format + }, + baseUrl: "https://api.github.com", + }); + } catch (error) { + // pass + } + const endTime = performance.now(); + const elapsedTime = endTime - startTime; + const reDosThreshold = 2000; + + expect(elapsedTime).toBeLessThanOrEqual(reDosThreshold); + if (elapsedTime > reDosThreshold) { + console.warn( + `🚨 Potential ReDoS Attack! getDuration method took ${elapsedTime.toFixed( + 2, + )} ms, exceeding threshold of ${reDosThreshold} ms.`, + ); + } + }); + + it("Test ReDoS - attack string #2", async () => { + const startTime = performance.now(); + try { + endpoint.parse({ + method: "POST", + url: "{".repeat(100000) + "@", // Pass in the attack string + headers: { + accept: "application/vnd.github.v3+json", + "content-type": "text/plain", + "user-agent": "Your User Agent String Here", + }, + mediaType: { + previews: ["test-preview"], // Ensure that mediaType.previews exists and has values + format: "raw", // Optional media format + }, + baseUrl: "https://api.github.com", + }); + } catch (error) { + // pass + } + const endTime = performance.now(); + const elapsedTime = endTime - startTime; + const reDosThreshold = 2000; + + expect(elapsedTime).toBeLessThanOrEqual(reDosThreshold); + if (elapsedTime > reDosThreshold) { + console.warn( + `🚨 Potential ReDoS Attack! getDuration method took ${elapsedTime.toFixed( + 2, + )} ms, exceeding threshold of ${reDosThreshold} ms.`, + ); + } + }); + + it("Test ReDoS - attack string #3", async () => { + const startTime = performance.now(); + try { + endpoint.parse({ + method: "POST", + url: "{" + "00" + "\u0000".repeat(100000) + "a!a" + "}", // Pass in the attack string + headers: { + accept: "application/vnd.github.v3+json", + "content-type": "text/plain", + "user-agent": "Your User Agent String Here", + }, + mediaType: { + previews: ["test-preview"], + format: "raw", + }, + baseUrl: "https://api.github.com", + }); + } catch (error) { + // pass + } + const endTime = performance.now(); + const elapsedTime = endTime - startTime; + const reDosThreshold = 2000; + + expect(elapsedTime).toBeLessThanOrEqual(reDosThreshold); + if (elapsedTime > reDosThreshold) { + console.warn( + `🚨 Potential ReDoS Attack! getDuration method took ${elapsedTime.toFixed( + 2, + )} ms, exceeding threshold of ${reDosThreshold} ms.`, + ); + } + }); });