BEZPECNOST

Jak nejlepe ochranit sql dotazy provedene v php?

Kolega z firmy (velice dobry programator) mi zjistil nekolik der v mem webu, diky spatne zabezpecenym dotazum. (Chybela SQL Injekce ...atd)

Ted by mne zajimalo kolik z Vas je s touto problematikou obeznameno, jake finty pouzivate?

Ja sem vyhrabal tohle ...

http://rjwebdesign.net/doc/php_mysql_bezpecnost_podvrzeni.pdf
Když v SQL dotazu používáte hodnotu proměnné, tak může dojít k těmto dvěma případům:

1. Hodnota je řetězec, v dotazu se uzavírá do apostrofů, např.
SELECT neco FROM tabulka WHERE name='$jmeno'
Aby v tomto případě mohl někdo pomocí obsahu proměnné $jmeno provést nekalosti v SQL, musel by ukončit apostrof, takže pro ochranu postačí použít před tímto dotazem funkci
$jmeno = AddSlashes($jmeno);
Nyní může být v $jmeno cokoliv, ale vše bude bráno jen jako obsah té podmínky WHERE, nebude možno SELECT dotaz oklamat nebo ho dokonce úplně ukončit a začít jiný dotaz (DELETE např. :-)

2. Hodnota je číslo, do apostrofů se neuzavírá, např.
SELECT neco FROM tabulka WHERE id=$id
Tady už AddSlashes nepomůžou, protože pro oklamání dotazu stačí do $id umístit např. _OR_1=1 (kde _ označuje mezeru) nebo ještě hůře středník a celý nový příkaz (např. DELETE).
I zde je ale pomoc snadná, úplně stačí přičíst k $id nulu. Tím se spolehlivě převede na číslo.
Já třeba to dělám přímo při vytváření krátkých názvů proměnných, takže nějak takhle:
$id = $_GET['id']+0;
ad 1: řetězec může být uzavřen i do uvozovek, ale opět stačí použít addslashes
ad 2: Je zbytečné přičítat nulu, když můžu prtoměnnou přímo na integer přetypovat (settype($foo, 'integer') - nevrací nic, jenom přetypuje proměnnou) a nebo v SQL dotazu použít: mysql_query('SELECT neco FROM tabulka WHERE id='.(int)$id);
funous >
ad 1: zásadně používám v PHP MySQL_Query jen a pouze apostrofy, protože celý řetězec dotazu je uzavřen v uvozovkách, takže jsem vůbec možnost uvozovek neuváděl.
ad 2: "Je zbytečné přidávat nulu, když můžu proměnnou přímo na integer přetypovat" - tahle věta trochu nedává smysl, hlavně to slovo "zbytečné". Mi připadne naopak zbytečně složité přetypovávat nebo dokonce komplikovat SQL dotaz způsobem který jsi uvedl, když úplně stačí na začátku kódu při vytváření krátkých názvů proměnných přičíst nulu.
To bys taky mohl napsat "Je zbytečné přidávat nulu, když můžu proměnnou prohnat přes několik ifů a detekovat zda je to číslo"
Tom:
ad 1: Imho je lepší používat apostrofy jako řetězec a spojovat řetězce a proměnný pomocí teček (a uzavírat řetězce v SQl do uvozovek). Já používám ('SELECT foo FROM bar WHERE a="'.$text.'"'). Nevypadá to sice nejlíp, ale pro parser by to mělo bejt nejrychlejší. (dokud je tam málo proměnnejch a taky do nějaké verze PHP).
ad 2: No, když chci dát proměnnou do "zkráceného tvaru", tak použiju $foo = (int)$_GET['foo'] a je to. Mám naprostou jistotu že je to integer, tzn že se zbavím i případného floatu (kdyby byl na škodu...)
funous >
ad 1.: Ano, to je taky možnost, ale mi připadá nepřehledná, což sice není tak velký problém, ale s nepřehledností v programování velmi úzce souvisí možnost udělat syntatickou chybu. Schválně se podívej do svého příspěvku, jak vypadají ty uvozovky a apostrofy vedle sebe ;-) (Já vím, v textovém editoru to je lépe odlišené jen tady na fóru to vypadá divně.)
o co přehlednější a chybovzdornější je
("SELECT foo FROM bar WHERE a='$text' ")
Navíc mi připadá i logičtější vnořovat apostrofy do uvozovek a nikoliv naopak, tak je to názornější a používá se to např. i v literatuře (přímá řeč uvnitř přímé řeči).

ad 2.: Jistě, to už je jen otázka zvyku nebo otázka toho, co komu připadne pohodlnější pro psaní nebo méně rušivé v kódu. Ta nula samozřejmě není můj nápad (a přiznám se že by mě ani nenapadnul, já bych šel určitě po nějaké převodní funkci nebo přetypování), to jsem viděl v jedné knížce nebo na www.php.net, nevím už přesně kde.
tom:
ad 1: Já bych to psal tak jako ty, kdyby to moje nebylo asi o třetinu rychlejší pro parser... Sice je to v řádu mikrosekund, ale rychlejší to je... Šetříme naše servery :)
Na přehlednost sem si už zvykl, chybuž taky tolik nedělám :) Mě se zdá zbytečné aby parser musel projíždět vnitřek nějakého dlouhého dotazu, hledat proměnné, escape sekvence apod., když tam je třeba jen jedna proměnná. Ale zas takový rozdíl to není :)

ad 2: Mě se zdá přehlednější to (int)$_GET['foo'], protože je hned jasné co to tam dělá :)
1) Zásadní rozdíl mezi ' a " je v tom, že pokud máme string v uvozovkách, celý se prohledává na přímo vložené proměnné. tj 'string' je rychlejší než "string". Já zásadně používám apostrofy.

2) $foo+0 je IMHO jednoznačně pomalejší, protože to přetypování se stejně provede. Ať už je to intval($foo) nebo (int)$foo, provádí se pouze přetypování, nikoly přičítání nuly (jakkoli je superrychlé).
:-) je pěkné jak se oháníte rychlostí (nepochybně máte pravdu, toho jsem si vědom a plně s vámi souhlasím), ale vždyť tady na WZ to stejně nikdo nepozná. Nikdo tady nedělá tak dlouhé a náročné PHP skripty, aby to zpomalovalo tak, že rozdíl bude poznat (tím nemyslím poznat pouhým okem, ale dokonce poznat nějakým přesným měřením doby provádění skriptu).

Jinak ani já samozřejmě nedávám zbytečně proměnné do řetězců, obzvlášť pole nikdy, ačkoliv to jde. Nedělám to z části i proto, že jinak velmi chytrý PSPad v tomhle případě nepozná že jde stále o proměnnou a tu část v hranatých závorkách už obarví stejně jako řetězec.
btw: nechci se plest do teto fundovane diskuze, ale zkousel nekdo neco z toho *.pdf? Ja ano, bohuzel(nebo bohudik) se mi nepovedlo ani jednou dosahnout nekaleho vysledky. Radby to videl na vlastni oci!

kdyz je treba tento dodaz: $dotaz="Select * from 'hack' where ID= '".$_POST[ID]."'; tak jak by slo tedy obelsit dotaz, aby se vypsala cela tabulka? nebo dokonce smazala?
"Select * from 'hack' where ID= ' " . $_POST[ID] . " '

pro přehlednost (špatně se tady odlišují uvozovky a apostrofy) to přepíšu, význam je stále stejný

"Select * from 'hack' where ID= '$_POST[ID]' "

správně se očekává např. tenhle výsledek:
"Select * from 'hack' where ID= '45' "

pokud někdo ve formulářovém textovém poli id napíše vhodnou hodnotu (předpokládám že jde o textové pole, pokud jde např. o hidden, bylo by to malinko složitější), pak ten dotaz bude vypadat např. takhle:

"Select * from 'hack' where ID= '45' OR '1'='1' "
přičemž se přečtou všechny záznamy, protože 1=1 platí vždy, tudíž všechny záznamy této podmínce vyhoví.
Obsah formulářového pole id by potom musel být:
45' OR '1'='1
je jasné že ty apostrofy jsou životně důležité (některé), takže AddSlashes problém vyřeší.
Smazání tabulky by vyžadovalo ukončit dotaz SELECT a začít dotaz DROP (nebo kterým se to mažou tabulky přiznáv se že nevím protože tabulky mažu jen přes PHPMyAdmin). Takže by ještě v hodnotě toho ID musel být středník, kterým se oddělujuje více příkazů v jednom SQL dotazu.
Ale je tu jedno ALE - hacker by v tom případě musel znát název oné tabulky, kterou chce mazat, to ale většinou není problém uhádnout protože lidi většinou volí názvy vypovídající a tudíž uhodnutelné.
>>TOM tak sem do policka kde by se vybiralo ID, vlozil tohle 45' OR '1'='1 a nic, vyhodilo, to prazdnou stranku. ID totiz neexistuje, kdyz sem zmenil ID na nejake ktere je v tabulce,tak to vyhodilo JEN to co melo.
bwt: hezka citace:-) ja to pdf cetl taky

docela by me zajimalo,jak to tedy opravdu je
sisa hod sem link na web .... ja zejtra reknu klukum v praci aby ti ho proklepli ;)
sisa - nebyla to citace, já to PDF téměř ani nečetl, jen jsem si ho jednou prolétnul. Takže pokud se něco shodovalo, jedná se čistě o náhodu nebo inspiraci. Rozhodně jsem nic neopisoval.
To oddělování SQL dotazů pomocí středníku - mysql_query vykoná vždy jen jeden dotaz, pokud je jich tam více oddělených středníkem, tak to buď vykoná ten první, nebo to neudělá nic s tím že je to chybný dotaz.
To totiž funguje jenom u aplikací které samy rozpoznávají dotazy oddělené středníkem (PHPMyAdmin, phpBB,...) a rozdělují je. Právě phpBB mělo v jisté verzi chybu kde se dalo do adresy napsat něco jako id=3"; DROP DATABASE xxx;# a opravdu to smazalo databázi (pokud byly práva)

K té rychlosti stačí si představit (čistě teoreticky), že na WZ jde za sekundu 1000 požadavků na PHP scripty (běží po sobě, každý 0,001 (tisícina sekundy, takže to do té sekundy stihnou. Samozřejmě to jde jinak, takže běží pomaleji a je jich míň), a že jsou tam řetězce v apostrofech. Řekněme že kdyby byly v uvozovkách, zpomalí se každý script o 0,0001 sekundy (desetitisícina). Zpomalení je to o desetinu na každý script. Jenže co se stane? Za sekundu se zpracuje jen asi 900 scriptů, do další sekundy jich zbyde 100. K té stovce je ale dalších 1000 požadavků, takže v další sekundu se má vykonat 1100 scriptů, stihne se zase jen asi 900. Atd. až se server přetíží...
Trochu přehánim, ale v důsledku je docela dobrý optimalizovat scripty na rychlost...
(samozřejmě, dokonalé wz zpracuje mnou určených 1000 požadavků než bych řekl bit, takže ať je to pomalé jak chce, nebude to znát. Ale kdyby všichni dost optimalizovali scripty, nebylo by nutno tolika HW upgradů :))
sisa + tom, nemusite pretypovavat nic

staci addslashes a mit vzdy $var v ' ' a je to ...
>>23k oki, http://sisa.chytrak.cz/hack.php , je to napsany takovym lamackym zpusobem, tak sem zvedav
šíša > Parse error: parse error, expecting `']'' in /3w/chytrak.cz/s/sisa/hack.php on line 24

Btw (taky asi pro šíšu) > Výsledek toho hacknutého dotazu závisí hodně na tom, jakým způsobem je zpracování výsledku v PHP provedeno.
Pokud správný dotaz SQL vybere z tabulky jeden řádek (např. podle ID) např. takhle:
$vysledek = MySQL_Query("SELECT .....");
$zaznam = MySQL_Fetch_Row($vysledek);
a s tím záznamem pak je pracováno, pak pochopitelně pokud hacknutý dotaz vrátí třeba všechny řádky tabulky (nejen jeden), pak se stejně zpracuje jen jeden z nich (protože jen jeden se načte do $zaznam), takže celá tabulka se nevypíše.
Pokud by ale nehacknutý dotaz nevrátil žádný řádek (např. při přihlašování pokud by bylo zadáno špatné heslo) a hacknutý by vrátil třeba všechny, pak by se tím hackem dalo přihlásit. A opět by nevadilo, že byly vráceny všechny záznamy uživatelů a ne pouze jeden (tady se ovšem předpokládá ještě aby ve skriptu nebylo kontrolováno, zda byl vrácen pouze jeden řádek).
tak ;)