Zapobieganie wstrzykiwaniu poleceń i argumentów
Cytuj, weryfikuj i przekazuj niezaufane dane wejściowe jako tablice, aby wyeliminować dzielenie słów i wstrzykiwanie oparte na eval.
Zapobieganie wstrzykiwaniu poleceń i argumentów to bezpłatna lekcja Linux Command Line & Bash Scripting Mastery na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Linux Command Line & Bash Scripting Mastery, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.
Dlaczego w Bashu dochodzi do ataków typu injection
Bash to potężny język klejący — przekazuje tekst bezpośrednio do jądra, innych programów i podpowłok. Ta zaleta staje się zagrożeniem, gdy tylko niezaufane dane wejściowe trafią do polecenia bez walidacji lub cudzysłowów.
Za niemal każdym atakiem typu injection w Bashu stoją dwie przyczyny:
- Podział na słowa: nieujęte w cudzysłowy zmienne są dzielone według białych znaków (
IFS), przez co jedna logiczna wartość zamienia się w wiele tokenów powłoki. - Rozwijanie wzorców: znaki takie jak
*,?i[są rozwijane przez powłokę, zanim polecenie w ogóle zostanie wykonane.
Osoba atakująca, która kontroluje nazwę pliku, nazwę użytkownika, parametr URL lub zmienną środowiskową, może wykorzystać oba mechanizmy do uruchamiania dowolnych poleceń, odczytywania plików lub eskalacji uprawnień.
W tej lekcji pokazano dokładnie, jak powstają te luki oraz — co ważniejsze — jak je eliminować za pomocą poprawnego cytowania, walidacji danych wejściowych i przekazywania argumentów w tablicach.
Podział na słowa: ukryte zagrożenie
Gdy Bash napotyka zmienną bez cudzysłowów, dzieli jej wartość przy każdym znaku wymienionym w $IFS (domyślnie: spacja, tabulator, znak nowej linii). To, co wygląda jak jeden argument, staje się wieloma.
Uruchom poniższy skrypt i zobacz, jak nazwa pliku zawierająca spację zamienia się w dwa osobne argumenty polecenia 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):"
lsZawsze używaj cudzysłowów: pierwsza zasada bezpiecznego Basha
Najprostszą i najskuteczniejszą ochroną przed podziałem na słowa jest zawsze ujmowanie rozwinięć zmiennych w podwójne cudzysłowy.
"$var"— rozwija się dokładnie do jednego tokenu, zachowując spacje, tabulatory i znaki nowej linii.'literal'— pojedyncze cudzysłowy: brak jakichkolwiek rozwinięć, przydatne dla stałych ciągów znaków.- Nigdy nie używaj
$varbez cudzysłowów, chyba że jawnie potrzebujesz podziału na słowa i rozwijania wzorców.
Poniższy skrypt pokazuje bezpieczną wersję poprzedniego przykładu.
#!/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):'
lsInjection za pomocą wzorców: gdy * staje się bronią
Zmienne bez cudzysłowów podlegają również rozwijaniu nazw ścieżek (globbingowi). Jeśli dane wejściowe kontrolowane przez użytkownika zawierają * lub ?, Bash rozwija je względem systemu plików, zanim polecenie zostanie wykonane.
Klasyczny wektor ataku: formularz internetowy ustawia PATTERN=*, a skrypt uruchamia cp $PATTERN /tmp/leak/ — kopiując każdy plik z bieżącego katalogu.
Rozwiązanie jest takie samo: ujmij zmienną w podwójne cudzysłowy. Ujęte w cudzysłowy "$PATTERN" jest przekazywane dosłownie; powłoka nie rozwija w nim wzorców.
#!/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_dstInjection argumentów przez nieujęte w cudzysłowy parametry pozycyjne
Skrypty przyjmujące argumenty od wywołującego są szczególnie narażone na ataki typu injection. Każdy parametr pozycyjny ($1, $2, ...) musi być ujmowany w cudzysłowy w każdym miejscu użycia.
Szczególnie niebezpieczny jest wzorzec polegający na przekazywaniu $@ lub $* bez cudzysłowów do innego polecenia:
"$@"— rozwija każdy parametr pozycyjny do osobnego, niezależnie ujętego w cudzysłowy słowa. Zawsze używaj tej postaci.$@lub$*bez cudzysłowów — podlegają podziałowi na słowa i rozwijaniu wzorców."$*"— łączy wszystkie parametry w jedno słowo (rzadko jest to pożądane).
#!/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"Injection poleceń przez eval i niezwalidowane dane wejściowe
eval ponownie interpretuje swój argument jako kod powłoki. Każde niezaufane dane trafiające do eval mogą uruchomić dowolne polecenia.
Typowe niebezpieczne wzorce:
eval "$user_input"eval echo \$$var(pośrednie wyszukiwanie zmiennej)- Przekazywanie danych użytkownika przez
bash -c "$input"
Zasada: nigdy nie przekazuj niezaufanych danych do eval ani bash -c. Używaj bezpiecznych alternatyw Basha:
- Pośrednie rozwinięcie:
${!varname}zamiasteval echo \$$varname - Tablice asocjacyjne do dynamicznego wyszukiwania par klucz-wartość
- Funkcje zamiast generowanych ciągów poleceń
#!/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
fiWalidacja danych wejściowych: listy dozwolonych zamiast list wykluczonych
Odrzucanie znanych niedozwolonych znaków (lista wykluczonych) jest zawodne — osoby atakujące znajdują kodowania lub znaki, o których nie pomyślano. Zamiast tego stosuj listę dozwolonych: akceptuj wyłącznie znaki, o których wiadomo, że są bezpieczne.
Strategie stosowania list dozwolonych w Bashu:
- Dopasowanie wyrażenia regularnego:
[[ "$input" =~ ^[A-Za-z0-9_-]+$ ]] - Dopasowanie wzorca:
case "$input" in [A-Za-z0-9]*) ... ;; esac - Sprawdzenie wyliczenia: porównanie ze stałym zestawem prawidłowych wartości
Przeprowadzaj walidację na granicy — natychmiast po wejściu danych do skryptu — zanim trafią do jakiegokolwiek polecenia.
#!/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' # REJECTEDBezpieczne przekazywanie argumentów za pomocą tablic
Gdy trzeba dynamicznie zbudować polecenie — warunkowo dodając flagi lub iterując po danych wejściowych — należy użyć tablicy Basha zamiast łączyć ciągi znaków.
Łączenie ciągów usuwa całą strukturę; tablica Basha zachowuje każdy argument jako osobny element, bez ponownego interpretowania przez powłokę.
- Deklarowanie:
args=() - Dodawanie:
args+=(--flag "$value") - Wykonanie:
command "${args[@]}"
"${args[@]}" rozwija każdy element jako osobne, niezależnie ujęte w cudzysłowy słowo — dokładnie tak jak "$@".
#!/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[@]}"Separator --: ochrona przed wstrzyknięciem flag
Nawet poprawnie ujęty w cudzysłowy argument może zostać błędnie zinterpretowany jako flaga opcji, jeśli zaczyna się od -. Rozważ rm "$file", gdy file='-rf .': cudzysłowy chronią przed podziałem na słowa, ale rm nadal interpretuje -rf jako flagi.
Konwencja POSIX -- sygnalizuje większości narzędzi GNU/BSD koniec opcji. Wszystko po -- jest traktowane jako argument pozycyjny, a nie flaga.
#!/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_targetOczyszczanie danych wejściowych na potrzeby SQL i narzędzi zewnętrznych
Gdy skrypty Basha wywołują klienckie narzędzia baz danych (psql, mysql), curl z adresami URL podanymi przez użytkownika lub podobne narzędzia, obowiązują dwie dodatkowe warstwy ochrony:
- Zapytania parametryzowane: nigdy nie wstawiaj danych użytkownika bezpośrednio do ciągów SQL. Przekazuj wartości za pomocą
-vwpsqllub--data-urlencodewcurl. - Oddzielaj dane od kodu: używaj
printfz dosłownym ciągiem formatu; nigdy nie pozwalaj, aby ciąg formatu pochodził od użytkownika.
Poniższy przykład bezpiecznie wykonuje zapytanie w PostgreSQL, całkowicie oddzielając wartość podaną przez użytkownika od tekstu 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"Lista kontrolna wzmacniania zabezpieczeń: połączenie wszystkich elementów
Bezpieczny skrypt Bash gotowy do użycia w środowisku produkcyjnym łączy wszystkie techniki z tej lekcji w spójną, wielowarstwową ochronę. Oto minimalny, ale kompletny wzmocniony szablon:
set -euo pipefail— kończ działanie po błędzie, traktuj niezainicjowane zmienne jako błędy i przekazuj błędy potoków dalej.- Waliduj przy wejściu — stosuj listę dozwolonych dla każdego zewnętrznego danych wejściowych, zanim trafią do jakiegokolwiek polecenia.
- Używaj cudzysłowów wszędzie —
"$var","$@","${array[@]}"— bez wyjątków, chyba że potrzebujesz podziału na słowa. - Używaj tablic do dynamicznego konstruowania poleceń.
- Poprzedzaj argumenty przez
--, gdy przekazujesz nazwy plików lub ciągi znaków podane przez użytkownika. - Nigdy nie używaj
evalz niezaufanymi danymi; do pośredniego wyszukiwania używaj${!var}. - Ograniczaj uprawnienia — uruchamiaj skrypty z minimalnym wymaganym poziomem uprawnień; unikaj
sudow skryptach przyjmujących dane użytkownika.
#!/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[@]}"Sprawdzenie wiedzy: cudzysłowy i zapobieganie injection
Sprawdź swoją znajomość najważniejszych pojęć z tej lekcji.
Podsumowanie lekcji: zapobieganie injection poleceń i argumentów
Omówiono kompletny zestaw technik bezpiecznej obsługi danych wejściowych w Bashu:
- Podział na słowa i rozwijanie wzorców to podstawowe mechanizmy, które zamieniają niebezpieczne zmienne w wektory ataku typu injection.
- Ujmuj każdą zmienną w podwójne cudzysłowy (
"$var","$@","${arr[@]}"), aby zablokować oba zagrożenia. - Używaj
"$@"— nigdy$@ani$*bez cudzysłowów — podczas przekazywania argumentów dalej. - Poprzedzaj nazwy plików podane przez użytkownika przez
--, aby zapobiec wstrzyknięciu flag. - Stosuj listę dozwolonych dla wszystkich zewnętrznych danych wejściowych, używając kontroli za pomocą wyrażenia regularnego (
[[ $v =~ ^pattern$ ]]), zanim dane trafią do jakiegokolwiek polecenia. - Buduj dynamiczne polecenia za pomocą tablic (
cmd+=()→"${cmd[@]}"), nigdy przez łączenie ciągów. - Eliminuj
evalibash -c "$input"; do bezpiecznego pośredniego rozwinięcia używaj${!varname}. - Zawsze rozpoczynaj skrypty od
set -euo pipefailiIFS=$'\n\t', aby zapewnić podstawowy poziom wzmocnionej ochrony.
Stosowanie tych praktyk konsekwentnie, od pierwszej linii każdego skryptu, niemal całkowicie ogranicza powierzchnię ataku Basha w przypadku podatności na injection.
Często zadawane pytania
Czy lekcja „Zapobieganie wstrzykiwaniu poleceń i argumentów” jest bezpłatna?
Tak — pełny tekst „Zapobieganie wstrzykiwaniu poleceń i argumentów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Linux Command Line & Bash Scripting Mastery, przejdź na CoddyKit PRO. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.
Co nauczysz się w „Zapobieganie wstrzykiwaniu poleceń i argumentów”?
Cytuj, weryfikuj i przekazuj niezaufane dane wejściowe jako tablice, aby wyeliminować dzielenie słów i wstrzykiwanie oparte na eval. Ćwiczysz Linux Command Line & Bash Scripting Mastery z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Linux Command Line & Bash Scripting Mastery?
Nie wymagamy żadnego doświadczenia. Linux Command Line & Bash Scripting Mastery w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Zapobieganie wstrzykiwaniu poleceń i argumentów”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Linux Command Line & Bash Scripting Mastery?
Tak. Każda lekcja Linux Command Line & Bash Scripting Mastery zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Zapobieganie wstrzykiwaniu poleceń i argumentów
- Bezpieczne obchodzenie się z sekretami i higiena środowiska
- Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo
- Analiza statyczna i audyt za pomocą ShellCheck