Привет^3

Давайте разберёмся, как символ - может создать реальные проблемы безопасности в автоматизации и скриптах.
Начнём с небольшой истории.

Ничего не предвещало беды

Была чудесная среда. Я пил вкусный кофе (настоятельно рекомендую инфьюз-обработку).
И понадобилось мне получить пути до файлов из командной строки.

Как это сделать?

Разбить строку по пробелу — кажется хорошей идеей.
Но вдруг я случайно нажму два пробела? Уже проблема.

В регулярных выражениях есть полезный метасимвол \s.
Он обозначает все пробельные символы: пробел, \t, \r, \f.

Звучит удобно.
Но дальше начинается интересное.

Мне нужно не только получить пути до файлов, но и открыть каждый из них.

И тут выясняется, что у команд есть флаги, которые начинаются с -.
А значит — такие аргументы интерпретируются не как файлы.

Чёртов дефис

Символ - может быть частью имени файла.
Более того — он может стоять в начале имени.

Например, файл с именем -file.

Create file with dash

Первая мысль обычно такая:

«Да всё просто, экранируем через \: touch \-file»

Escaping dash

Но нет — это не работает.

А если взять имя в кавычки?

Quoted dash file

Тоже мимо.

А как тогда?

Я нашёл два рабочих варианта:

  • использовать явный путь;
  • использовать специальную конструкцию --.

Разберём оба.

Полный (или относительный) путь

Во всех примерах выше мы передавали имя файла относительно текущей директории (cwd).

Если же указать путь явно (./-file или /path/to/-file), файл перестаёт интерпретироваться как флаг:

Relative path

То же самое работает и с абсолютным путём:

Absolute path

Специальная конструкция --

В bash есть специальная конструкция, обозначающая конец флагов--.

Все аргументы после -- больше не интерпретируются как опции:

Double dash

А зачем это вообще нужно?

Для повседневной работы это просто неудобство.
А вот в автоматизации и security-инструментах такие мелочи легко превращаются в проблемы.

Если неправильно обрабатывать аргументы, можно:

  • пропустить файл, имя которого начинается с -;
  • или сделать что-то намного хуже.

Например, такой код:

for f in $(ls); do
  rm $f
done

(да, for f in $(ls) — это отдельная боль, но сейчас не об этом)

Если в директории есть файл -rf, последствия очевидны.

Правильный вариант:

for f in $(ls); do
  rm -- "$f"
done

Если вы парсите аргументы без –, вы уже пишете потенциально небезопасный код.