From 30f3b831d31ec47e7d120bcd34194b7b69e6f716 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Tue, 27 Oct 2020 11:48:45 +0100 Subject: [PATCH] fix: path traversal in std/http/file_server.ts (#8134) --- std/http/file_server.ts | 5 +++-- std/http/file_server_test.ts | 37 +++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/std/http/file_server.ts b/std/http/file_server.ts index e4c8c49313..b75f9f9c14 100644 --- a/std/http/file_server.ts +++ b/std/http/file_server.ts @@ -322,14 +322,15 @@ function html(strings: TemplateStringsArray, ...values: unknown[]): string { } function normalizeURL(url: string): string { - let normalizedUrl = posix.normalize(url); + let normalizedUrl = url; try { - normalizedUrl = decodeURIComponent(normalizedUrl); + normalizedUrl = decodeURI(normalizedUrl); } catch (e) { if (!(e instanceof URIError)) { throw e; } } + normalizedUrl = posix.normalize(normalizedUrl); const startOfParams = normalizedUrl.indexOf("?"); return startOfParams > -1 ? normalizedUrl.slice(0, startOfParams) diff --git a/std/http/file_server_test.ts b/std/http/file_server_test.ts index 3368b2e15d..ca8d3b3b28 100644 --- a/std/http/file_server_test.ts +++ b/std/http/file_server_test.ts @@ -1,5 +1,9 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals } from "../testing/asserts.ts"; +import { + assert, + assertEquals, + assertStringIncludes, +} from "../testing/asserts.ts"; import { BufReader } from "../io/bufio.ts"; import { TextProtoReader } from "../textproto/mod.ts"; import { ServerRequest } from "./server.ts"; @@ -147,6 +151,37 @@ Deno.test("serveFallback", async function (): Promise { } }); +Deno.test("checkPathTraversal", async function (): Promise { + await startFileServer(); + try { + const res = await fetch( + "http://localhost:4507/../../../../../../../..", + ); + assert(res.headers.has("access-control-allow-origin")); + assert(res.headers.has("access-control-allow-headers")); + assertEquals(res.status, 200); + const listing = await res.text(); + assertStringIncludes(listing, "README.md"); + } finally { + await killFileServer(); + } +}); + +Deno.test("checkURIEncodedPathTraversal", async function (): Promise { + await startFileServer(); + try { + const res = await fetch( + "http://localhost:4507/%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..", + ); + assert(res.headers.has("access-control-allow-origin")); + assert(res.headers.has("access-control-allow-headers")); + assertEquals(res.status, 404); + const _ = await res.text(); + } finally { + await killFileServer(); + } +}); + Deno.test("serveWithUnorthodoxFilename", async function (): Promise { await startFileServer(); try {