0Pricing
Linux Command Line & Bash Scripting Mastery · Урок

Предотвращение внедрения команд и аргументов

Заключайте в кавычки, проверяйте и передавайте в массивах ненадёжные входные данные, устраняя разбиение слов и внедрение через eval

«Предотвращение внедрения команд и аргументов» — бесплатный урок Linux Command Line & Bash Scripting Mastery на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Linux Command Line & Bash Scripting Mastery, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Linux Command Line & Bash Scripting Mastery содержит 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) и разблокировать остальной курс Linux Command Line & Bash Scripting Mastery, подпишись на CoddyKit PRO. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.

Чему я научусь в уроке «Предотвращение внедрения команд и аргументов»?

Заключайте в кавычки, проверяйте и передавайте в массивах ненадёжные входные данные, устраняя разбиение слов и внедрение через eval Ты практикуешь Linux Command Line & Bash Scripting Mastery с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Linux Command Line & Bash Scripting Mastery?

Предыдущий опыт не требуется. Linux Command Line & Bash Scripting Mastery на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Предотвращение внедрения команд и аргументов»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Linux Command Line & Bash Scripting Mastery?

Да. Каждый урок Linux Command Line & Bash Scripting Mastery включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Предотвращение внедрения команд и аргументов
  2. Безопасная работа с секретами и чистота окружения
  3. Выполнение с минимальными привилегиями и дисциплина sudo
  4. Статический анализ и аудит с ShellCheck
← Назад к Linux Command Line & Bash Scripting Mastery