Spouštění externích souborů
Školení, která pořádám
Při práci na Admineru jsem přemýšlel, jak by se ještě dala zmenšit velikost stahovaného souboru, a pohrával jsem si s myšlenkou, že by se některé části kódu mohly stahovat z Internetu, pokud by si to uživatel přál. Používá se to už u zvýrazňování syntaxe SQL příkazů, chystám to u alternativních stylů. Přemýšlel jsem i o JavaScriptovém frameworku, ale kompletní nefunkčnost JavaScriptu při nedostupném Internetovém připojení by byla příliš omezující.
Dal by se stahovat i PHP kód? Hned několika způsoby:
include "http://";
- Tento způsob je ve výchozím nastavení zakázán direktivou
allow_url_include
a není příliš rozumné po uživatelích chtít, aby si ho povolili.
eval(file_get_contents("http://"))
- Tento způsob bývá obvykle možný, ale dá se zakázat direktivou
allow_url_fopen
.
- Knihovna cURL
- Někde se používá pro stahování externích souborů tato knihovna, protože má bohaté možnosti konfigurace. Já pro konfiguraci raději používám kontexty.
- fsockopen
- Pro stažení externího souboru se dá použít i nízkoúrovňová funkce fsockopen, někdy bývá ale také zakázaná.
<?php
$url = parse_url("http://");
$fp = fsockopen($url["host"], ($url["port"] ? $url["port"] : 80));
fwrite($fp, "GET " . (isset($url["path"]) ? $url["path"] : "/") . (isset($url["query"]) ? "?$url[query]" : "") . " HTTP/1.1\r\n");
fwrite($fp, "Host: $url[host]\r\n");
fwrite($fp, "Connection: close\r\n");
fwrite($fp, "\r\n");
fpassthru($fp);
fclose($fp);
?>
Při použití protokolu HTTP/1.1 je potřeba dát pozor na kódování chunked, s verzí 1.0 zase teoreticky nejde poslat hlavička Host
.
Získání dat metodou POST
Všechny způsoby jdou zakázat. Co zakázat prakticky nejde, je získání dat metodou POST.
<?php
$source = str_replace("\r", "", $_POST["source"]);
if (!$source) {
echo '<iframe src="http://adminer.sf.net/source.php?action=' . urlencode("http://$_SERVER[SERVER_NAME]$_SERVER[REQUEST_URI]") . '"></iframe>';
} elseif (md5($source) == "84ab81e6605ca8141bb331c550093072") {
eval("?>$source");
} else {
echo "Invalid source.\n";
}
?>
Z externí adresy se načte zdrojový kód, který se spustí v případě, že sedí jeho otisk.
Skript source.php
načte do formuláře zdrojový kód a pokud je zapnutý JavaScript, tak ho automaticky odešle.
<form action="<?php echo (preg_match('~^https?://~', $_GET["action"]) ? htmlspecialchars($_GET["action"]) : "/"); ?>" method="post" target="_parent">
<p><input type="hidden" name="source" value="<?php echo strtr(htmlspecialchars(file_get_contents("adminer.php")), array("\t" => "	", "\n" => " ")); ?>" /></p>
<p><input type="submit" value="Run" /></p>
</form>
<script type="text/javascript">document.getElementsByTagName('form')[0].submit();</script>
Po prvotním stažení kódu by ho bylo možné uložit do session proměnné a spouštět nadále z ní.
Přestože by bylo lákavé získat celý Adminer ve 200 bajtech, tak jde jen o Proof of Concept, který v praxi realizovat nebudu.
Diskuse
Ta poslední metoda by mě tedy opravdu nenapadla. Velmi zajímavé řešení.
Přemýšlels nad tím, jak vyřešit předání nového otisku v případě aktualizace souboru? Aktualizace jedné stahované knihovny by znamenala nutnost aktualizací u všech klientů (minimálně "seznamu hashů") - to by sice v případě "ultramalého" klienta až tak nevadilo, ale stejně...
Připadá mi to podobné jako různé "lite instalátory", kdy si stáhneš pár set kilo programu, vybereš si co chceš nainstalovat, a on si stáhne a nainstaluje potřebné soubory. Problematika online distribuce SW (nejen aktualizací) u desktopových aplikací už tedy nějak řešena je, u webových jsem to zatím moc neviděl. Máš tušení, proč to tak je?
Když jsem to psal, tak jsem si říkal, že by loader předával informaci o verzi, kterou chce stáhnout.
Jak to udělat pro nejnovější verzi, mě zatím nenapadlo.
U webových aplikací se lite instalace neřeší obvykle proto, že je nepotřebuješ instalovat :-). A když už potřebuješ (všelijaké ty WordPressy a MediaWiki), tak by musel mít loader právo zápisu zdrojových souborů (protože kód nestačí držet v paměti, jak by si to mohl dovolit Adminer), což bývá u webových aplikací problém. Takže je jednodušší si to nakopírovat ručně.
Peter:
Nové verzie sa čiastočne dajú ošetriť tak, že spolu s dotazom na stiahnutie sa odošle informácia o verzii, ktorá má byť stiahnutá. Takže vždy najnovšiu verziu by to síce nestiahlo, ale aspoň by fungovali tie staršie. No a potom stačí presvedčiť klientov, aby vždy aktualizovali.
Ano, přesně to samé jsem psal…
Aktuální verze by se dala vyřešit asymetrickou kryptografií. Kód ze serveru by byl podepsaný soukromým klíčem a loader by obsahoval veřejný klíč, který by podpis ověřil. To už by ale potřebovalo přítomnost knihovny OpenSSL, samotné hešování by na to nestačilo.
Martin Straka:
Rozhodne zajimave.
A jako bonus to vytvori XSS zranitelnost na serveru, kde je source.php:)
.../source.php?action=javascript:alert(123)
phx:
Vzhledem k tomu, ze na serveru je vetsinou rychle pripojeni slo by udelat nejaky downloader, ktery by pri prvnim spusteni stahnul zdrojaky do temp souboru an disk. Takze by uzivatel musel na server nahrat jen par bajtu.
david@grudl.com:
Obsese velikostí není u mužů neobvyklá, ale cílem bývá zvětšování, ty holt musíš mít zase něco extra :-))
Zkoušel jsi něco jako eval(gzuncompress(file_get_contents(__FILE__, NULL, NULL, __COMPILER_HALT_OFFSET__))) ?
Jakub Vrána :
Ano, o tomhle jsem uvažoval dlouho. Problém je, že v PHP není žádná vestavěná dekompresní funkce. Uvažoval jsem dokonce, že bych si napsal vlastní, ale tímto směrem jsem se nevydal. Když budu citovat svůj vlastní článek pro php|arch (dosud nevydaný):
Everybody knows that eval is evil (one reason is that it is not compatible with PHP accelerators). Therefore, the code is not compressed but only minified.
Problem:
Ahoj, narazil jsem na problém, že mi nejde obejít přechod mezi HTTP a HTTPS. Potřebuji zavolat ze své stránky (HTTP) script na serveru (HTTPS) ale bez zásahu uživatele. Zkoušel jsem echnout Framy zkoušel jsem FSockOpen, FOpen a nic z toho mě nepustí.
Prostě když napíšu do prohlížeče framu nebo odkazu url:
https://www.s.cz/data.php?x=10tak to funguje ale až po osouhlašení cetrifikátu.
ale když chci aby mi to zpracoval sám script který vypočítá proměnnou x nevím jak ji předat serveru s bez toho debilniho odklikávání cetifikátu.
Potřebuju to metodou bez výstupu... tedy bez echo nejlépe FsockOpen ale jak... https nefunguje ani na portu 80 ani na portu 443. Prostě vrací false;
Jakub Vrána :
Použij <?php fsockopen("ssl://$host", 443); ?>.
Diskuse je zrušena z důvodu spamu.