Vypnutí magic_quotes_gpc
Školení, která pořádám 
O konfigurační direktivě magic_quotes_gpc by měl vědět každý PHP programátor. Já si na svých serverech mohu konfiguraci PHP určit dle libosti, takže se na určité nastavení této direktivy mohu spolehnout, ale např. projekt určený k nasazení v různých prostředích s touto direktivou musí počítat. Vzhledem k tomu, že tuto direktivu není možné nastavit ve skriptu funkcí ini_set (protože se vyhodnocuje ještě před spuštěním skriptu), je nutné s jejími různými hodnotami počítat buď přímo v kódu nebo se na její nastavení ve skriptu spolehnout a data ošetřit na začátku skriptu:
<?php
// odstranění ošetření dat způsobeného direktivou magic_quotes_gpc
if (get_magic_quotes_gpc()) {
$process = array(&$_GET, &$_POST, &$_COOKIE, &$_REQUEST);
while (list($key, $val) = each($process)) {
foreach ($val as $k => $v) {
unset($process[$key][$k]);
if (is_array($v)) {
$process[$key][stripslashes($k)] = $v;
$process[] = &$process[$key][stripslashes($k)];
} else {
$process[$key][stripslashes($k)] = stripslashes($v);
}
}
}
unset($process);
}
?>
Kód se dá samozřejmě převrátit tak, aby data naopak v případě vypnutí direktivy ošetřoval, takže pak je možné psát elegantní kód ve tvaru "WHERE id IN ('" . implode("', '", $_GET["kategorie"]) . "')" a naopak je nutné vypisovaná data zvenku ošetřovat funkcí stripslashes.
Kód funguje i s poli parametrů a do proměnné $process postupně přidává další prvky ke zpracování – proto je použita archaická konstrukce list() = each(). Všimněte si, že se ošetřuje i pole $_FILES, na to se v podobných kódech často zapomíná. Ošetřené jsou i klíče polí, takže se nesmí zapomenout ani na ně. Řešení pomocí rekurzivní funkce je potenciálně nebezpečné kvůli možnému vyčerpání paměti, jak ukazuje Ilia:
<?php
file_get_contents("http://www.example.com/?a" . str_repeat("[]", 1024) . "=1");
?>
Přijďte si o tomto tématu popovídat na školení o bezpečnosti PHP aplikací.
Diskuse
hmmm, velmi vtipné a elegantní řešení!
Ondrej:
V diskuzi na phpguru.org pises, ze je adslashovana i promenna $_FILES[]['name'] ale na tehle strance projizdis cele pole $_FILES - nemuzes si nadelat paseku, kdyby ti to stripslashovalo treba $_FILES[]['tmp_name'] - pak by move_uploaded_file nenaslo docasnej soubor.
tmp_name přiděluje PHP a dá se spolehnout na to, že ' ani podobné znaky součástí jména nebudou.
Ondrej:
btw: to prochazeni vnorenych poli je fakt docela mazany ... chvili mi trvalo, ze jsem pochopil, co to vlastne delas :-)
Andrew:
To jo, mazané to je. Já jsem sice neměl problém zjistit o co tam jde, ale zato mi trvalo, než jsem měl to srdce se rozloučit s krasným jednoduchým a krátkým slashováním polí:
<?php
function array_addslashes($data) {
return (is_array($data) ? array_map('array_addslashes', $data) : addslashes($data));
}
?>
No ale bezpečnost je bezpečnost a byť se rekurzí dá vyřešit spousta krásných věcí, používat je třeba s mírou.
Honza M.:
\ jistě bude podobný znak. A ten se objevuje v tmp_name pod windows, řekl bych. Doufám, že neplácám blbosti, ale každopádně mi po zavolání této funkce přestaly fungovat metody is_uploaded_file a move_uploaded_file.
Honza M.:
Jo. Koukám ruší to znak \ v tmp_name. Ten se pod windows vyskytuje a je to očividně špatně, že se ruší.
juneau:
Asi se mi něco takového stalo: copy&pastnul jsem kód do stránky s formulářem pro upload více souborů, tedy výsledné pole ve tvaru $_FILES['fotografie']['name'][1-15] ... a od té doby hodinu a půl řešil, proč funkce copy() funguje a move_uploaded_file() ne. Cesty byly správně, žádné uvozovky nikde, ale move...() prostě nejela. Po smazání tohoto kódu vše opět funkční. Všiml jsem si, že při move_uploaded_file() nedocházelo ani k vytvoření .tmp souboru, pokud mohu soudit. Takže takový malý bugreport, nebo možná moje blbost, ale po té hodině a půl už nemám nervy na "průzkum místa činu".
Jo souhlasím u jmen souborů se některé znaky zakázané. Hezky napsané jen co je pravda :-)
Mordae:
Hmm, nějak mi vypadlo z hlavy které to jsou...? Prave mi proslo:
touch 'a!h@o#j$l%i^d&i*j(a)k_s+e|v}e{d"e:c?o>z<'
rm a\!h\@o#j\$l%i\^d\&i\*j\(a\)k_s+e\|v\}e\{d\"e\:c\?o\>z\<
Co jste rikal o tech nazvech?
asi tyhle:
$ touch '*'
touch: cannot touch `*': No such file or directory
;)
Pepca:
V Linuxu se v názvu souboru nesmí vyskytovat pouze dva znaky: lomítko, protože odděluje addresáře a "\0" protože se tím v C ukončují řetězce.
lukas:
Mně pripada lepsi napsat si funkce na import konkretni promenne, protoze pak se zabiji dve mouchy jednou ranou - osetreni magic_quotes a validaci hodnoty uvnitr promenne. Napriklad mivam definovane funkce invite_str, invite_num, ... (vsechny osetruji magic quotes a ta druha killne skript kdyz zjisti, ze hodnota uvnitr neni numeric).
M.:
Trochu OT, ale proč jste označil konstrukci "list() = each()" za archaickou?
Xixli:
Za zmienku by ešte možno stálo nastaviť si magic_quotes_gpc cez .htaccess: php_flag magic_quotes_gpc off
Avšak to tvoje je tiež pekné :-)
joe:
resil nekdo problem s tim, ze php slashuje nejen hodnoty, ale i hodnoty klicu v poli?
nechce se mi kvuli tomu vytvaret docasne pole a tim potom prepisovat vstup. Ale jine reseni mne bohuzel nenapada :(
To je dobrá připomínka. Kód jsem patřične opravil.
joe:
Jeste jako bonus:
PHP5 slashuje jmena promennych vzdy. pokud se jedna o pole, oslashuje pouze posledni prvek. tj:
<?php
/*
<input type="text" name="a'b" value="val's" />
==> [a\'b] => val's
<input type="text" name="a'b[a'b][a'b]" value="val's" />
==> [a'b] => Array
(
[a'b] => Array
(
[a\'b] => val's
)
)
*/
?>
tj. vyhnout se slashum, jak zubarovi s vrtackou.
Víš to jistě? Já dostanu výsledek:
Array ( [a'b] => Array ( [a\'b] => Array ( [a\'b] => val\'s ) ) )
PHP 5.0.5 a 5.1.2 pod Windows. Funkci jsem napsal tak, aby se podle toho chovala. Obzvlášť se mi nezdá tebou prezentovaný výsledek pro jednoduchý případ: [a\'b] => val's.
Byla to nejspíš chyba PHP, zpětné lomítko je nyní ve všech klíčích - upravil jsem podle toho kód.
magic_quotes_gpc slashuje klíče superglobals od případu k případu jinak, pročež je dost složité toto nějak stručně zjistit->ošetřit. Záleží nejen na verzi PHP (pro budoucí kompatibilitu si račte hodit korunou), ale taky na tom, zda hodnotou prvku je řetězec nebo pole. Viz tabulku:
http://cz2.php.net/manual/en/function.get-…-gpc.php#49612
Jelikož hodnoty klíčů určuje programátor (nikoliv uživatel vyplňující formulář), tak se mi zdá vhodnější tyto "nebezpečné" znaky vůbec nepoužívat. S případě slashování jde především o uvozovky, z historických důvodů není možné v klíči či názvu proměnné použít ani tečku.
Jak bude vypadat ten kód pro ošetřování řetězců při vypnuté directivě magic_quotes_qpc? Jen se změní stripslashes za addslashes?
Moc to nechápu tu fci a nevim, jak to spojit s tim ošetřením typu (u getů čísla přetypovávat na integer, atd.). Používam toto a chtěl bych se zeptat:
1.Zda je to bezpečné?
2.Jelikož musim tu fci u každýho dotazu volat na každou proměnou a zbytečně narůstá kód, nešlo by to nějak z automatizovat?
Předem děkuju.
<?php
static function validacePromenne($promenna, $typ = 'int')
{
switch ($typ) {
case 'int':
$promenna = intval($promenna);
break;
case 'string':
if (!get_magic_quotes_gpc()) $promenna =
addslashes($promenna);
break;
default: $typ = 'int';
}
return $promenna;
}
static function odstranitLomitka($hodnota)
{
$hodnota = is_array($hodnota) ?
array_map('odstraneniLomitek', $hodnota) :
stripslashes($hodnota);
return $hodnota;
}
?>
Jakube nenapsal bys mi nějaký inspirativní příspěvek?
Gifi:
Mam jednu otazku. osetreni magic_quotes_gpc pomoci kodu ktery si zde uvedl pouziju pouze tedky kdyz to na serveru je nastaveny pokud ne neuvedu ale zpusob prace pro ukladani do databaze a nacitani dat zustava stejny? a jeste otazka trosku mimo. kdyz pouziju fci addslashes tak v mysql databazi pri prochazeni nevidim zadne escapovane znaky. premyslel jsem jestli mysql pouziva pri vypsani na vystupu stripslashes. zkousel jsem taky vlozit do formularoveho pole nejaky mysql dotaz jestli se vykona a tak overit jestli fci addsplashes funguje. s programkem teprv zacinam takze pls jednoduse
Ano, kód slouží k tomu, aby se s daty pracovalo stejně nezávisle na nastavení direktivy.
Escapování slouží jen k tomu, aby bylo možné data uložit, v databázi jsou uložená čistá. Např. 'McDonald\'s' se uloží jako McDonald's. Databáze žádné odošetření při výstupu neprovádí.
Gifi:
a tak to se vysvetluje. kdyz jsem mel zapnutou direktivu magic_quotes na on a jeste do databaze je ukladal pomoci addsplashes tak v databazi byli uvozovky vyditelne, a to jen proto, ze se escapovani provadelo dvakrat, ano?
Gifi:
jeste me vrta hlavou malicost. kdyz pouziju na vystupu htmlspecialchars tak na strance odkaz nefunguje a vypsal tak jak ho vidim v editoru,ale kdyz tuto direktivu nepouziju ve formularich nebo textarea taky neni aktivni. je proto nutne tyto znaky prevadet
3m2:
manual pise ze Magic Quotes sa tyka len GET, POST, and COOKIE.
Preco potom prehanas algoritmom aj $_REQUEST a $_FILES???
dik za odpoved
$_REQUEST je sjednocení zmíněných tří a na některé položky ve $_FILES se magic_quotes aplikuje taky.
3m2:
ok $_FILES je jasne :)
ale ked to aplikujes na GET,POST,COOKIE a aj REQUEST
tak vlastne volas stripslashes 2x
mozes teda zmrsit data, alebo nie?
Ne. REQUEST není reference na ostatní, je to kopie.
3m2:
aha :) dik :))
u mna to ale blblo, mozno chybka v php
štíhloprd:
To s tím REQUEST (a proč ho tam používáš) jsem nepochopil. Mohl bys to vysvětlit podrobněji? (prosím)
$_REQUEST je potřeba ošetřit proto, že se s ním zachází stejně jako s $_GET, $_POST a $_COOKIE (magic_quotes_gpc se na $_REQUEST aplikuje stejně jako na ně).
Hmmmm... koukám, bez referencí by to asi nešlo tak elegantně.
1) S PHP 6.0 se magic_quotes_gpc a magic_quotes_runtime ruší.
2) Teď ještě, jak na magic_quotes_runtime()? :-)
magic_quotes_runtime stačí vypnout v kódu pomocí ini_set(). magic_quotes_gpc se zohledňuje už před spuštěním kódu, takže to vypnout pomocí ini_set() nestačí.
štíhloprd:
Case study: dejme tomu, že jsem pro klienta udělal stránky v PHP verze 4 nebo 5, které s magic_quotes_gpc počítají (ptají se na výsledek funkce get_magic_quotes_gpc()). Za rok klientův hosting upgraduje na PHP 6, kde magic_quotes_gpc odpadnou (
http://cz.php.net/manual/en/info.configuration.…-quotes-gpc ). Co s tím? :-) resp. Jak ten web napsat tak, aby mi za rok klient nemusel volat nas*aný, že mu mnou dělané stránky hází chyby?
(Hledal jsem, ale nenašel:
-co v PHP 6 způsobí zavolání funkce get_magic_quotes()?
-budou v PHP 6 magic_quotes_gpc "off" nebo "on"? (resp. budou uvozovky escapovány nebo ne?)
Direktiva magic_quotes_gpc bude pochopitelně vypnutá. get_magic_quotes() vrátí vždy false.
štíhloprd:
ještě bych dodal, že dle dokumentace (v<6.0):
-pokud je magic_quotes_gpc off, get_magic_quotes_gpc() vrací 0
-pokud je magic_quotes_gpc on, get_magic_quotes_gpc() vrací 1
asi bude třeba to vyzkoušet, popř. testování na false, resp. 0 a 1 provádět přes === nebo !== .
A tady jsem vygooglil seznam změn pro PHP 6.
http://www.corephp.co.uk/archives/19-Prepare-for-PHP-6.html
Zkusil jsem ověřit, kde všude PHP 5.2.6 nacpe ony fuckinglomítka:
- $_GET, $_POST, $_COOKIE: do všech klíčů + hodnot
- $_FILES: jen do některých klíčů!
Například [input type="file" name="o'file"] a upload souboru o'brazek.png vytvoří pole
array(2) {
"o\'file" => array(4) {
"name" => string(11) "o" // jméno se poškodí
"type" => string(11) "image/x-png"
"error" => int(0)
"size" => int(20624)
}
"o'file" => array(1) {
"tmp_name" => string(23) "C:\PHP\temp\php3ED8.tmp"
}
}
Závěr je ten, že pro pole $_FILES je potřeba volit odlišný přístup. Doporučil bych je ze zpracování ZCELA VYNECHAT a na straně aplikace zajistit, že v klíčích se nebudou používat znaky ' " \ a NULL-byte.
$_FILES jsem z kódu odstranil. Různé verze PHP se ale chovaly různě.
Pinqui:
Takže by mělo stačit dát k´d pro tuto kontrolu před začátek html (doctype) a pak by se mělo již vše ošetřovat?