Přeskočit na hlavní obsah

Prevence injection

  • Injection útoky, zejména SQL injection, jsou velmi časté
  • Pouze malé množství aplikací je vyvíjeno vlastními silami společnosti
  • Většina aplikací pochází z externích zdrojů - open source
  • Open source aplikace dávají možnost opravit problémy, ale aplikace s uzavřeným zdrojovým kódem ne
  • Ty vyžadují jiný přístup k chybám
  • K chybám dochází, když aplikace odešle interpretu nedůvěryhodná data
  • Tyto chyby jsou velmi rozšířené ve starším kódu
  • Často se vyskytují v SQL dotazech, LDAP dotazech, XPath dotazech nebo OS příkazech, či argumentech programů
  • Chyby lze snadno odhalit při zkoumání kódu (code review)
  • Jsou však obtížně odhalitelné prostřednictvím testování
  • Útočník je může najít pomocí skenerů nebo fuzzerů
  • Vždy je nejlepší opravit problém v samotném kódu, případně předělat některé části aplikace
  • Pokud zdrojový kód není k dispozici nebo je oprava staršího kódu neekonomická, má smysl pouze virtuální oprava

Formy injection zranitelnosti

Dotazovací jazyky

  • Nejznámější formou injection je SQL injection
  • Útočník má možnost modifikovat existující dotazy do databáze
  • Více informací najdete v SQL injection cheat sheetu
  • Dotazy založené na LDAP, SOAP, XPath a REST mohou být také náchylné k injection útokům

SQL injection

  • Útok spočívá ve vložení částečného nebo úplného SQL dotazu prostřednictvím vstupních dat nebo dat přenášených z prohlížeče do webové aplikace
  • Úspěšný útok může získat citlivá data z databáze, měnit ji nebo ji smazat, případně provádět další možné databázové operace
  • Cílem útoků je ovlivnit provádění předem definovaných SQL dotazů
  • Útoky lze rozdělit do následujících tříd
    1. Inband
      • Data jsou extrahována stejným kanálem, který se používá k injektování SQL kódu
      • Jde o nejjednodušší druh útoku
      • Data jsou zobrazena přímo na webu aplikace
    2. Out-of-band
      • Data jsou získána jiným kanálem (např. je vygenerován email s výsledky dotazu, který je odeslán útočníkovi)
    3. Inferential nebo Blind
      • Nedochází ke skutečnému přenosu dat
      • Útočník je schopen rekonstruovat informace odesláním konkrétních požadavků a pozorováním chování databázového serveru

Jak zranitelnost testovat

  1. Dělejte code review
    1. Zkontrolujte, zda jsou dotazy do databáze prováděny prostřednictvím prepared statemenets
    2. Zkontrolujte, zda jsou data před použitím v dotazu sanitizována, pokud jsou prováděny dynamické dotazy
    3. Soustřeďte se na použití sp_execute, execute nebo exec v rámci uložených procedur (případně funkce související s používanou databází)
  • Automatizovaná exploitace
    • Většinu uvedených technik je možné automatizovat například pomocí SQLMap nástroje
    • Použijte statickou analýzu kódu
  • Injection uložené procedury
    • Správně upravte uživatelský vstup při dynamickém dotazování v uložené proceduře
  • Time delay exploitace
    • Spočívá v odeslání injektovaného dotazu
    • V případě, že je dotaz v pořádku, může útočník sledovat dobu, za kterou server odpoví
    • Pokud dojde k prodlevě, může útočník předpokládat, že je dotaz v pořádku
    • V následujícím příkladu útočník zjišťuje, zda je verze MySQL 5 nebo ne (server zpozdí odpověď o 10s)
http://www.example.com/product.php?id=10 AND IF(version() like '5%', sleep(10), 'false'))--
  • Out of band exploitace
    • Technika je užitečná, když útočník neví nic o výsledku SQL operace
    • Spočívá v použití DBMS funkcí k provedení out of band připojení a doručení výsledků injektovaného dotazu jako součást požadavku na server útočníka

Jak zranitelnosti předejít

  1. Použijte prepared statements
    • Zajišťují, že útočník nezmění záměr dotazu ani v případě, že SQL dotaz vloží sám útočník
    • V bezpečném příkladu níže není možné zadat tom' or '1'='1, protože po jeho zadání útočník hledá uživatelské jméno tohoto tvaru
  • Uložené procedury
    • Rozdíl mezi prepared statements a uloženými procedurami spočívá v tom, že kód pro uloženou proceduru je definován a uložen v samotné databázi a poté je volán z aplikace
    • Obě techniky mají stejnou účinnost při prevenci SQL injection
    1. Vyberte si přístup, který je pro vaši aplikaci nejrozumější
    • Uložené procedury nejsou vždy bezpečné
    • Některé konstrukce mají stejný účinek jako parametrizované dotazy, pokud jsou implementovány bezpečně
      • Procedura neobsahuje žádné nebezpečné dynamické SQL
  1. Validujte vstupy na základě seznamu povolených položek
    • Různé části SQL dotazu není možné parametrizovat - názvy tabulek, sloupců, indikátor řazení atp.
    1. Validujte vstup vždy před zpracováním dotazu v takovýchto situacích
    • V případě názvů tabulek a sloupců je nejlepší, když tyto hodnoty pocházejí z kódu a ne z uživatelského vstupu a parametrů
    1. Mapujte a ověřujte hodnoty uživatelských parametrů na očekávané hodnoty v případě, že jsou vyžadovány jako vstup
    2. Zvažte přepsání daných částí aplikace v případě, že možnost z předchozího bodu používáte - jde o špatný návrh
  2. Escapujte veškerý uživatelský vstup
    1. Použijte tuto techniku pouze jako poslední možnost - pokud není možné použít některou z výše zmíněných možností
    • Slouží k escapování uživatelského vstupu před vložením do dotazu
    • Obvykle se doporučuje pro dodatečnou úpravu staršího kódu

Příklady

  • Java prepared statement
    • PreparedStatement je implementace parametrizovaného dotazu v jazyce Java
    • Prakticky všechny jazyky podporují parametrizované dotazování
// This should REALLY be validated too
String custname = request.getParameter("customerName");
// Perform input validation to detect attacks
String query = "SELECT account_balance FROM user_data WHERE user_name = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, custname);
ResultSet results = pstmt.executeQuery();
  • Java uložená procedura
    • CallableStatement je implementace uložené procedury v jazyce Java
    • Procedura sp_getAccountBalance by musela být v databázi předem definována a musela by implementovat dotaz výše
// This should REALLY be validated
String custname = request.getParameter("customerName");
try {
CallableStatement cs = connection.prepareCall("{call sp_getAccountBalance(?)}");
cs.setString(1, custname);
ResultSet results = cs.executeQuery();
// Result set handling...
} catch (SQLException se) {
// Logging and error handling...
}

LDAP injection

  • Útok používaný ke zneužití aplikací, které konstruují LDAP příkazy na základě uživatelského vstupu
  • Pokud aplikace nesanitizuje vstup, je možné modifikovat LDAP příkazy podobně jako u SQL injection
  • Útoky mohou vést k udělení oprávnění, neoprávněným dotazům a modifikaci uvnitř LDAP stromu
  • LDAP útoky jsou časté kvůli dvěma faktorům
    1. Absence bezpečnějších parametrizovaných rozhraní pro LDAP dotazy
    2. Rozšířené používání LDAP protokolu k ověření uživatelů v systémech

Jak zranitelnost testovat

  1. Dělejte code review
    1. Zkontrolujte, zda jsou v dotazu na LDAP použity speciální escape znaky
  • Automatizovaná exploitace
    1. Použijte skenovací nástroj OWASP ZAP, které má modul pro detekci LDAP injection

Jak zranitelnosti předejít

  1. Escapujte všechny proměnné pomocí správné LDAP encoding funkce
    • LDAP ukládá názvy způsobem založeným na DN (distinguished name) - jako takový jedinečný identifikátor
      • cn=Richard Feynman, ou=Physics Department, dc=Caltech, dc=edu
      • uid=inewton, ou=Mathematics Department, dc=Cambridge, dc=com
    • Někdy se používají pro přístup ke zdrojům, jako je uživatelské jméno
    • Některé znaky jsou v DN považovány za speciální
      • \ # + < > , ; " = včetně počáteční a koncové mezery
    • Každý DN přesně ukazuje na jednu položku (jako jeden řádek v databázové tabulce)
    • Pro každou položku existuje několik atributů (jako sloupců v databázi)
    • Pro vyhledávání uživatelů s určitými atributy je možné využít vyhledávacích filtrů
    • Filtry se zapisují v polské notaci (prefixová notace), viz níže
    1. Vylučte všechna nedůvěryhodná data při sestavování LDAP dotazu
    2. Escapujte vstup v závislosti na vyhledávacím filtru nebo DN
(&(ou=Physics)(| (manager=cn=Freeman Dyson,ou=Physics,dc=Caltech,dc=edu)
(manager=cn=Albert Einstein,ou=Physics,dc=Princeton,dc=edu) ))

Příklady

  • JAVA LDAP escapování
    • Znak zpětného lomítka je literál řetězce Java a escapovací znak regulárního výrazu
public String escapeDN (String name) {
// From RFC 2253 and the / character for JNDI
final char[] META_CHARS = {'+', '"', '<', '>', ';', '/'};
String escapedStr = new String(name);
// Backslash is both a Java and an LDAP escape character,
// so escape it first
escapedStr = escapedStr.replaceAll("\\\\\\\\","\\\\\\\\");
// Positional characters - see RFC 2253
escapedStr = escapedStr.replaceAll("\^#","\\\\\\\\#");
escapedStr = escapedStr.replaceAll("\^ | $","\\\\\\\\ ");
for (int i=0 ; i < META_CHARS.length ; i++) {
escapedStr = escapedStr.replaceAll("\\\\" +
META_CHARS[i],"\\\\\\\\" + META_CHARS[i]);
}
return escapedStr;
}
public String escapeSearchFilter (String filter) {
// From RFC 2254
String escapedStr = new String(filter);
escapedStr = escapedStr.replaceAll("\\\\\\\\","\\\\\\\\5c");
escapedStr = escapedStr.replaceAll("\\\\\*","\\\\\\\\2a");
escapedStr = escapedStr.replaceAll("\\\\(","\\\\\\\\28");
escapedStr = escapedStr.replaceAll("\\\\)","\\\\\\\\29");
escapedStr = escapedStr.replaceAll("\\\\" +
Character.toString('\\u0000'), "\\\\\\\\00");
return escapedStr;
}

Skriptovací jazyky

  • Všechny skriptovací jazyky mají nějakou formu volání eval, které za běhu přijímá a provádí kód
  • Pokud je kód tvořen nezvalidovaným a neescapovaným uživatelským vstupem, může dojít k injektáži kódu
  • To útočníkovi umožní změnit logiku aplikace
  • Při každém použití skriptovacího jazyka se implementace vyššího jazyka provádí pomocí nižšího jazyka (např. C)
  • Pokud má skriptovací jazyk chybu v kódu, lze nasadit Null Byte Injection vektor útoku a získat přístup k dalším oblastem paměti

Operační systémy

  • Technika používaná prostřednictvím webového rozhraní k provádění příkazů OS na webovém serveru
  • Uživatel zadává příkazy OS prostřednictvím webového rozhraní za účelem jejich spuštění
  • Takové zneužití je možné u jakéhokoli rozhraní, které není řádně sanitizováno
  • Útočník tak může spouštět příkazy OS, nahrát škodlivé programy nebo získat hesla

Jak zranitelnost testovat

Dělejte code review

  1. Zkontrolujte, zda jsou volány execute metody a zda jsou jako data pro tento příkaz brány neověřené uživatelské vstupy
  • Pokud na konec URL parametru, za kterým následuje příkaz OS, přidáte středník, příkaz se provede
  • %3B je zakódováno a dekóduje středník
  • Středník je interpretován jako oddělovač příkazů
http://sensitive/something.php?dir=%3Bcat%20/etc/passwd
  • Útok byl úspěšný, pokud aplikace odpoví výstupem z /etc/passwd souboru
  1. Použijte skenery webových aplikací, které injektují různé varianty příkazů a testují odpověď
  2. Využijte statickou analýzu kódu

Jak zranitelnosti předejít

Parametrizujte

  1. Použijte mechanismy, které automaticky zajistí oddělení dat a příkazů (např. uvozovky, encoding)

Validujte vstup

  1. Validujte všechny hodnoty příkazů a argumentů
  • Existují různé stupně validace pro příkaz a jeho argumenty
    1. Validujte příkazy podle seznamu povolených příkazů
    • Argumenty těchto příkazů validujte následovně
      1. Explicitně uveďte povolené argumenty a validujte je
      2. Použijte allow-list regulární výraz se seznamem povolených znaků a maximální délkou řetězce
        1. Zajistěte, aby součástí výrazu nebyly metaznaky `& | ; $ > < \ !`` a bílé znaky
        • Např. následující regulární výraz povoluje malá písmena a číslice, neobsahuje metaznaky a délka je omezena na 3 - 10 znaků: ^[a-z0-9]{3,10}$

Příklad v jazyce Java

Špatné použití

  • Zde je příkaz spolu s argumenty předán jako řetězec, což usnadňuje manipulaci s výrazem a vkládání škodlivých řetězců
ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe -arg1 -arg2");

Správné použití

  • Zde je příkaz, kde je každý argument předáván samostatně
  • To usnadňuje validaci a snižuje riziko vložení škodlivých řetězců
ProcessBuilder pb = new ProcessBuilder("TrustedCmd", "TrustedArg1", "TrustedArg2");
Map<String, String> env = pb.environment();
pb.directory(new File("TrustedDir"));
Process p = pb.start();

Síťové protokoly

  • Webové aplikace často komunikují se síťovými démony (SMTP, IMAP, FTP)
  • Uživatelský vstup se tak stává součástí komunikačního streamu
  • Zde je také možné injektovat sekvence příkazů a zněužít tak danou relaci

Pravidla prevence injection

Proveďte správnou validaci vstupů

  1. Validujte vstup na základě seznamu povolených hodnot
  • Nejde ale o úplnou obranu, protože mnoho aplikací vyžaduje ve vstupech speciální znaky

Použijte bezpečné API

  1. Upřednostňujte použití bezpečného API, které se vyhne interpretu nebo poskytne parametrizované rozhraní
  • Pozor na API, jako jsou uložené procedury, která jsou parametrizovaná, ale přesto mohou způsobit injection

Escapujte uživatelský vstup

  1. Escapujte speciální znaky pomocí specifické syntaxe pro daný interpret, pokud není k dispozici parametrizované API rozhraní

Kam dál

Zranitelnosti

Cheat sheety

Checklisty

Další