Найдите уязвимость в bash-скрипте deploy.sh, где используется ssh user@host "rm -rf $DIR/*" при ненадёжной переменной DIR — объясните риск и как безопасно обрабатывать параметры и кавычки
Коротко — в чём уязвимость - Команда в скрипте: `ssh user@host "rm -rf $DIR/*"` опасна потому, что значение переменной `DIR` подставляется в команду, которая затем интерпретируется удалённой оболочкой. При пустой или некорректной `DIR` вы легко получите удаление корня (`rm -rf /*`) или возможность инъекции команд (символы `;`, `&`, `` ` ``, `$(...)` и т.п. в значении переменной могут изменить удалённую команду). Риски - Если `DIR` пустая → удалится `/*`. - Если `DIR` начинается с `-` → может быть интерпретировано как опция `rm` (решается `--`). - Если `DIR` контролируется извне → возможна удалённая команда-инъекция. Как безопасно — правила и примеры 1) Всегда проверяйте и валидируйте `DIR` перед удалением: - не пустая, не равна `/`, не указывает выше ожидаемой директории, подходит под ожидаемый шаблон. Пример проверок: ``` [ -n "$DIR" ] || { echo "DIR empty, abort"; exit 1; } case "$DIR" in /|/*) echo "Refusing to operate on root or absolute-wildcard"; exit 1;; esac ``` 2) Используйте `--` для прекращения разбора опций: ``` rm -rf -- "$DIR"/* ``` 3) Не подставляйте необработанную переменную напрямую в двойные кавычки SSH-команды. Надёжный шаблон — передать значение как аргумент в удалённый bash (через stdin), тогда значение не будет интерпретировано локальной/удалённой оболочкой: ``` ssh user@host 'bash -s' -- "$DIR" <<'BASH' DIR="$1" [ -n "$DIR" ] || { echo "DIR empty"; exit 1; } case "$DIR" in /) echo "Refuse root"; exit 1;; esac # удалить все (включая dotfiles) shopt -s dotglob 2>/dev/null || true rm -rf -- "$DIR"/* 2>/dev/null BASH ``` Объяснение: `"$DIR"` передаётся как позиционный аргумент `$1` в удалённый `bash`. Здесь вы можете безопасно валидировать и затем вызвать `rm -rf -- "$DIR"/*`. 4) Альтернатива — эскейпить значение для bash с `printf '%q'` (только если удалённая оболочка — bash): ``` esc=$(printf '%q' "$DIR") ssh user@host "rm -rf -- $esc/*" ``` (но этот метод требует аккуратности и совместимости; предпочтительнее передача как аргумента, см. пункт 3). 5) Лучше избегать `rm -rf` вообще, когда можно: - использовать rsync/ssh и удаление через find с явными проверками: `find "$DIR" -mindepth 1 -delete` - или удалить конкретные ожидаемые подкаталоги по списку. Краткие рекомендации - Проверяйте: не пусто, не `/`, находится в допустимой области. - Всегда использовать `--` перед путём. - Не выполняйте удаление на основе неэкранированных строк, поступивших извне; передавайте значения как аргументы удалённой программе (см. `ssh user@host 'bash -s' -- "$DIR"`). - По возможности избегайте прямой подстановки в командную строку — эскейпьте/валидируйте.
- Команда в скрипте: `ssh user@host "rm -rf $DIR/*"` опасна потому, что значение переменной `DIR` подставляется в команду, которая затем интерпретируется удалённой оболочкой. При пустой или некорректной `DIR` вы легко получите удаление корня (`rm -rf /*`) или возможность инъекции команд (символы `;`, `&`, `` ` ``, `$(...)` и т.п. в значении переменной могут изменить удалённую команду).
Риски
- Если `DIR` пустая → удалится `/*`.
- Если `DIR` начинается с `-` → может быть интерпретировано как опция `rm` (решается `--`).
- Если `DIR` контролируется извне → возможна удалённая команда-инъекция.
Как безопасно — правила и примеры
1) Всегда проверяйте и валидируйте `DIR` перед удалением:
- не пустая, не равна `/`, не указывает выше ожидаемой директории, подходит под ожидаемый шаблон.
Пример проверок:
```
[ -n "$DIR" ] || { echo "DIR empty, abort"; exit 1; }
case "$DIR" in
/|/*) echo "Refusing to operate on root or absolute-wildcard"; exit 1;;
esac
```
2) Используйте `--` для прекращения разбора опций:
```
rm -rf -- "$DIR"/*
```
3) Не подставляйте необработанную переменную напрямую в двойные кавычки SSH-команды. Надёжный шаблон — передать значение как аргумент в удалённый bash (через stdin), тогда значение не будет интерпретировано локальной/удалённой оболочкой:
```
ssh user@host 'bash -s' -- "$DIR" <<'BASH'
DIR="$1"
[ -n "$DIR" ] || { echo "DIR empty"; exit 1; }
case "$DIR" in /) echo "Refuse root"; exit 1;; esac
# удалить все (включая dotfiles)
shopt -s dotglob 2>/dev/null || true
rm -rf -- "$DIR"/* 2>/dev/null
BASH
```
Объяснение: `"$DIR"` передаётся как позиционный аргумент `$1` в удалённый `bash`. Здесь вы можете безопасно валидировать и затем вызвать `rm -rf -- "$DIR"/*`.
4) Альтернатива — эскейпить значение для bash с `printf '%q'` (только если удалённая оболочка — bash):
```
esc=$(printf '%q' "$DIR")
ssh user@host "rm -rf -- $esc/*"
```
(но этот метод требует аккуратности и совместимости; предпочтительнее передача как аргумента, см. пункт 3).
5) Лучше избегать `rm -rf` вообще, когда можно:
- использовать rsync/ssh и удаление через find с явными проверками: `find "$DIR" -mindepth 1 -delete`
- или удалить конкретные ожидаемые подкаталоги по списку.
Краткие рекомендации
- Проверяйте: не пусто, не `/`, находится в допустимой области.
- Всегда использовать `--` перед путём.
- Не выполняйте удаление на основе неэкранированных строк, поступивших извне; передавайте значения как аргументы удалённой программе (см. `ssh user@host 'bash -s' -- "$DIR"`).
- По возможности избегайте прямой подстановки в командную строку — эскейпьте/валидируйте.