LINUX.ORG.RU

Убедиться, что создаваемый файл находится в заданном каталоге

 


0

1

Привет, пишу что-то вроде хранилища, в котором можно создавать файлы. Хранилище - это пока обычный каталог. Пытаюсь сделать проверку того, что путь к создаваемому файлу не вылазит за пределы каталога:

import pathlib

storage = pathlib.Path('/tmp/storage')
newfile = pathlib.Path('../../etc/passwd')

def add_file(storage, newfile):
    fullpath = storage.joinpath(newfile).resolve()
    if not storage in fullpath.parents():
        raise ValueError('{} is not inside {}', newfile, storage)
    ...

Проблема в том, что Path.resolve() не работает для файлов, которые ещё не существуют:

>>> add_file(storage, pathlib.Path('README'))
OSError: [Errno 2] No such file or directory: '/tmp/storage/README'

Как же блин безопасно проверить, что файл находится в пределах указанного каталога?

Ответ на: комментарий от zolden

pathlib для того и нужна, чтобы автоматически привести слеши к системным

anonymous
()
Ответ на: комментарий от zolden

В pathlib принято использовать прямые слеши на всех платформах.

HeipaVai1o
() автор топика

Я думаю, тут стоит вопрос, насколько хорошо надо защитить яйца от двери.

Если так только от опечаток защитить, то можно написать:

os.path.abspath(str(newfile)).startswith(str(storage) + '/')
Если тебе надо реально безопасно, то pathlib всё правильно делает, что не даёт тебе проверять несуществующие пути. У тебя в твоём каталоге может быть хоть ссылка на /. Поэтому надо сделать newfile.parent.resolve(), а если его не существует, то либо выдать ошибку, либо (если твоя программа поддерживает создание директорий), проверять уже его родителя, и так в цикле.

proud_anon ★★★★★
()
Ответ на: комментарий от proud_anon

либо (если твоя программа поддерживает создание директорий), проверять уже его родителя, и так в цикле.

Да, по ходу, так и придётся делать. Иначе у злоумышленника остаётся возможность создать свой симлинк наружу в момент между проверкой пути и созданием файла, и файл создастся уже снаружи.

HeipaVai1o
() автор топика

Есть один простой метод, позволяющий забыть о '../..' как о страшном сне.

В частности просто chroot'ишся в свой рабочий каталог и усё.

PS: решиние не питоно-спефично и как оно делается в питоне — я не знаю.

beastie ★★★★★
()
Ответ на: комментарий от HeipaVai1o

Иначе у злоумышленника

Так это реально вопрос безопасности, а не просто пользователь в рамках своих прав файлы создаёт?

Тогда вообще можно парсить путь вручную (Path.parts), если вышли куда не надо, запрещать создавать файл. И не давать создавать ссылки вообще.

proud_anon ★★★★★
()
Ответ на: комментарий от anonymous

Я не знаю, что это за NixOS и от куда у них проблемы с chroot (и зачем там mount --bind я тоже не представляю), но по крайней мере у меня результаты кардинально противоположные. :)

root@otto:/tmp/a# tree
.

0 directories, 0 files
root@otto:/tmp/a# time for i in `seq 1000`; do touch $i; rm $i; done

real    0m2.273s
user    0m0.116s
sys     0m0.376s
root@otto:/tmp/a# chroot /tmp/b
bash-4.2# tree
.
|-- bin
|   |-- bash
|   |-- rm
|   |-- sh
|   `-- touch
|-- lib
|   `-- x86_64-linux-gnu
|       |-- libc.so.6
|       |-- libdl.so.2
|       |-- libpthread.so.0
|       |-- librt.so.1
|       `-- libtinfo.so.5
|-- lib64
|   `-- ld-linux-x86-64.so.2
`-- usr
    `-- bin
        |-- seq
        |-- time
        `-- tree

6 directories, 13 files
bash-4.2# time for i in `seq 1000`; do touch $i; rm $i; done

real    0m1.963s
user    0m0.088s
sys     0m0.264s
bash-4.2# exit
exit
root@otto:/tmp/a# 

PS: если конечно быть лентяем и делать mount --bind EVERYTHING!!! вместо избранных cp, то наверно, да, пенальти будут. ;)

PPS: запускал несколько раз, т.ч. цифры более-менее точные и кеш тут не причём, если кто спросит.

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 2)
Ответ на: комментарий от beastie

PPPS: единственный drawback — chroot(2) доступен только root'у. Т.ч. после надо ещё делать setuid(2)/setgid(2).

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 1)

:3 :3 :3

if storage.exists() and storage.joinpath('README').exists():add_file(storage, pathlib.Path('README'))

wakuwaku ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.