Hlavní navigace

Zranitelnosti typu injekce: vložení systémového příkazu

David Formánek

V druhém díle seriálu se podíváme na injekci příkazu, která často umožní kompletní převzetí zařízení či serveru se zranitelnou aplikací. Představíme princip chyby a ukážeme různé možnosti útoku i obrany.

Doba čtení: 9 minut

V prvním díle seriálu jsme se zabývali SQL injekcí, což je jistě jedna z nejznámějších bezpečnostních chyb. Ještě horší důsledky ale mívá tzv. injekce příkazu (OS Command Injection). Chyba tohoto typu se nevyskytuje v aplikacích tak často, protože podmínkou zneužití je volání externích příkazů operačního systému, což není tak běžné jako přístup k relační databázi.

Bohužel slabší je i povědomí o zranitelnosti a podpora bezpečnostních nástrojů. Naopak zneužití chyby představuje obrovské riziko, protože útočník může spouštět libovolné příkazy systému a zranitelnou aplikaci používat jako příkazový řádek. To může být problém např. i na lokálním stroji, pokud aplikace běží s vyššími uživatelskými právy, než má její uživatel. Jakou moc tím útočník získá, pak závisí na tom, jak jsou ctěny obecnější bezpečnostní principy jako spouštění aplikací s nejnižšími možnými privilegii a vzájemné oddělení jednotlivých systémů (viz konec článku).

Na rozdíl od webových aplikací je ale volání externích příkazů běžné např. v routerech. Naposledy měly problém směrovače D-Link. Samotné zneužití chyby sice vyžadovalo přihlášení, bohužel jiná chyba zároveň umožňovala snadno získat heslo, což je opravdu silná kombinace. Jiné chyby stejného typu se přitom za poslední rok v routerech firmy našly už dvakrát předtím.

Podobně jihokorejský výrobce Dasan umožnil injekci při volání příkazu ping a zároveň obejití autentizace pouhým přidáním řetězce ?images/ na konec URL. Dále byl zranitelný také speciální bezpečnostní router od Norton, který měl chránit i ostatní zařízení v síti, přitom ale umožnil spustit libovolný příkaz komukoliv na dosah Bluetooth. Za poslední rok měly jinak problém s injekcí příkazu minimálně ještě zařízení firem ASUS, Cisco a Linksys. Neznamená to samozřejmě, že by se chyba vyskytovala pouze v routerech, zranitelnost se nedávno opravila např. v jazyce Python a ani na webu není úplnou vzácností.

Přímá injekce příkazu

Ne na každý typ zranitelnosti bohužel existuje pěkný komiks, takže injekci příkazu zkusíme vysvětlit na vlastním příkladu. Předpokládejme, že by někdo chtěl vytvořit malou konkurenci službě VirusTotal a pro začátek by aplikace měla umožnit analýzu zadané URL pomocí volně dostupného antiviru ClamAV a jeho výstup vypsat na webovou stránku.

Protože ale software nenabízí žádné API dostupné v použitém programovacím jazyku, je nutné zavolat příkaz operačního systému. Toho lze dosáhnout s použitím funkcí jako system (jazyk C) či call (Python) nebo metod Runtime.exec (Java) či Process.Start (C#). Autor aplikace také nebude chtít soubor na dané adrese zbytečně ukládat a rozhodne se použít zřetězený příkaz:

"wget -qO - " + url + " | clamscan -"

Ten stáhne soubor na URL zadané uživatelem a přímo pošle analýze antiviru jako vstup. Útočník ale může místo samotné URL zadat řetězec obsahující oddělovače příkazu a místo pouhé specifikace parametru vložit i libovolné další příkazy. Například sekvence cokoliv; ls -l # způsobí interpretaci řádku:

wget -qO - cokoliv; ls -l # | clamscan -

Místo stažení souboru a analýzy se spustí příkaz ls. Příkaz wget se sice zavolá, ale pro „cokoli“ skončí tichou chybou. Příkaz clamscan se nezavolá, protože bude interpretován jako komentář (za znakem #). V dalším požadavku může zavolat útočník např. příkaz cat, narazí-li na citlivé soubory, případně může soubory přepsat či rovnou smazat, kde mu to práva dovolí. Zranitelné mohou být samozřejmě i aplikace na platformě Windows, jen na oddělení příkazů použije útočník třeba znaky  &&.

I pokud by aplikace nevypisovala výstup příkazů přímo na stránku (ale např. jen parsovala výsledek analýzy), existuje řada technik, jak lze data ze serveru vyextrahovat. Nejrychlejší je přímo odeslat data jiným kanálem přes síť. Někdy je výhodné data ještě zkomprimovat (aby se přenášelo méně dat), zašifrovat (aby nešlo poznat, co se vlastně posílá) a zakódovat např. v base 64 (aby bylo možné data vynést i s použitím textových protokolů). Konkrétních možností, jak data vynést, je mnoho:

  • Asi nejjednodušší je znovu využít příkaz wget pro dotaz na útočníkův server, kde kradené informace budou skryté v parametru požadavku nebo v některé z HTTP hlaviček.
  • Nemusíme se omezovat na protokol HTTP, podobně se dají použít nástroje jako curl a nc (NetCat) či na Windows telnetnet use.
  • Pokud tyto příkazy nejsou k dispozici či z nějakého důvodu nestačí, může útočník využít interpreterů a plné síly skriptovacích jazyků jako Python, Perl či Ruby, jsou-li na serveru zranitelné aplikace dostupné.
  • Data lze také rozdělit na části a vyzradit skrz protokol ICMP příkazem ping s parametrem -p nebo přes DNS dotaz (příkaz host), kde doména může být klidně ukradené heslo nebo část libovolných zakódovaných dat.
  • V případě webové aplikace může být nejjednodušší data vystavit přímo na webovou stránku (např. jako statický soubor s náhodným jménem), odkud si je útočník stáhne jako návštěvník webu. Podobně může soubor uložit mezi dočasné soubory či do jiné sdílené složky, kde bude schopen data vyzvednout.

Další možnosti útoku

Neexistuje-li žádný takový přímý komunikační kanál, pořád lze využít „slepých“ technik podobně jako u SQL injekce a data vynášet bit po bitu. V minulém článku jsme popsali extrakci dat pomocí injekce v přihlašovacím formuláři, kde úspěšné přihlášení bylo použito jen jako binární indikátor toho, že se v databázi nachází hodnota určitého formátu.

Uvažme, kdyby v našem příkladu na analýzu URL nebyl vypsán na stránku celý výstup volaných příkazů, ale webová aplikace ve výstupu jen hledala klíčová slova a podle toho vrátila informaci, zda je soubor pravděpodobně infikovaný či neškodný. Pak útočník může přes injekci vložit vlastní sérii příkazu, která vyextrahuje citlivá data a určí pouze hodnotu prvního bitu hledané informace. V případě bitu 0 (resp. 1) pak vypíše text imitující výstup antivirového programu pro infikovaný (resp. neškodný) soubor, který následně aplikace zpracuje pro zobrazení výsledku na webové stránce.

Následnými požadavky určí hodnoty druhého a dalších bitů, až se mu z posloupnosti odpovědí infikovaný / neškodný soubor podaří zrekonstruovat veškerá požadovaná data. Pokud je samotná extrakce dat netriviální, může si informace jako mezivýsledek uložit do dočasného souboru a následnými dotazy už pouze číst tento soubor.

Univerzálnější řešení je podmíněné vložení zpoždění přes příkaz sleep (případně jiný, u kterého dokážeme určit, jak dlouho se bude vykonávat). Útočník pak odhadne hodnotu bitu podle toho, jak rychle se stránka načte. Je vhodné velikost zpoždění zvolit podle připojení tak, aby byl rozdíl v rychlosti odpovědi zřetelný i v případě náhodného zdržení na síti, ale zároveň útok netrval zbytečně dlouho.

Pro větší jistotu lze samozřejmě požadavek zopakovat vícekrát i pro stejný bit. Provádět slepé injekce manuálně by bylo únavné, takže se vyplatí útoky automatizovat. Množství technik umí aplikovat relativně nový volně dostupný nástroj Commix (jeho první představení jsem viděl osobně na konferenci Black Hat Europe 2015).

Zaměřovali jsme se hlavně na způsoby, jak lze skrz zranitelnou aplikaci jednorázově ukrást data. Sofistikovanější útočník se ale může pokoušet server ovládnout dlouhodobě, ustanovit si vlastní obousměrný komunikační kanál a ideálně i proniknout do dalších systémů (např. dostupných z vnitřní sítě).

První krok může být využití injekce příkazu k nahrání vlastního interpreteru příkazů (tzv. reverse shell), skrz který bude útočník nadále jednoduše komunikovat a nebude potřebovat stále využívat zranitelnost. Pro zvlášť silný útok bude pravděpodobně potřeba získat vyšší oprávnění na úrovni operačního systému. Samotný Commix přímo obsahuje modul pro zneužití zranitelnosti Shellshock, pokud by napadnutý systém nebyl dlouho aktualizovaný.

Obecněji se určitě hodí zmínit framework Metasploit a tzv. Meterpreter, což je kód, který se pro nenápadnost usídlí pouze v operační paměti u existujícího procesu na serveru se zranitelnou aplikací, vytvoří šifrované připojení k útočníkovi a čeká na specifické příkazy. Kromě základních funkcí jako stažení konkrétního souboru nabízí právě možnost automatického zvýšení oprávnění skrz zneužití zranitelností aplikovatelných na dané prostředí (to samozřejmě nebude úspěšné, pokud je systém záplatovaný a dobře nakonfigurovaný). 

Dále umožňuje snadné vytvoření persistentních zadních vrátek, aby měl útočník přístup k systému i pokud by třeba někdo zranitelnou aplikaci odinstaloval (a stroj restartoval). Podporováno je také zvyšování nenápadnosti útoku či minimálně ztížení následného vyšetřování incidentu – jsou zde například příkazy pro mazání systémových logů nebo úpravu časových razítek v metadatech souborů.

Obrana

Nejlepší obranou před injekcí příkazu je se pokud možno přímým voláním příkazů operačního systému vyhnout a využít kvalitní (a aktualizované) knihovny použitého jazyka. Není-li vhodná knihovna k dispozici, pak nezbývá než se ujistit, že vkládaná data obsahují jen znaky bez speciálního významu pro použitý interpreter. Snaha vyjmenovat a kontrolovat všechny nebezpečné sekvence se ale často mine účinkem a nekompletní blacklist je zdrojem na první pohled skrytých zranitelností. Vždy bychom naopak měli validovat vstup oproti množině znaků, které zjevně zvláštní význam nemají, a ideálně kontrolovat přímo vyžadovaný formát (např. zda se jedná o validní URL). Ověřit bychom měli nejlépe všechny vstupy, i když na první pohled nepochází od uživatele.

Uvědomme si například, že injekce příkazu hrozí i tehdy, pokud se samotný příkaz nevytváří z uživatelského vstupu, ale využívá proměnné prostředí (i nepřímo uvnitř spouštěného příkazu). To umožní útočníkovi s lokálním přístupem tuto proměnnou přepsat a vykonávat příkazy s právy zranitelné aplikace. Obvykle nemá smysl se nevalidní vstupy snažit opravit, raději je odmítneme.

Pokud je potřeba přijmout data včetně speciálních znaků, je nutné provést sanitizaci přidáním dalších znaků pro odstranění speciálního významu, např. jazyk PHP má na to funkci escapeshellarg. Takový úkon bychom ale nikdy neměli provádět ručně, protože je opět náchylný na chyby a opomenutí určité varianty.

Kromě ochran před konkrétními typy chyb je vždy dobré ctít obecnější bezpečnostní principy v prostředí, kde aplikace běží. Většinou sice přímo nezabrání průniku (a rozhodně tedy nejsou dostatečnou ochranou samy o sobě), ale mohou aspoň omezit některé výše zmíněné techniky útoku.

  • Aplikace by měla běžet pod uživatelským účtem s co nejnižšími oprávněními, aby útočník nezískal plný přístup hned při průniku.
  • Systém by měl být záplatovaný, aby nebylo možné získané oprávnění eskalovat skrz další zranitelnosti.
  • Firewall by měl být vhodně nakonfigurovaný, abychom omezili přímé techniky extrakce dat přes síť.
  • Systém by neměl obsahovat nástroje pro síťovou komunikaci nebo další interpretery skriptů (pokud nejsou potřeba pro samotné fungování aplikace), aby nemohly být zneužity pro účely útočníka.
  • V systému by měl běžet anti-malwarový program, aby detekoval běžně používané útočné nástroje. Dalším stupněm je bezpečnostní software s behaviorální analýzou na obvyklé útoky či detekcí anomálií.
  • Velmi citlivá data nesouvisející s aplikací by měla být umístěna na samostatném serveru, aby k nim útočník nezískal přístup ani v případě úspěšného převzetí systému.

V tomto článku jsme si ukázali, jaké riziko představuje volání externích příkazů. Pozitivní je, že k němu většinou nedochází příliš často a skládané příkazy většinou nebývají komplikované jako třeba SQL dotazy. Na rozdíl od mnoha jiných chyb je proto většinou reálné v rozumném čase takové části aplikace dohledat a problematické části kódu manuálně zkontrolovat. Příště se naopak podíváme, co může způsobit tak běžný úkon jako vypisování dat do dynamické webové stránky.

Našli jste v článku chybu?