diff options
author | sarahdenofiletrav <74922000+sarahdenofiletrav@users.noreply.github.com> | 2020-11-26 21:31:19 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-26 22:31:19 +0100 |
commit | 28869a632d190dc29d78738bc5e90eadf99bc824 (patch) | |
tree | 85b749f6ffbaeada706a6b6d15b0f4ce2399d454 /std/http/file_server_test.ts | |
parent | 4f46dc999b9fd3f26b6586d06d099d7039ca35c8 (diff) |
fix(std/http): prevent path traversal (#8474)
Fix path traversal problem when the request URI
does not have a leading slash.
The file server now returns HTTP 400 when requests
lack the leading slash, and are not absolute URIs.
(https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html).
Diffstat (limited to 'std/http/file_server_test.ts')
-rw-r--r-- | std/http/file_server_test.ts | 101 |
1 files changed, 100 insertions, 1 deletions
diff --git a/std/http/file_server_test.ts b/std/http/file_server_test.ts index 5f7137998..050109fe0 100644 --- a/std/http/file_server_test.ts +++ b/std/http/file_server_test.ts @@ -2,11 +2,12 @@ import { assert, assertEquals, + assertNotEquals, assertStringIncludes, } from "../testing/asserts.ts"; import { BufReader } from "../io/bufio.ts"; import { TextProtoReader } from "../textproto/mod.ts"; -import { ServerRequest } from "./server.ts"; +import { Response, ServerRequest } from "./server.ts"; import { FileServerArgs, serveFile } from "./file_server.ts"; import { dirname, fromFileUrl, join, resolve } from "../path/mod.ts"; let fileServer: Deno.Process<Deno.RunOptions & { stdout: "piped" }>; @@ -78,6 +79,78 @@ async function killFileServer(): Promise<void> { fileServer.stdout!.close(); } +interface StringResponse extends Response { + body: string; +} + +/* HTTP GET request allowing arbitrary paths */ +async function fetchExactPath( + hostname: string, + port: number, + path: string, +): Promise<StringResponse> { + const encoder = new TextEncoder(); + const decoder = new TextDecoder(); + const request = encoder.encode("GET " + path + " HTTP/1.1\r\n\r\n"); + let conn: void | Deno.Conn; + try { + conn = await Deno.connect( + { hostname: hostname, port: port, transport: "tcp" }, + ); + await Deno.writeAll(conn, request); + let currentResult = ""; + let contentLength = -1; + let startOfBody = -1; + for await (const chunk of Deno.iter(conn)) { + currentResult += decoder.decode(chunk); + if (contentLength === -1) { + const match = /^content-length: (.*)$/m.exec(currentResult); + if (match && match[1]) { + contentLength = Number(match[1]); + } + } + if (startOfBody === -1) { + const ind = currentResult.indexOf("\r\n\r\n"); + if (ind !== -1) { + startOfBody = ind + 4; + } + } + if (startOfBody !== -1 && contentLength !== -1) { + const byteLen = encoder.encode(currentResult).length; + if (byteLen >= contentLength + startOfBody) { + break; + } + } + } + const status = /^HTTP\/1.1 (...)/.exec(currentResult); + let statusCode = 0; + if (status && status[1]) { + statusCode = Number(status[1]); + } + + const body = currentResult.slice(startOfBody); + const headersStr = currentResult.slice(0, startOfBody); + const headersReg = /^(.*): (.*)$/mg; + const headersObj: { [i: string]: string } = {}; + let match = headersReg.exec(headersStr); + while (match !== null) { + if (match[1] && match[2]) { + headersObj[match[1]] = match[2]; + } + match = headersReg.exec(headersStr); + } + return { + status: statusCode, + headers: new Headers(headersObj), + body: body, + }; + } finally { + if (conn) { + Deno.close(conn.rid); + } + } +} + Deno.test( "file_server serveFile", async (): Promise<void> => { @@ -169,6 +242,32 @@ Deno.test("checkPathTraversal", async function (): Promise<void> { } }); +Deno.test("checkPathTraversalNoLeadingSlash", async function (): Promise<void> { + await startFileServer(); + try { + const res = await fetchExactPath("127.0.0.1", 4507, "../../../.."); + assertEquals(res.status, 400); + } finally { + await killFileServer(); + } +}); + +Deno.test("checkPathTraversalAbsoluteURI", async function (): Promise<void> { + await startFileServer(); + try { + //allowed per https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html + const res = await fetchExactPath( + "127.0.0.1", + 4507, + "http://localhost/../../../..", + ); + assertEquals(res.status, 200); + assertStringIncludes(res.body, "README.md"); + } finally { + await killFileServer(); + } +}); + Deno.test("checkURIEncodedPathTraversal", async function (): Promise<void> { await startFileServer(); try { |