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í Bezpečnost PHP aplikací.
Diskuse
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.
Jakub Vrána :
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".
MiSHAK.wz.cz:
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?
spaze:
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 :(
Jakub Vrána :
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.
Jakub Vrána :
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.
Jakub Vrána :
Byla to nejspíš chyba PHP, zpětné lomítko je nyní ve všech klíčích - upravil jsem podle toho kód.
Honza Odvárko:
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
dgx:
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.
tomasr:
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?
tomasr:
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;
}
?>
tomasr:
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
Jakub Vrána :
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
Jakub Vrána :
$_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?
Jakub Vrána :
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)
Jakub Vrána :
$_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ě).
štíhloprd:
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()? :-)
Jakub Vrána :
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?)
Jakub Vrána :
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
david@grudl.com:
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.
Jakub Vrána :
$_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?
harvey:
Len ma tak napadlo, že čo takto rekurzívne použiť užívateľom definovanú funkciu, na všetky prvky pola, čo by mohlo vapadať asi takto:
<? php
function definovanaFunkcia(&$value,$key)
{
$value=addslashes($value);
$key=addslashes($key);
}
//použitie na všetky prvky poľa
array_walk_recursive($_GET, 'definovanaFunkcia');
?>
Samozrejme treba si overiť, či je magic_quotes_gpc zapnuté, či vypnuté a podľa toho použiť správnu uživatelskú
Jakub Vrána :
Funkce je k dispozici od PHP 5, nedovoluje ale změnit hodnoty klíčů.
harvey:
Aaaaa, to mi akosi uniklo, ďakujem za napomenutie.
Vyriešil som to na koniec takto, aj keď je to vlastne riešenie ktoré predpokladá magic_quotes_gpc vypnuté, ale samozrejme, dá sa to upraviť. Použil som síce rekurzívne volanie funkcie, ale pomocou premennej $depth sa dá nastavit, ako hlboko sa može funkcia zanoriť, ak je to hlbšie, vypíše sa text, prip. sa to dá inak ošetriť. Na klúče som použil regulerny výraz, aby to nezhodili nejaké skripty vpísané do adresného riadku prehliadača, kde by pole $_GET mohlo narobiť psie kusy.
<?php
function safeArray(&$input,$depth=3)
{
$output='';
if(is_array($input))
{
$depth--;
reset($input);
while(list($key,$value)=each($input))
{
$key=preg_replace('/[^\w]/','X',$key);
if(is_array($value))
{
if($depth>0)
{
$output[$key]=safeString($value,$depth);
}
else
{
$output[$key]='nejaka hlaska';
}
}
else
{
$output[$key]=addslashes(htmlspecialchars($value));
}
}return $input=$output;
}
}
// volanie funkcie
safeArray($_GET);
?>
radas:
Zdravím,
snažim se zabezpečit POST/GET data při vkládáni do databáze inspiroval jsem se přízpěvkem od harvey
<?php
function clearValues(&$value, $key)
{
$value=trim($value);
$value=strip_tags($value);
$value= addslashes($value);
$value=mysql_real_escape_string($value);
return $value;
}
//volam
array_walk($_GET,"clearValues");
array_walk_recursive($_POST, "clearValues");
?>
můžu se spolehnout, že tato funkce již bezpečně "očisti" posíláné data do databaze na nehrozi mi SQL injection?
Jakub Vrána :
Ne, to je celé úplně špatně. Při vkládání do databáze stačí použít mysql_real_escape_string (pokud je vypnuté magic_quotes_gpc), při vypisování dat do stránky potom htmlspecialchars.
Diskuse je zrušena z důvodu spamu.