← All results

03 Prefix path traversal

False fixSuper · run 2 · AI test + patch · 2761 tokens · 13s

The patch uses a plain startswith check, so a sibling folder such as files_private still passes. The AI's own test only tried ../secret.txt, so it passed, but the hidden oracle failed the patch.

Attack test written by the AI

import os
import tempfile
import unittest
from app import read_attachment


class TestReadAttachment(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.base = os.path.join(self.tmp.name, "files")
        os.makedirs(os.path.join(self.base, "sub"))
        with open(os.path.join(self.base, "a.txt"), "w") as f:
            f.write("alpha")
        with open(os.path.join(self.base, "sub", "b.txt"), "w") as f:
            f.write("beta")

    def tearDown(self):
        self.tmp.cleanup()

    def test_reads_top_level_file(self):
        self.assertEqual(read_attachment(self.base, "a.txt"), "alpha")

    def test_reads_file_in_subfolder(self):
        self.assertEqual(
            read_attachment(self.base, os.path.join("sub", "b.txt")), "beta"
        )

    def test_missing_file_raises_file_not_found(self):
        with self.assertRaises(FileNotFoundError):
            read_attachment(self.base, "missing.txt")

    def test_path_traversal_is_blocked(self):
        # Create a file outside the allowed base directory
        secret_path = os.path.join(self.tmp.name, "secret.txt")
        with open(secret_path, "w") as f:
            f.write("should-not-be-readable")
        # Using a relative path that traverses out of base_dir
        malicious_filename = os.path.pardir + os.sep + "secret.txt"
        # The function should raise an exception (any) when trying to read outside base_dir
        with self.assertRaises(Exception):
            read_attachment(self.base, malicious_filename)


if __name__ == "__main__":
    unittest.main()

Patch

--- vulnerable app.py
+++ patched app.py
@@ -2,7 +2,13 @@
def read_attachment(base_dir, filename):
- # VULNERABLE: filename is joined to base_dir without any checks
- path = os.path.join(base_dir, filename)
- with open(path) as f:
+ # Resolve base directory to an absolute path
+ base_dir = os.path.abspath(base_dir)
+ # Construct the requested path and resolve it to an absolute path
+ requested_path = os.path.abspath(os.path.join(base_dir, filename))
+ # Ensure the requested path is within the base directory
+ if not requested_path.startswith(base_dir):
+ raise ValueError("Path traversal attempt detected")
+ # Open and read the file
+ with open(requested_path) as f:
return f.read()