|
| 1 | +import shutil |
| 2 | +import os |
| 3 | +import pymupdf |
| 4 | +import subprocess |
| 5 | +import sys |
| 6 | + |
| 7 | + |
| 8 | +def test_4767(): |
| 9 | + ''' |
| 10 | + Check handling of unsafe paths in `pymupdf embed-extract`. |
| 11 | + ''' |
| 12 | + with pymupdf.open() as document: |
| 13 | + document.new_page() |
| 14 | + document.embfile_add( |
| 15 | + 'evil_entry', |
| 16 | + b'poc:traversal test\n', |
| 17 | + filename="../../test.txt", |
| 18 | + ufilename="../../test.txt", |
| 19 | + desc="poc", |
| 20 | + ) |
| 21 | + document.embfile_add( |
| 22 | + 'evil_entry2', |
| 23 | + b'poc:traversal test\n', |
| 24 | + filename="test2.txt", |
| 25 | + ufilename="test2.txt", |
| 26 | + desc="poc", |
| 27 | + ) |
| 28 | + path = os.path.abspath(f'{__file__}/../../tests/test_4767.pdf') |
| 29 | + document.save(path) |
| 30 | + testdir = os.path.abspath(f'{__file__}/../../tests/test_4767_dir').replace('\\', '/') |
| 31 | + shutil.rmtree(testdir, ignore_errors=1) |
| 32 | + os.makedirs(f'{testdir}/one/two', exist_ok=1) |
| 33 | + |
| 34 | + def run(command, *, check=0, capture=1): |
| 35 | + print(f'Running: {command}') |
| 36 | + cp = subprocess.run( |
| 37 | + command, shell=1, |
| 38 | + text=1, |
| 39 | + check=check, |
| 40 | + stdout=subprocess.PIPE if capture else None, |
| 41 | + stderr=subprocess.STDOUT if capture else None, |
| 42 | + ) |
| 43 | + print(cp.stdout) |
| 44 | + return cp |
| 45 | + |
| 46 | + def get_paths(): |
| 47 | + paths = list() |
| 48 | + for dirpath, dirnames, filenames in os.walk(testdir): |
| 49 | + for filename in filenames: |
| 50 | + path = f'{dirpath}/{filename}'.replace('\\', '/') |
| 51 | + paths.append(path) |
| 52 | + return paths |
| 53 | + |
| 54 | + cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry') |
| 55 | + print(cp.stdout) |
| 56 | + assert cp.returncode |
| 57 | + assert cp.stdout == 'refusing to write stored name outside current directory: ../../test.txt\n' |
| 58 | + assert not get_paths() |
| 59 | + |
| 60 | + cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry -unsafe') |
| 61 | + assert cp.returncode == 0 |
| 62 | + assert cp.stdout == "saved entry 'evil_entry' as '../../test.txt'\n" |
| 63 | + paths = get_paths() |
| 64 | + print(f'{paths=}') |
| 65 | + assert paths == [f'{testdir}/test.txt'] |
| 66 | + |
| 67 | + cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry2') |
| 68 | + assert not cp.returncode |
| 69 | + assert cp.stdout == "saved entry 'evil_entry2' as 'test2.txt'\n" |
| 70 | + paths = get_paths() |
| 71 | + print(f'{paths=}') |
| 72 | + assert paths == [f'{testdir}/test.txt', f'{testdir}/one/two/test2.txt'] |
| 73 | + |
| 74 | + cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry2') |
| 75 | + assert cp.returncode |
| 76 | + assert cp.stdout == "refusing to overwrite existing file with stored name: test2.txt\n" |
| 77 | + paths = get_paths() |
| 78 | + print(f'{paths=}') |
| 79 | + assert paths == [f'{testdir}/test.txt', f'{testdir}/one/two/test2.txt'] |
| 80 | + |
| 81 | + cp = run(f'cd {testdir}/one/two && {sys.executable} -m pymupdf embed-extract {path} -name evil_entry2 -unsafe') |
| 82 | + assert not cp.returncode |
| 83 | + assert cp.stdout == "saved entry 'evil_entry2' as 'test2.txt'\n" |
| 84 | + paths = get_paths() |
| 85 | + print(f'{paths=}') |
| 86 | + assert paths == [f'{testdir}/test.txt', f'{testdir}/one/two/test2.txt'] |
0 commit comments