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í.

Jakub Vrána, Řešení problému, 13.1.2006, diskuse: 56 (nové: 0)

Diskuse

ikona dgx:

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.

ikona Jakub Vrána OpenID:

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".

ikona 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?

ikona 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?

ikona Jakub Vrána OpenID:

Pro průchod polí se obvykle používá spíše foreach: http://php.vrana.cz/prochazeni-poli.php.

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 :(

ikona Jakub Vrána OpenID:

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.

ikona Jakub Vrána OpenID:

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.

joe:

kod:      http://phpfi.com/98860
vysledky: http://phpfi.com/98861

http://bugs.php.net/bug.php?id=29776

ikona Jakub Vrána OpenID:

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

ikona 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.

ikona Jakub Vrána OpenID:

... a mezeru.

vlado:

je to parada

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?

ikona Jakub Vrána OpenID:

Ano.

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

ikona Jakub Vrána OpenID:

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?

ikona Jakub Vrána OpenID:

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

ikona Jakub Vrána OpenID:

$_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?

ikona Jakub Vrána OpenID:

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)

ikona Jakub Vrána OpenID:

$_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()? :-)

ikona Jakub Vrána OpenID:

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?)

ikona Jakub Vrána OpenID:

Direktiva magic_quotes_gpc bude pochopitelně vypnutá. get_magic_quotes() vrátí vždy false.

štíhloprd:

Aha, díky. Teď jsem to teprve našel podrobněji:
http://cz.php.net/magic_quotes

š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

ikona 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.

ikona Jakub Vrána OpenID:

$_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ú

ikona Jakub Vrána OpenID:

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?

ikona Jakub Vrána OpenID:

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.

Vložit příspěvek

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2016 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.