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

8

Учитывая приведенный ниже сценарий, как я могу убедиться, что аргумент содержит только допустимое имя файла, /home/charlesingalls/а не путь ( ../home/carolineingalls/), подстановочный знак и т. Д.?

Я только хочу, чтобы скрипт мог удалить один файл из заданного жестко закодированного каталога. Этот скрипт будет запускаться как привилегированный пользователь.

#!/bin/bash

rm -f /home/charlesingalls/"$1"
Аарон Чикали
источник
2
Если вы не хотите поддерживать «foo / bar», просто убедитесь, что он не содержит /. Подстановочные знаки не интерпретируются внутри кавычек.
Random832
3
Если вы удаляете файл , не используйте -rс rm. rm -rпредназначен для рекурсивного удаления каталога и всех файлов и каталогов под ним. Это полезно только при удалении каталогов. В общем, не груз-культ. т.е. не просто копируйте вещи, которые выглядят полезными, в вашу командную строку или скрипт, не понимая, что они делают или как они работают. Самолеты богов, несущие волшебный груз, могут разозлиться и удалить все свои файлы.
Cas
Хороший вопрос по поводу флага -r - я понимаю его использование, просто не очень хорошо продумал.
Аарон Чикали

Ответы:

7

Если вы хотите удалить только файл /home/charlesingalls(а не файл в подкаталоге), тогда это просто: просто убедитесь, что аргумент не содержит a /.

case "$1" in
  */*) echo 1>&2 "Refusing to remove a file in another directory"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac

Это работает, rmдаже если аргумент равен .или ..или пуст, но в этом случае rmбезуспешно не удастся удалить каталог.

Подстановочные знаки здесь не имеют значения, поскольку расширение подстановочных знаков не выполняется.

Это безопасно даже при наличии символических ссылок: если файл является символической ссылкой, символическая ссылка (которая находится в /home/charlesingalls) удаляется, и цель этой ссылки не затрагивается.

Обратите внимание, что это предполагает, что /home/charlesingallsне может быть перемещен или изменен. Это должно быть хорошо, если каталог жестко запрограммирован в сценарии, но если он определен по переменным, то определение может перестать быть действительным ко времени выполнения rmкоманды.

Основываясь на дополнительной информации о том, что аргумент является именем виртуального хоста, вы должны сделать белый, а не черный список: убедитесь, что имя является разумным именем виртуального хоста, а не просто запрещает косую черту. Я проверяю, что имя начинается со строчной буквы или цифры и что оно не содержит символов, кроме строчных букв, цифр, точек и тире.

LC_CTYPE=C LC_COLLATE=C
case "$1" in
  *[!-.0-9a-z]*|[!0-9a-z]*) echo >&2 "Invalid host name"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac
Жиль "ТАК - перестань быть злым"
источник
В этом случае файлы являются файлами конфигурации веб-сервера, которые были созданы предыдущим процессом. Все они имеют одинаковый уровень важности (каждый представляет собой виртуальный хост). Однако каталог, в котором они существуют, находится рядом с аналогичными каталогами, и все они существуют в каталоге, который принадлежит веб-серверу. Этот конкретный скрипт предназначен для того, чтобы разрешить удаление конфигураций виртуального хоста только в определенном каталоге.
Аарон Чикали
имеет ли этот подход смысл для вас при условии использования этого варианта? Я ценю ваш опыт и признаю, что в определенное время я «приводил базуку в перестрелку», чтобы автоматизировать что-то в Linux.
Аарон Чикали
@AaronCicali Почему скрипт может удалить любые конфигурации хоста, а не только ту, которая принадлежит сущности, которая сделала исходный запрос? Почему имя виртуального хоста не было проверено первым (тогда оно не будет содержать никаких специальных символов)?
Жиль "ТАК - перестань быть злым"
Объект, который делает исходный запрос, представляет собой графический интерфейс, который действительно имеет разрешение на удаление любого из виртуальных хостов в этой папке. Имя виртуального хоста проверяется первым. Это происходит из списка ранее созданных виртуальных хостов (доменных имен). Я считаю, что задача этого скрипта - обеспечить его максимальную безопасность, не полагаясь на безопасность другой части приложения. А именно, что он может удалять только файлы из этого конкретного каталога. Также потребуется выполнить дополнительную очистку.
Аарон Чикали
@AaronCicali Хорошо, в качестве дополнительной проверки вменяемости это имеет смысл. В этом случае вы должны создать белый список: принимайте только имена, которые выглядят как правдоподобные имена хостов Если вы не разрешаете субдомены, вы можете даже запретить.
Жиль "ТАК - перестань быть злым"
10

Этот ответ предполагает, что $1разрешено включать подкаталоги. Если вас интересует более простой случай, когда $1должно быть простое имя каталога, посмотрите другой ответ.


Подстановочные знаки не раскрываются в двойных кавычках. Поскольку $1в двойных кавычках, подстановочные знаки не являются проблемой.

Обе ../и символические ссылки могут скрыть реальное местоположение файла. Ниже показаны тесты, позволяющие определить, находится ли файл действительно, а не просто так, как мы хотим.

Новые системы: использование realpath

Что касается того, чтобы узнать, действительно ли файл находится в самом деле, находится ли он под /home/charlesingalls/или нет, вы можете использовать realpath:

realpath --relative-base=/home/charlesingalls/ "/home/charlesingalls/$1"  | grep -q '^/' && exit 1

Вышеприведенное запускается, exit 1если указанный файл $1находится где-либо, кроме каталога /home/charlesingalls/. realpathканонизирует весь путь, устраняя как символические ссылки, так и ../.

realpath является частью GNU coreutils и должен быть доступен в любой системе Linux.

realpathтребуется GNU coreutils 8.15 (январь 2012 г.) или выше .

Примеры

Чтобы продемонстрировать, как realpath следует ../для определения реального местоположения файла (например, -qопция grep опущена, так что фактический вывод grep видим):

$ touch /tmp/test
$ realpath --relative-base=$HOME "$HOME/../../tmp/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Чтобы продемонстрировать, как следует символические ссылки:

$ ln -s /tmp/test ~/test
$ realpath --relative-base=$HOME "$HOME/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Старые системы: использование readlink -e

readlinkтакже способен кононизировать путь, следуя как символическим ссылкам, так и ../:

readlink -e "$HOME/test" | grep -q "^$HOME" || exit 1

Используя те же файлы примеров:

$ readlink -e "$HOME/../../tmp/test" | grep "$HOME" || echo FAIL
FAIL
$ readlink -e "$HOME/test" | grep "^$HOME" || echo FAIL
FAIL

В дополнение к тому, что они доступны в более старых системах GNU, версии readlinkдоступны в BSD.

John1024
источник
Моя Ubuntu 14.04 coreutilsне имеетrealpath
heemayl
1
Кажется, это больше того, что я ищу, но, к сожалению, мой сервер Centos 6.5 не имеет реального пути. Я не в состоянии установить его. Googling вызывает упоминание о "readlink -f", заменяющем realpath, но я пока не заставил его работать.
Аарон Чикали
1
В подзаголовке упоминается -f(«все компоненты должны существовать, кроме последнего») и в примерах используется -e(«все компоненты должны существовать»), что немного сбивает с толку.
Исана
2
@AaronCicali Это определенно не тот ответ, который вам нужен, потому что это опасно неправильно. Вы не должны разрешать символические ссылки . Цель символической ссылки может изменяться между временем, когда вы проверяете его, и временем, когда вы его используете. (Это известная категория ошибок проектирования ). Кроме того, не имеет смысла разрешать символические ссылки, поскольку rmдействует сам аргумент, а не его цель.
Жиль "ТАК - перестань быть злым"
1
Подсказка: используйте, grep -qчтобы не grepвыводить совпадающие строки. Вы по-прежнему получаете статус выхода, &&и ||все равно работаете так, как привыкли.
CVn
2

Если вы хотите полностью запретить пути, самый простой способ - проверить, содержит ли переменная слеш ( /). В Баш:

if [[ "$1" = */* ]] ; then...

Это заблокирует все пути, в том числе foo/bar. Вы можете проверить ..вместо этого, но это оставит возможность символических ссылок, указывающих на каталоги вне целевого пути.

Если вы хотите разрешить удаление только одного файла, я не думаю, что вы должны использовать rm -r.


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

su charlesingalls -c "rm /home/charlesingalls/'$1'"

Хотя, как прокомментировал @Gilles, здесь есть проблема с кавычками: он потерпит неудачу, если будет $1содержать одинарную кавычку, поэтому сначала следует проверить переменную (например if [[ "$1" = *\'* ]] ; then fail..., с помощью белого списка разумного набора символов) или передать имя файла через него. переменная окружения с, например,

file="$1" su charlesingalls -c 'rm "/home/charlesingalls/$file"'
ilkkachu
источник
Хороший вопрос относительно флага -r, которого действительно не должно быть. Удаление сейчас ...
Аарон Чикали
Обратите внимание, что ваша suкоманда не работает, потому что цитирование неверно. Вы выполняете команду с аргументом, интерполированным как фрагмент оболочки. Например, если аргумент равен, $(touch foo)тогда ваш код выполняется touch foo.
Жиль "ТАК - перестань быть злым"
@ Жиль, дерьмо, я знал, что должно быть что-то не так. вложенные цитаты не мой любимый друг. Я думаю, что текущая версия работает лучше, двойные кавычки будут оценивать переменную во внешней оболочке, а одинарные кавычки удерживают ее от оценки во внутренней оболочке. Нет гарантии.
ilkkachu
@ilkkachu Эта версия не работает, если аргумент содержит одну кавычку. (Но только в этом случае, что делает проверку намного проще, чем при использовании двойных кавычек.) Невозможно напрямую интерполировать произвольную строку. Вам нужно либо помассировать строку (например, заменить все 'на '\''), либо пропустить ее через другой канал, такой как переменная окружения (что я и сделал бы здесь:) file_to_remove="$1" su -c 'rm "/home/charlesingalls/$file_to_remove"'. Получается, что имя файла должно быть именем виртуального хоста, поэтому здесь также можно отказаться от всех специальных символов.
Жиль "ТАК - перестань быть злым"