Предотвращение внедрения команд и аргументов
Заключайте в кавычки, проверяйте и передавайте в массивах ненадёжные входные данные, устраняя разбиение слов и внедрение через eval
«Предотвращение внедрения команд и аргументов» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.
Почему в Bash происходят атаки с внедрением
Bash — мощный язык для связывания компонентов: он передаёт текст непосредственно ядру, другим программам и дочерним оболочкам. Эта мощь становится уязвимостью, как только непроверенные данные попадают в команду без проверки или экранирования.
Почти за каждым внедрением в Bash стоят две основные причины:
- Разбиение на слова: переменные без кавычек разделяются по пробельным символам (
IFS), превращая одно логическое значение в несколько токенов оболочки. - Раскрытие шаблонов: такие символы, как
*,?и[, раскрываются оболочкой ещё до запуска команды.
Злоумышленник, контролирующий имя файла, имя пользователя, параметр URL или переменную окружения, может использовать оба механизма для выполнения произвольных команд, чтения файлов или повышения привилегий.
В этом уроке показано, как именно возникают такие уязвимости и, что ещё важнее, как устранить их с помощью правильного заключения в кавычки, проверки вводимых данных и передачи аргументов на основе массивов.
Разбиение на слова: скрытая угроза
Когда Bash встречает переменную без кавычек, он разделяет её значение по любому символу, указанному в $IFS (по умолчанию: пробел, табуляция, перевод строки). То, что выглядит как один аргумент, превращается в несколько.
Запустите приведённый ниже скрипт и посмотрите, как имя файла с пробелом превращается в два отдельных аргумента команды rm.
#!/usr/bin/env bash
# Dangerous: unquoted variable
FILE='important file.txt'
# Create the file so the demo is self-contained
touch "$FILE"
echo "Files before:"
ls
# BUG: rm sees TWO arguments: 'important' and 'file.txt'
# If 'important' does not exist, rm prints an error but continues.
rm $FILE # <-- unquoted, word-split happens here
echo "Files after (unquoted rm):"
lsВсегда используйте кавычки: первое правило безопасного Bash-кода
Самая простая и эффективная защита от разбиения на слова — всегда заключать раскрытие переменных в двойные кавычки.
"$var"— раскрывается ровно в один токен, сохраняя пробелы, табуляции и переводы строк.'literal'— одинарные кавычки: раскрытие не выполняется вообще, поэтому они удобны для фиксированных строк.- Никогда не используйте
$varбез кавычек, если только Вам явно не нужны разбиение на слова и раскрытие шаблонов.
В приведённом ниже скрипте показан безопасный вариант предыдущего примера.
#!/usr/bin/env bash
set -euo pipefail
FILE='important file.txt'
touch "$FILE"
echo 'Files before:'
ls
# SAFE: double-quotes keep the filename as one token
rm "$FILE"
echo 'Files after (quoted rm):'
lsВнедрение через шаблоны: когда * становится оружием
Переменные без кавычек также подвержены раскрытию имён путей (раскрытию шаблонов). Если ввод, контролируемый пользователем, содержит * или ?, Bash раскрывает его относительно файловой системы до запуска команды.
Классический вектор атаки: веб-форма задаёт PATTERN=*, а скрипт выполняет cp $PATTERN /tmp/leak/, копируя все файлы из текущего каталога.
Исправление то же: заключите переменную в двойные кавычки. Заключённое в кавычки "$PATTERN" передаётся буквально, и оболочка не раскрывает в нём шаблоны.
#!/usr/bin/env bash
set -euo pipefail
# Simulate attacker-supplied input
PATTERN='*'
mkdir -p /tmp/safe_demo_src /tmp/safe_demo_dst
touch /tmp/safe_demo_src/secret1.txt /tmp/safe_demo_src/secret2.txt
cd /tmp/safe_demo_src
# UNSAFE: glob expands, copies every file
# cp $PATTERN /tmp/safe_demo_dst/
# SAFE: pattern is treated as a literal filename
cp "$PATTERN" /tmp/safe_demo_dst/ 2>&1 || echo 'No file named literally "*" — attack neutralised'
rm -rf /tmp/safe_demo_src /tmp/safe_demo_dstВнедрение аргументов через позиционные параметры без кавычек
Скрипты, принимающие аргументы от вызывающей стороны, являются основной целью атак с внедрением. Каждый позиционный параметр ($1, $2, ...) необходимо заключать в кавычки везде, где он используется.
Особенно опасно передавать $@ или $* без кавычек другой команде:
"$@"— раскрывает каждый позиционный параметр как отдельное слово, заключённое в собственные кавычки. Всегда используйте эту форму.$@или$*без кавычек — подвержены разбиению на слова и раскрытию шаблонов."$*"— объединяет все параметры в одно слово (обычно это не то, что Вам нужно).
#!/usr/bin/env bash
set -euo pipefail
# Safe wrapper: forward all arguments quoted
grep_wrapper() {
local pattern="$1"
shift
# "$@" preserves each file argument as one token
grep -rn "$pattern" "$@"
}
# Usage: ./script 'error msg' /var/log/syslog '/path with spaces/app.log'
echo 'Searching current script for "safe":'
grep_wrapper 'safe' "$0"Внедрение команд через eval и непроверенные данные
eval повторно разбирает свой аргумент как код оболочки. Любые непроверенные данные, попавшие в eval, могут выполнить произвольные команды.
Распространённые опасные конструкции:
eval "$user_input"eval echo \$$var(косвенный поиск переменной)- Передача пользовательских данных через
bash -c "$input"
Правило: никогда не передавайте непроверенные данные в eval или bash -c. Используйте безопасные альтернативы Bash:
- Косвенное раскрытие:
${!varname}вместоeval echo \$$varname - Ассоциативные массивы для динамического поиска по парам «ключ—значение»
- Функции вместо сгенерированных строк команд
#!/usr/bin/env bash
set -euo pipefail
# Simulated attacker-supplied variable name
VARNAME='PATH; echo INJECTED'
# UNSAFE: eval lets attacker run 'echo INJECTED'
# eval "echo \$$VARNAME"
# SAFE: indirect expansion only resolves valid variable names
# First validate that VARNAME is a legal identifier
if [[ "$VARNAME" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
echo "Value: ${!VARNAME}"
else
echo "ERROR: invalid variable name: '$VARNAME'" >&2
exit 1
fiПроверка вводимых данных: разрешённые списки вместо запрещённых
Отклонение известных опасных символов (по запрещённому списку) ненадёжно: злоумышленники найдут кодировки или символы, которые Вы не учли. Вместо этого используйте разрешённый список: принимайте только известные безопасные символы.
Стратегии использования разрешённых списков в Bash:
- Проверка регулярным выражением:
[[ "$input" =~ ^[A-Za-z0-9_-]+$ ]] - Проверка по шаблону:
case "$input" in [A-Za-z0-9]*) ... ;; esac - Проверка перечислением: сравнение с фиксированным набором допустимых значений
Проверяйте данные на границе — сразу после их поступления в скрипт, до передачи любой команде.
#!/usr/bin/env bash
set -euo pipefail
validate_username() {
local name="$1"
# Allowlist: only lowercase letters, digits, underscore, hyphen; 1-32 chars
if [[ ! "$name" =~ ^[a-z0-9_-]{1,32}$ ]]; then
echo "ERROR: invalid username '${name}'" >&2
return 1
fi
echo "Username accepted: $name"
}
validate_username 'alice' # OK
validate_username 'bob_smith-2' # OK
validate_username 'root; rm -rf /' # REJECTED
validate_username '../etc/passwd' # REJECTEDБезопасная передача аргументов с помощью массивов
Когда нужно динамически сформировать команду — например, условно добавить флаги или перебрать вводимые значения, — используйте массив Bash вместо объединения строк.
Объединение строк уничтожает структуру, а массив Bash сохраняет каждый аргумент как отдельный элемент без повторного разбора оболочкой.
- Объявление:
args=() - Добавление:
args+=(--flag "$value") - Выполнение:
command "${args[@]}"
"${args[@]}" раскрывает каждый элемент как отдельное слово с индивидуальным заключением в кавычки — точно так же, как "$@".
#!/usr/bin/env bash
set -euo pipefail
# Build a find command safely with an array
BASEDIR='/tmp'
USER_PATTERN='*.log' # could come from user input (validate first!)
MAX_DAYS=7
cmd=(find "$BASEDIR" -type f -name "$USER_PATTERN" -mtime "+${MAX_DAYS}")
# Optionally add -delete only when requested
DELETE=false
if [[ "$DELETE" == 'true' ]]; then
cmd+=(-delete)
fi
echo "Running: ${cmd[*]}"
"${cmd[@]}"Разделитель --: защита от внедрения флагов
Даже правильно заключённый в кавычки аргумент может быть ошибочно принят за флаг параметра, если начинается с -. Рассмотрим rm "$file", где file='-rf .': кавычки защищают от разбиения на слова, но rm всё равно интерпретирует -rf как флаги.
Соглашение POSIX -- сообщает большинству утилит GNU/BSD, что наступил конец параметров. Всё после -- рассматривается как позиционный аргумент, а не как флаг.
#!/usr/bin/env bash
set -euo pipefail
# Simulate a dangerous filename supplied by the user
FILENAME='-rf /tmp/safe_demo_target'
mkdir -p /tmp/safe_demo_target
touch /tmp/safe_demo_target/keep_me.txt
echo 'Files before:'
ls /tmp/safe_demo_target/
# UNSAFE (flag injection — do NOT uncomment in real systems):
# rm "$FILENAME"
# SAFE: -- ends option processing; filename is treated literally
rm -- "$FILENAME" 2>&1 || echo "No such file (attack neutralised): $FILENAME"
echo 'Files after:'
ls /tmp/safe_demo_target/
rm -rf /tmp/safe_demo_targetОчистка вводимых данных для SQL и внешних инструментов
Когда скрипты Bash вызывают интерфейсы командной строки баз данных (psql, mysql), curl с URL, предоставленными пользователем, или подобные инструменты, применяются два дополнительных уровня защиты:
- Параметризованные запросы: никогда не вставляйте пользовательские данные в строки SQL. Передавайте значения через
-vвpsqlили через--data-urlencodeвcurl. - Разделяйте данные и код: используйте
printfс буквальным форматом; никогда не позволяйте пользовательскому вводу быть строкой формата.
В приведённом ниже примере запрос к PostgreSQL выполняется безопасно: значение, предоставленное пользователем, полностью исключено из текста SQL.
#!/usr/bin/env bash
set -euo pipefail
# Validate first: only allow alphanumeric usernames
USERNAME="${1:-alice}"
if [[ ! "$USERNAME" =~ ^[a-z0-9_]{1,32}$ ]]; then
echo 'ERROR: invalid username' >&2
exit 1
fi
# UNSAFE:
# psql -c "SELECT * FROM users WHERE name = '$USERNAME';"
# SAFE: pass value as a psql variable, never inside the SQL text
# psql -v username="$USERNAME" -c 'SELECT * FROM users WHERE name = :username;'
# Demonstrate the principle with printf (safe format string usage)
printf 'Query would use username: %s\n' "$USERNAME"Контрольный список усиления защиты: объединяем всё вместе
Безопасный скрипт Bash производственного уровня объединяет все методы из этого урока в согласованную многоуровневую защиту. Ниже приведён минимальный, но полный шаблон усиленного скрипта:
set -euo pipefail— завершать работу при ошибке, считать неинициализированные переменные ошибками и передавать сбои конвейера.- Проверяйте при входе — проверяйте по разрешённому списку каждый внешний ввод до его передачи любой команде.
- Заключайте в кавычки всё —
"$var","$@","${array[@]}"— без исключений, кроме случаев, когда Вам нужно разбиение. - Используйте массивы для динамического построения команд.
- Добавляйте
--перед аргументами, когда передаёте имена файлов или строки, предоставленные пользователем. - Никогда не используйте
evalс непроверенными данными; для косвенного поиска предпочитайте${!var}. - Ограничивайте права — запускайте скрипты с минимально необходимыми привилегиями; избегайте
sudoв скриптах, принимающих пользовательский ввод.
#!/usr/bin/env bash
# Hardened template — safe argument injection prevention
set -euo pipefail
IFS=$'\n\t'
#--- 1. Validate inputs at the boundary ---
SEARCH_DIR="${1:-}"
PATTERN="${2:-}"
[[ -z "$SEARCH_DIR" || -z "$PATTERN" ]] && { echo 'Usage: script <dir> <pattern>' >&2; exit 1; }
[[ ! "$SEARCH_DIR" =~ ^[A-Za-z0-9/_.-]+$ ]] && { echo 'ERROR: unsafe directory path' >&2; exit 1; }
[[ ! "$PATTERN" =~ ^[A-Za-z0-9._-]+$ ]] && { echo 'ERROR: unsafe pattern' >&2; exit 1; }
#--- 2. Build command with an array ---
cmd=(find -- "$SEARCH_DIR" -type f -name "$PATTERN")
#--- 3. Execute — no string interpolation, no eval ---
echo "Executing: ${cmd[*]}"
"${cmd[@]}"Проверка знаний: кавычки и предотвращение внедрения
Проверьте своё понимание ключевых понятий этого урока.
Итоги урока: предотвращение внедрения команд и аргументов
Вы изучили полный набор защитных средств для безопасной обработки ввода в Bash:
- Разбиение на слова и раскрытие шаблонов — основные механизмы, превращающие небезопасные переменные в векторы внедрения.
- Заключайте каждую переменную в двойные кавычки (
"$var","$@","${arr[@]}"), чтобы подавить обе угрозы. - Используйте
"$@"— никогда не используйте без кавычек$@или$*— при передаче аргументов дальше. - Добавляйте
--перед предоставленными пользователем именами файлов, чтобы предотвратить внедрение флагов. - Проверяйте весь внешний ввод по разрешённому списку с помощью регулярного выражения (
[[ $v =~ ^pattern$ ]]) до его передачи любой команде. - Создавайте динамические команды с помощью массивов (
cmd+=()→"${cmd[@]}"), а не объединением строк. - Исключите
evalиbash -c "$input"; используйте${!varname}для безопасного косвенного раскрытия. - Всегда начинайте скрипты с
set -euo pipefailиIFS=$'\n\t', чтобы обеспечить базовый усиленный уровень защиты.
Последовательное применение этих правил с первой строки каждого скрипта почти сводит к нулю поверхность атак Bash для уязвимостей класса внедрения.
Часто задаваемые вопросы
Урок «Предотвращение внедрения команд и аргументов» бесплатный?
Да — полный текст урока «Предотвращение внедрения команд и аргументов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.
Чему я научусь в уроке «Предотвращение внедрения команд и аргументов»?
Заключайте в кавычки, проверяйте и передавайте в массивах ненадёжные входные данные, устраняя разбиение слов и внедрение через eval Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать DevOps Bootcamp?
Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Предотвращение внедрения команд и аргументов»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке DevOps Bootcamp?
Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Предотвращение внедрения команд и аргументов
- Безопасная работа с секретами и чистота окружения
- Выполнение с минимальными привилегиями и дисциплина sudo
- Статический анализ и аудит с ShellCheck