| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | package caddyhttp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2024-04-23 20:05:57 -04:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestSanitizedPathJoin(t *testing.T) { | 
					
						
							|  |  |  | 	// For reference: | 
					
						
							|  |  |  | 	// %2e = . | 
					
						
							|  |  |  | 	// %2f = / | 
					
						
							|  |  |  | 	// %5c = \ | 
					
						
							|  |  |  | 	for i, tc := range []struct { | 
					
						
							| 
									
										
										
										
											2024-04-23 20:05:57 -04:00
										 |  |  | 		inputRoot     string | 
					
						
							|  |  |  | 		inputPath     string | 
					
						
							|  |  |  | 		expect        string | 
					
						
							|  |  |  | 		expectWindows string | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputPath: "", | 
					
						
							|  |  |  | 			expect:    ".", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputPath: "/", | 
					
						
							|  |  |  | 			expect:    ".", | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			// fileserver.MatchFile passes an inputPath of "//" for some try_files values. | 
					
						
							|  |  |  | 			// See https://github.com/caddyserver/caddy/issues/6352 | 
					
						
							|  |  |  | 			inputPath: "//", | 
					
						
							|  |  |  | 			expect:    filepath.FromSlash("./"), | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			inputPath: "/foo", | 
					
						
							|  |  |  | 			expect:    "foo", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputPath: "/foo/", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("foo/"), | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputPath: "/foo/bar", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("foo/bar"), | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot: "/a", | 
					
						
							|  |  |  | 			inputPath: "/foo/bar", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("/a/foo/bar"), | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputPath: "/foo/../bar", | 
					
						
							|  |  |  | 			expect:    "bar", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot: "/a/b", | 
					
						
							|  |  |  | 			inputPath: "/foo/../bar", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("/a/b/bar"), | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot: "/a/b", | 
					
						
							|  |  |  | 			inputPath: "/..%2fbar", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("/a/b/bar"), | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot: "/a/b", | 
					
						
							|  |  |  | 			inputPath: "/%2e%2e%2fbar", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("/a/b/bar"), | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			// inputPath fails the IsLocal test so only the root is returned, | 
					
						
							|  |  |  | 			// but with a trailing slash since one was included in inputPath | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 			inputRoot: "/a/b", | 
					
						
							|  |  |  | 			inputPath: "/%2e%2e%2f%2e%2e%2f", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("/a/b/"), | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-02-07 05:13:17 -05:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot: "/a/b", | 
					
						
							|  |  |  | 			inputPath: "/foo%2fbar", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("/a/b/foo/bar"), | 
					
						
							| 
									
										
										
										
											2024-02-07 05:13:17 -05:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot: "/a/b", | 
					
						
							|  |  |  | 			inputPath: "/foo%252fbar", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("/a/b/foo%2fbar"), | 
					
						
							| 
									
										
										
										
											2024-02-07 05:13:17 -05:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot: "C:\\www", | 
					
						
							|  |  |  | 			inputPath: "/foo/bar", | 
					
						
							|  |  |  | 			expect:    filepath.Join("C:\\www", "foo", "bar"), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2024-04-23 20:05:57 -04:00
										 |  |  | 			inputRoot:     "C:\\www", | 
					
						
							|  |  |  | 			inputPath:     "/D:\\foo\\bar", | 
					
						
							|  |  |  | 			expect:        filepath.Join("C:\\www", "D:\\foo\\bar"), | 
					
						
							| 
									
										
										
										
											2024-06-04 14:23:55 -06:00
										 |  |  | 			expectWindows: "C:\\www", // inputPath fails IsLocal on Windows | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot:     `C:\www`, | 
					
						
							|  |  |  | 			inputPath:     `/..\windows\win.ini`, | 
					
						
							|  |  |  | 			expect:        `C:\www/..\windows\win.ini`, | 
					
						
							|  |  |  | 			expectWindows: `C:\www`, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot:     `C:\www`, | 
					
						
							|  |  |  | 			inputPath:     `/..\..\..\..\..\..\..\..\..\..\windows\win.ini`, | 
					
						
							|  |  |  | 			expect:        `C:\www/..\..\..\..\..\..\..\..\..\..\windows\win.ini`, | 
					
						
							|  |  |  | 			expectWindows: `C:\www`, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot:     `C:\www`, | 
					
						
							|  |  |  | 			inputPath:     `/..%5cwindows%5cwin.ini`, | 
					
						
							|  |  |  | 			expect:        `C:\www/..\windows\win.ini`, | 
					
						
							|  |  |  | 			expectWindows: `C:\www`, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			inputRoot:     `C:\www`, | 
					
						
							|  |  |  | 			inputPath:     `/..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5cwindows%5cwin.ini`, | 
					
						
							|  |  |  | 			expect:        `C:\www/..\..\..\..\..\..\..\..\..\..\windows\win.ini`, | 
					
						
							|  |  |  | 			expectWindows: `C:\www`, | 
					
						
							| 
									
										
										
										
											2024-04-23 20:05:57 -04:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			// https://github.com/golang/go/issues/56336#issuecomment-1416214885 | 
					
						
							|  |  |  | 			inputRoot: "root", | 
					
						
							|  |  |  | 			inputPath: "/a/b/../../c", | 
					
						
							| 
									
										
										
										
											2024-06-01 20:40:59 -07:00
										 |  |  | 			expect:    filepath.FromSlash("root/c"), | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} { | 
					
						
							|  |  |  | 		// we don't *need* to use an actual parsed URL, but it | 
					
						
							|  |  |  | 		// adds some authenticity to the tests since real-world | 
					
						
							|  |  |  | 		// values will be coming in from URLs; thus, the test | 
					
						
							|  |  |  | 		// corpus can contain paths as encoded by clients, which | 
					
						
							|  |  |  | 		// more closely emulates the actual attack vector | 
					
						
							|  |  |  | 		u, err := url.Parse("http://test:9999" + tc.inputPath) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("Test %d: invalid URL: %v", i, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		actual := SanitizedPathJoin(tc.inputRoot, u.Path) | 
					
						
							| 
									
										
										
										
											2024-04-23 20:05:57 -04:00
										 |  |  | 		if runtime.GOOS == "windows" && tc.expectWindows != "" { | 
					
						
							|  |  |  | 			tc.expect = tc.expectWindows | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 		if actual != tc.expect { | 
					
						
							| 
									
										
										
										
											2022-10-18 21:55:25 -06:00
										 |  |  | 			t.Errorf("Test %d: SanitizedPathJoin('%s', '%s') =>  '%s' (expected '%s')", | 
					
						
							| 
									
										
										
										
											2021-06-17 09:59:08 -06:00
										 |  |  | 				i, tc.inputRoot, tc.inputPath, actual, tc.expect) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-08-16 08:48:57 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestCleanPath(t *testing.T) { | 
					
						
							|  |  |  | 	for i, tc := range []struct { | 
					
						
							|  |  |  | 		input        string | 
					
						
							|  |  |  | 		mergeSlashes bool | 
					
						
							|  |  |  | 		expect       string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:  "/foo", | 
					
						
							|  |  |  | 			expect: "/foo", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:  "/foo/", | 
					
						
							|  |  |  | 			expect: "/foo/", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:  "//foo", | 
					
						
							|  |  |  | 			expect: "//foo", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:        "//foo", | 
					
						
							|  |  |  | 			mergeSlashes: true, | 
					
						
							|  |  |  | 			expect:       "/foo", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:        "/foo//bar/", | 
					
						
							|  |  |  | 			mergeSlashes: true, | 
					
						
							|  |  |  | 			expect:       "/foo/bar/", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:  "/foo/./.././bar", | 
					
						
							|  |  |  | 			expect: "/bar", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:  "/foo//./..//./bar", | 
					
						
							|  |  |  | 			expect: "/foo//bar", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:  "/foo///./..//./bar", | 
					
						
							|  |  |  | 			expect: "/foo///bar", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:  "/foo///./..//.", | 
					
						
							|  |  |  | 			expect: "/foo//", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			input:  "/foo//./bar", | 
					
						
							|  |  |  | 			expect: "/foo//bar", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} { | 
					
						
							|  |  |  | 		actual := CleanPath(tc.input, tc.mergeSlashes) | 
					
						
							|  |  |  | 		if actual != tc.expect { | 
					
						
							|  |  |  | 			t.Errorf("Test %d [input='%s' mergeSlashes=%t]: Got '%s', expected '%s'", | 
					
						
							|  |  |  | 				i, tc.input, tc.mergeSlashes, actual, tc.expect) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |