| 64 | } |
| 65 | |
| 66 | func TestSaveFile_PathTraversal(t *testing.T) { |
| 67 | uploadDir := t.TempDir() |
| 68 | |
| 69 | body := &bytes.Buffer{} |
| 70 | writer := multipart.NewWriter(body) |
| 71 | part, err := writer.CreateFormFile("file", "../../evil.txt") |
| 72 | if err != nil { |
| 73 | t.Fatalf("Failed to create form file: %v", err) |
| 74 | } |
| 75 | part.Write([]byte("malicious content")) |
| 76 | writer.Close() |
| 77 | |
| 78 | req := httptest.NewRequest(http.MethodPost, "/upload", body) |
| 79 | req.Header.Set("Content-Type", writer.FormDataContentType()) |
| 80 | ctx := &Ctx{Request: req, Server: &Server{config: Config{UploadPath: uploadDir}}} |
| 81 | |
| 82 | _, fh, err := ctx.FormFile("file") |
| 83 | if err != nil { |
| 84 | t.Fatalf("Failed to retrieve form file: %v", err) |
| 85 | } |
| 86 | |
| 87 | err = ctx.SaveFile(fh) |
| 88 | if err != nil { |
| 89 | t.Fatalf("SaveFile returned unexpected error: %v", err) |
| 90 | } |
| 91 | |
| 92 | // The file must be inside uploadDir, not at the traversal destination. |
| 93 | expected := filepath.Join(uploadDir, "evil.txt") |
| 94 | if _, statErr := os.Stat(expected); os.IsNotExist(statErr) { |
| 95 | t.Errorf("expected file at %s but it was not created", expected) |
| 96 | } |
| 97 | |
| 98 | // Traversal destination must not exist. |
| 99 | traversal := filepath.Join(uploadDir, "../../evil.txt") |
| 100 | absTraversal, _ := filepath.Abs(traversal) |
| 101 | absUpload, _ := filepath.Abs(uploadDir) |
| 102 | if _, statErr := os.Stat(absTraversal); !os.IsNotExist(statErr) { |
| 103 | // Only flag this if the traversal path resolves outside uploadDir. |
| 104 | if len(absTraversal) < len(absUpload) || absTraversal[:len(absUpload)] != absUpload { |
| 105 | t.Errorf("path traversal: file exists outside upload dir at %s", absTraversal) |
| 106 | } |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | // TODO: Fix this tests |
| 111 | // |