0Pricing
DevOps Bootcamp · Lekcja

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 DevOps Bootcamp 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 DevOps Bootcamp, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DevOps Bootcamp 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):"
ls

Zawsze 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 $var bez 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):'
ls

Injection 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_dst

Injection 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} zamiast eval 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
fi

Walidacja 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'   # REJECTED

Bezpieczne 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_target

Oczyszczanie 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ą -v w psql lub --data-urlencode w curl.
  • Oddzielaj dane od kodu: używaj printf z 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 eval z niezaufanymi danymi; do pośredniego wyszukiwania używaj ${!var}.
  • Ograniczaj uprawnienia — uruchamiaj skrypty z minimalnym wymaganym poziomem uprawnień; unikaj sudo w 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 eval i bash -c "$input"; do bezpiecznego pośredniego rozwinięcia używaj ${!varname}.
  • Zawsze rozpoczynaj skrypty od set -euo pipefail i IFS=$'\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 DevOps Bootcamp, przejdź na CoddyKit PRO. Kurs DevOps Bootcamp 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 DevOps Bootcamp 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ąć DevOps Bootcamp?

Nie wymagamy żadnego doświadczenia. DevOps Bootcamp 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 DevOps Bootcamp?

Tak. Każda lekcja DevOps Bootcamp 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

  1. Zapobieganie wstrzykiwaniu poleceń i argumentów
  2. Bezpieczne obchodzenie się z sekretami i higiena środowiska
  3. Wykonywanie z minimalnymi uprawnieniami i dyscyplina sudo
  4. Analiza statyczna i audyt za pomocą ShellCheck
← Powrót do DevOps Bootcamp