Teda ta česká varianta nezní úplně nejlépe.
Injection se, technicky vzato, dá přeložit i jako injekce (samotné vstříknutí, nikoliv prostředek — např. injekční stříkačka), ale zní to tedy dost divně, obzvláště pak v titulku jako typ zranitelnosti.
Možná bychom mohli společně vymyslet nějaký lepší překlad? A nebo to prostě nepřekládat? V dnešním IT světě pravděpodobně každý těmto základním anglickým slovům rozumí. Také nepřekládáme věci jako klávesa Únik (místo Escape) atd.
@martyd.
Ano, tak ze srandy. Speciálně když procházíš ty hory českých textů protože v anglicky nenajdeš nic ...
@Michal Kleiner
Zatím to vypadá, že největší přínos překládání takových termínů do češtiny je ta sranda kterou si nad tím skřiveným výsledkem potom obecenstvo užije. Přitom čeština umí s cizími slovy tak hezky pracovat. To zas aby člověk prátral nejenom v technikáliích, ale v tom co který překlad zahrnuje a jestli tam není zase nějaký háček jako s těmi buffety nebo co to bylo.
Ale fakt by mě upřímě zajímalo: Proboha proč?
Já také nejsem zastáncem překládání zažitých výrazů, na druhou stranu ani text s půlkou slov česky a půlkou anglicky na mě nepůsobí moc dobře. Zrovna u SQL injection je ale překlad docela přirozený, protože je velmi podobný, významově dává smysl a nehrozí, že by si jej někdo nespojil s původním termínem. Zároveň se překlad injekce už standardně používá (jiný překlad jsem nikdy neviděl), takže jsem nechtěl zavádět žádné nové české výrazy. Mně také vždy zarazí nějaký překlad, který slyším poprvé, ale je to myslím hlavně věc zvyku... Stejný výraz hodlám používat i v dalších článcích, takže si třeba zvyknete :)
Nezapomeňte, že sám píšete: "IT světě" (informačně-technologickém světě), přitom je logicky správně "světě IT" (světě informačních technologií).
Na druhou stranu, stejnou "blbost" většina z nás říká téměř denně, a tou je: "IP adresa" (internetově-protokolová adresa) místo logického "adresa IP", tedy adresa Internetového protokolu.
Svět je hořký... sorry jako ;-)
To není jen SQL injection. To je často třeba command line injection jak nám ukázala nedávná chyba v GITu. Já proto preferuju základní pravidlo !nikdy nelep stringy ručně!. Je lepší si napsat query builder (pokud nemohu použít prepared statements) a ten napsat tak, aby správně prováděl escapování a jakéhokoliv vstupu.
Vždycky mě drtí třeba dosazování argumentů třeba v bashi. Zkusit tam někdy poslat třeba uvozovku, to se člověk hned diví, co to dělá.
Nebo takový HTML injection ... lidi jsou pořád nepoučitelní.
a query builder dělá co? Lepí stringy. Tvoje pravidlo bych upravil na: vždy escapuj podle kontextu do kterého výstup posíláš.
Lepení stringů má obrovskou výhodu, rychlým pohledem lze objevit chybu a udělat kontrolu, naopak různé továrničky, buildery, fasády přidávají výrazné bariéry ke kontrole a mohou vznikat složitě odhalitelné chyby ač myšlenka v počátku byla správná. Tím neříkám, že abstrakce je vždy špatně, ale jsou situace, kdy méně je více.
Co dotaz typu CenaOD - CenaDO, který se rozpadá na 4 možnosti:
select * from polozky;
select * from polozky where cena >=:CenaOD;
select * from polozky where cena <=:CenaDO;
select * from polozky where cena >=:CenaOD and cena <=:CenaDO;
V případě lepení si dotaz bez problémů složím (i s případným ošetřením), jak to ale udělám pomocí prepared statements? Musím udržovat různé struktury?
@Ditys Cidae
Pokud jsem správně pochopil tvou otázku, tak
1) si dotaz složíš - tedy dostaneš jeden z těch tvarů, pak
2) ten tvar dáš připravit
3) přidáš do toho parametry a
4) odpálíš dotaz.
případně opakuješ 3) a 4) - někdy jsou 3) a 4) v jednom. Např. můžeš bindovat parametry po jednom podle jména a pak "execute" nebo je do execute poslat jako pole key => value. Záleží na jazyku
*Neznám úplně všechny implementace všech jazyků, ale takto je to obvykle
@Ditys Cidae
Zkus mrknout sem pro inspiraci jak předávat libovolné parametry a skládat z nich dotazy ....
Pojmenované parametry mají dva potenciální problémy:
1. Může být problém udržet unikátnost názvů.
2. Některá API to prostě nepodporují. U JDBC jsem pojmenované parametry popravdě neviděl.
V případě číslovaných to řešit lze. Když si předávám fragmenty SQL, mohu si k nim předávat i pole parametrů, a pak to mohu spojovat všechno současně. Neviděl jsem ještě takovou knihovnu, ale v principu by to nemělo být těžké napsat. Jen pro tak komplikované případy se asi více ujala různá DSL jako QueryDSL a Slick.
Akorát že to případné ošetření zdaleka tak bezproblémové není. S prepared statements z toho můžete udělat jeden dotaz a chybějící parametry předávat jako null, mít zapsané čtyři kompletní dotazy, a nebo ty čtyři dotazy složit ze společné select … from části a měnit jenom tu podmínku – z čehož zase vzniknou čtyři konstanty, na které není jak zaútočit.
Ale tam nejde o to, jak se ten dotaz tvoří. Tam jde o to, že jako parametr nasype něco, co změní význam příkazu. Takže tohle si dělej jak chceš, ale musíš zajistit, že se dovnitř nedostane závadný parametr.
Ono i kdybys prošel string s paramtrem ve smyčce a apostrofy nahradil třeba \047 a uvozovky \042 (dle ASCII), tak ti to nepozmění příkaz a maximálně vrátí, že nic nenašel. A pokud stejnou funkcí "filtruješ" data při ukládání, tak ty nebezpečný znaky můžeš normálně a neškodně používat v dotazech.
Pokud na omezení takové zranitelnosti stačí jeden cyklus a jeden switch pro náhradu některých znaků, tak by za vykecání osobních údajů pomocí SQL Injection měly automaticky padat maximální flastry, co GDPR dovoluje.
Můžete SQL lepit úplně stejně jak jste zvyklý - jen jakýkoliv dynamický obsah, musíte správně oeskejpovat (pokud se jedná o SQL identifikátory) nebo nahradit tzv placeholdry - a někde bokem si urdžovat vazbu placeholder,<->parametr. Pak při volání SQL dotazu musíte použít API, které umožňuje zadat reálné parametry bokem. libpq např. má funkci PQexecParams.
Můžete si to představit tak, že napíšete:
pqexecparam("select * from polozky where cena >= $1", $cenaOd);
pqexecparam("select * from polozky where cena >= $1 and cena <= $2", $cenaOd, $cenaDo);
jen jakýkoliv dynamický obsah, musíte správně oeskejpovat (pokud se jedná o SQL identifikátory) nebo nahradit tzv placeholdry
Jenom bych k tomu dodal, že escapování by mělo být vždy až poslední možnost, protože to není řešení problému, jenom jakási záplata. Escapování prakticky nejde udělat 100% správně, protože 100% funkční escapování musí vstup interpretovat naprosto stejně, jako cílový parser. Mimo jiné chování toho parseru v okrajových situacích se může měnit i z verze na verzi nebo může záviset na prostředí (OS). A znamenalo by to třeba stejně ošetřit nulové bajty, chybné sekvence bajtů v UTF-8 atd. Bezpečně se to dá udělat jenom tak, že vezmete vstup od uživatele a na jeho základě vyrobíte vlastní výstup, který je od toho vstupu bezpečně oddělený. Třeba se takhle dají zpracovávat čísla nebo výčty/enumy. Např. když víte, že v Javě budete mít na výstupu „escapování“ int
, a v něm nemůže být nic pro SQL příkaz nebezpečného – maximálně dostanete výjimku při parsování uživatelského vstupu, protože se jej nepodaří převést na int
.
Tady bych úplně nesouhlasil - pohybuji se ale v jiném prostředí než Vy. Je jasné, že PS jsou bezpečné 100% z principu. Nicméně pokud použiji pro escapování speciální funkce většinou dodávané v driveru, tak by to mělo být 100% bezpečné. Bez escape se u SQL identifikátorů neobejdete - tam vám PS nepomohou - a chca nechca musíte důvěřovat knihovnám. PostgreSQL funkce - https://www.postgresql.org/docs/9.5/static/libpq-exec.html#LIBPQ-EXEC-ESCAPE-STRING bych se asi nebál použít.
PS mají zase jiné nevýhody - které dokáží překvapit i dlouholeté uživatele.
SQL injection není problém, pokud programátor ošetří všechny parametry. Jde o tu důslednost, které musí předcházet pochopení problému - ale je to jednoduchý útok, a i obrana je jednoduchá.
za prvé je tam náročnější protokol, takže po síti se protočí víc paketů - ale s tím na dnešním železe není a neměl by být problém.
za druhé - pokud nepoužívám PS, tak při optimalizaci znám obě strany predikátu např. prijmeni = 'stehule' a tuto informaci mohu využít pro optimalizaci. Hodnoty které se nevyskytují často čtu indexem, ostatní sekvenčně. Pokud mám predikát ve tvaru prijmeni = $1 a v době optimalizace nevím co v $1 je, tak samozřejmě tuto optimalizaci udělat nemohu nebo střílím naslepo - a mohu se trefit, nebo také nemusím. Jsou různá řešení, ale žádné není dokonalé. PS je primárně motivováno opakovaným použitím prováděcích plánů - nicméně někdy vygenerovaný plán pro stehulu není optimalni pro novaka, a není jednoduché tuto situaci detekovat s menší režií než je přegenerování prováděcího plánu.
Pokud se použije speciální API - v postgresu parametrized queries, tak tento problém nehrozí - plány jsou generovány jednorázově pro konkrétní vektor parametrů poslaný jiným kanálem. Bohuže API PQ někde není k dispozici, případně jej programátoři nepoužívají - protože neznají rizika PS.
Já nevěřím tomu, že by se dalo zaručit, že to bude vždy 100 %, i když použiju třeba starší driver pro připojení k novější databázi. Když si představím, že třeba driver podporuje jinou verzi UTF-8 než databáze, nemusí se shodnout na tom, co je a co není platný znak. Samozřejmě jsou to už hodně okrajové případy, a určitě je daleko lepší použít escapování z driveru než se pokoušet escapovat nějak ručně nebo neescapovat vůbec.
SQL identifikátory by se do dotazu vůbec neměly dostat z uživatelského vstupu. Měl bych mít v aplikaci seznam povolených identifikátorů pro konkrétní dotaz (třeba sloupce, podle kterých lze třídit), a uživatel může maximálně vybrat některou z těch hodnot ze seznamu. Je to jak kvůli bezpečnosti, tak kvůli výkonu – na ty identifikátory musím mít připravenou výkonnost databáze, např. indexy pro řazení.
Ono to důsledné ošetření všech parametrů není zas až tak jednoduché, zejména pokud má aplikace více vrstev. To ošetření by se mělo dělat na poslední vrstvě těsně před databází. Nemusí být vždy zřejmé, kde to je, zejména když aplikace používá několik různých přístupů k databázi. Pak nastávají klasické případy „to už je určitě ošetřené někde jinde“ nebo „ošetřil jsem to a je to escapované dvakrát, takže to na jednom místě odstraním“ (a odstraní to v místě, které se volá i jinou cestou a tam to ošetřené není).
Stručně řečeno, při použití escapování se programátor musí dost snažit, aby tam nějakou díru nevyrobil, při použití bindování se musí docela snažit, aby tam nějakou díru vyrobil.
Jak píšete - escape proti SQL injection se musí dělat těsně před databází. Což je třeba případ client side PS.
Je to názor proti názoru - myslím si, že architekturou, postupy mohu napsat bezpečnou aplikaci i bez PS - za dodržení určitých pravidel. Pokud ta pravidla nejsem schopný dodržet, a chci psát bezpečně, pak musím použít PS.
Driveru bych i celkem věřil a v některých jazycích (Java, Python 3) není ani moc prostoru použít omylem jiný encoding nebo dostat nevalidní UTF-8.
Může tu ale za určitých okolností být jiný problém, zvlášť u dynamických jazyků. Mějme nějaké id, které má býť číselné. Pro jistotu ale to escapujme, dostaneme něco jako "SELECT * FROM someTable WHERE id = " + escape(id). Zrádné je, že to funguje a na první pohled může kód vypadat OK, protože escapujeme. Při bližším pohledu ale vidíme, že zde ani nepotřebuju apostrof, abych utekl z původní formy dotazu. Prepared statements ani funkce quote (která vrátí string i s apostrofy) takovýto problém nemají.
@Vít Šesták
Číslo stačí přetypovat na číslo nebo ho sanitizovat jako číslo. Při použití jiné znakové sady funkce escapuje úplně jiné znaky nebo i žádné a pokud jsou zapnuté emulované ne nativné prepared statements, je šance že se zase jenom escapuje a dostat jiný encoding samozřejmě není problém, stačí načítat nějaké externí zdroje, např. xml, csv a už to může být .... teoreticky nikdy nevíš jaký vstup kdo pošle do nějaké funkce.
> Číslo stačí přetypovat na číslo nebo ho sanitizovat jako číslo.
Ano, stejně jako by stačilo kolem přidat uvozovky. Problém ale je v tom, že pokud budeme brát escape jako běžně používanou funkci, může to dopadnout tak, že podobný kód napíše méně zkušený kolega (nebo i zkušený unavený kolega o půlnoci), bude na první pohled fungovat dobře a nebude vypadat vůbec podezřele.
> Při použití jiné znakové sady funkce escapuje úplně jiné znaky nebo i žádné
Zmiňoval jsem Python 3 a Javu, kde řetězec není sekvence bytů, ale sekvence znaků. Jak tu chcete použít špatné kódování, když příslušné API s kódováním nepracuje?
V C to samozřejmě problém být může.
Pokud bych nechtěné escape vnímal jako riziko, tak si obalím driver funkci svojí, kde se rozhodnu podle typu. V Pythonu stejně tak v Javě by to neměl být problém. Client side PS jsou na tom postavené.
Špatně zakódovaná data Vám mohou přijít z venku. Záleží na restriktivitě implementace stringů v tom či oném jazyce. Vůbec netuším jak na tom Python nebo Java, případně další jazyky aktuálně jsou.
Vít Šesták: Převádět všechno na text není dobrý nápad. Může to mít výrazný dopad na výkon, např. když databáze místo nalezení čísla v indexu bude sekvenčně procházet tabulku, všechna čísla převádět na text a ten pak porovnávat s vaším parametrem.
API s kódováním sice nepracuje, ale někde na vstupu je sekvence bajtů, a do databáze se zase odešle sekvence bajtů. Takže jak na vstupu tak na výstupu může být špatné kódování. Chyba se v tom udělá velice snadno. Třeba aplikace bude načítat data jako ISO-Latin 1, pak bude pracovat se znaky, escapovat je atd., a výsledek odešle jako sekvenci bajtů do databáze – a bude tvrdit, že je to UTF-8 (třeba protože to tak bude nastavené jako výchozí hodnota na spojení). Když ta aplikace na vstupu dostane ve skutečnosti UTF-8, bude to v běžných případech fungovat – (např.) dvoubajtové sekvence znaků v UTF-8 načte jako dva znaky ISO-Latin-1, při escapování se nijak nezmění a zase je pošle jako dva bajty na výstup, odkud to databáze načte jako jeden UTF-8 znak. Útočník takhle může do databáze klidně poslat sekvenci 0xEF, 0xBB, 0xBF uprostřed textu, neplatnou sekvenci UTF-8 apod., a escapovací funkce s tím nic neudělá. Jediné štěstí je, že všechny zajímavé znaky v SQL jsou ze sedmibitového ASCII, takže se nemohou vyskytovat ve vícebajtových UTF-8 sekvencích – v takovém případě by obejití escapování bylo triviální. Před tímhle jednoduchým útokem vás pak ale nechrání escapování, ale to, jak bylo navrženo UTF-8, a parser UTF-8 v databázi, který (snad) na neplatných UTF-8 sekvencích skončí chybou.
Útoky neplatným UTF8 znakem jsou docela stará záležitost - tipoval bych, že se jednalo ještě o Postgres možná sedmičkové řady - pokud někdo nepoužívá archaické verze sw, tak bych předpokládal, že je to ošetřené, jak na straně db, tak na straně db driverů. Dívám se do release notes, a byla to záležitost Pg 8.1 https://www.postgresql.org/docs/9.2/static/release-8-1.html - rok 2005.
Ale je dobře, že to zmiňujete - je to dost rafinovaný útok, na kterém se dá hodně naučit. A také pochopit, proč se, velmi důrazně, se nedoporučuje psát vlastní escape funkce, kde málokterý programátor by detekoval nevalidní UTF8.
> Převádět všechno na text není dobrý nápad. Může to mít výrazný dopad na výkon, např. když databáze místo nalezení čísla v indexu bude sekvenčně procházet tabulku, všechna čísla převádět na text a ten pak porovnávat s vaším parametrem.
Netvrdím, že je to ideální řešení, i když myslím, že tak stupidní věc jen tak nějaká DB neudělá. Možná H2.
> Takže jak na vstupu tak na výstupu může být špatné kódování
Pokud používáme java.lang.String (docela realistický předpoklad), tak ten neumí reprezentovat nic špatně zakódovaného. Ano, interně se použije třeba UTF-16 nebo ASCII, ale to ne implementační detail a konstruktor nic nevalidního nevpustí. Takový vstup je buď odmítnut, nebo nějak sanitizován. Třeba tady se to sanitizuje: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#String(byte[],%20java.nio.charset.Charset)
Je tedy celkem jedno, jestli vstup je UTF-8, UTF-16, UTF-7, ASCII nebo třeba GBK. Jakmile se z toho udělá String, musí to být validní, a dál nám případný sanitizovaný nevalidní vstup nemá jak ublížit. Maximálně uložíme nějaký gibberish do DB (pokud špatně dekódujeme vstup), ale na escapování to vliv nemá.
Netvrdím, že je to ideální řešení, i když myslím, že tak stupidní věc jen tak nějaká DB neudělá. Možná H2.
Databáze se nerozhoduje podle nálady, jak bude dělat implicitní konverze, má na to jasně definovaná pravidla. Pokud někdo má číslo, chce porovnávat čísla, ale to číslo převede na string, je stupidní on, nikoli databáze.
Je tedy celkem jedno, jestli vstup je UTF-8, UTF-16, UTF-7, ASCII nebo třeba GBK. Jakmile se z toho udělá String, musí to být validní, a dál nám případný sanitizovaný nevalidní vstup nemá jak ublížit. Maximálně uložíme nějaký gibberish do DB (pokud špatně dekódujeme vstup), ale na escapování to vliv nemá.
To, že mám v Javě validní string, neznamená, že to je ten samý string, jaký byl na vstupu (což může způsobit chybná data v databázi), ani že je to ten samý string, jaký se zapíše do databáze (což v případě ochrany escapováním můževést k SQL injection).
Představte si, že aplikace si myslí, že odesílá data do databáze v ISO Latin-1, ale databáze si bude myslet, že data dostává v UTF-1 (které už se naštěstí prakticky nepoužívá). V aplikaci bude něco jako "SELECT * FROM users WHERE login = '${escape(login)}'"
. V proměnné login
bude útočníkem vložen text ¡'; DELETE FROM users; --
. Funkce escape()
„zabezpečí“ vstup a před ten apostrof např. přidá zpětné lomítko. Do databáze se tedy pošle text:
SELECT * FROM users WHERE login = '¡\'; DELETE FROM users; --'
Text se pošle zakódovaný v ISO Latin-1. Zajímavá je ta sekvence s obráceným vykřičníkem – ten bude v ISO Latin-1 zakódován jako 0xA1
, následující zpětné lomítko jako 0x5C
a apostrof jako 0x27
. No a databáze to čte jako UTF-1, všechny znaky před tím jsou z ASII, takže jsou shodné – ale sekvence 0xA1 0x5C
je v UTF-1dvoubajtová sekvence kódující codepoint U+0106
, tedy znak Ć
. Takže databáze dostane na vstupu následující text:
SELECT * FROM users WHERE login = 'Ć'; DELETE FROM users; --'
Mně to připadá jako učebnicový příklad SQL injection, vám ne? A přitom aplikace pracovala s naprosto validním textem.
Právě proto považuju escapování za nebezpečné. Z hlediska escapování můžete mít všechno správně, ale stačí chyba v konfiguraci připojení do databáze, která se při normálním používání nijak neprojeví – a v aplikaci máte SQL injection. Navíc jak je vidět u z této diskuse, ne každý si záludnosti kódování znaků uvědomuje, a když „to funguje“, neřeší, jestli je to správně.
Pavel Stěhule: To ale píšete o PostgreSQL, pravděpodobně to platí i pro MS SQL a Oracle. Ale třeba s MySQL byly (alespoň dříve) časté problémy, kdy každá strana komunikace používala jiné kódování. To, že prohlížeč poslal UTF-8, aplikační server s tím pracoval, jako by to bylo Windows-1250 nebo ISO Latin-2, ale databáze to chápala zase jako UTF-8, takže to nakonec „fungovalo“, je bohužel příklad z praxe.
Také jsem se setkal s tím, že MySQL aplikace měly nastavené špatné kódování - v 90 letech to byl standard.
Dneska už bych ale očekával, že všichni pojedou na UTF8 - 8bit kódování používá jen pár veteránů a dožívající aplikace.
Vtip je ale v tom, že pokud použijete pro escape funkce driveru, tak je úplně jedno, jestli má klient dobře nebo špatně nastavené kódování. Driver zná kódování serveru a nepošle mu nic, co by server zmátlo. Tedy můžete tam poslat nějaké paznaky, které nebudou sedět, ale escape z driveru vždy zajistí sanitizaci nebezpečných znaků, které by měly specifický význam v kódování použitém na serveru.
Přiznám se, že nesouhlasím s tím, jak se tu argumentuje, že escape je problém pro slabší programátory. Podobný problém je i s PS - jen není na straně security, ale na straně performance. Což je o něco menší zlo. Na druhou stranu, vývojářů, kteří pochopí jak správně escapovat bude drtivě víc než vývojářů, kteří pochopí v čem může být problém s PS.
V ani jednom případu se ale nejedná o jadernou fyziku - a kdo tyhle základní techniky nepochopí, tak by neměl profesionálně programovat. Aplikační programování je samo o sobě většinou technologicky jednoduchá záležitost - komplikace jsou spíš v aplikační doméně, nicméně měla by být nastavená nějaká minimální laťka.
Pavel Stěhule: Rozdíl je v tom, že escapování bude problém vždy, zatímco performance problém prepared statements je jenom záležitost implementace. Je možné implementovat bindování hodnot bez prepared statements, pak to nebude mít žádný negativní výkonnostní dopad – naopak se může ušetřit čas parsování.
Tomu nerozumím. Pokud bude bindování implementováno třeba v knihovně na klientské straně, bude se na server odesílat úplně stejný dotaz s vyplněnými hodnotami, jako kdyby se použilo escapování (ve skutečnosti ta knihovna bude muset interně to escapování implementovat, takže je také náchylná na mnou uváděné chyby). Z hlediska výkonu to tedy bude úplně to samé. A to samé, co může implementovat knihovna na klientské straně, může přece implementovat i samotný databázový server na své straně.
Ostatně třeba JDBC driver pro PostgreSQL funguje právě takhle. Používá pro Java PreparedStatement bindování na klientovi, a teprve když se daný dotaz opakuje, přepne na server side prepared statements.
Nebo jinak řečeno – bindování hodnot přece není principiálně svázáno s prepared statements, je možné to dělat i bez nich. A výkonnostní zlepšení nebo penalizace se týká prepared statements, ne bindování.
No to je právě asi ten problém, že se bindované proměnné, parsování dotazu a plan cache míchá dohromady. Bindování proměnných se dá dělat (a dělá) bez zbývajících dvou, může se to udělat čistě na straně aplikace, která dál databázovému serveru předá klasický SQL dotaz s vloženými escapovanými hodnotami. Je to trochu neefektivní, protože ten SQL příkaz se musí parsovat dvakrát, a navíc se podobné dotazy parsují stále dokola. Ale běžně se to tak dělá – a optimalizovat by se to dalo tím, že se použije formát prepared statement, tj. SQL příkaz se zástupnými znaky místo hodnot. Server si pak může nakešovat AST dotazu a příště ho nemusí parsovat. Až potud to nemá žádný negativní dopad na výkon, naopak by to mělo být efektivnější – místo parsování SQL dotazu se jenom vyhledá už rozparsovaný dotaz v cache. Teprve nad tím rozparsovaným dotazem se pak může předpřipravit prováděcí plán (který samozřejmě bude obecný).
Chápu, že u některých implementací může být problém to takhle oddělit, protože mají třeba parsování dotazu a vytváření prováděcího plánu propojené. Ale implementací SQL databází je spousta.
Když se na straně aplikace použije bindování proměnných, dá se pak v implementaci přepnout, zda se použijí databázové prepared statements, nebo zda se to všechno pomocí escapování vyřídí na aplikační úrovni. V lepším případě se to dá dokonce řídit případ od případu – programátor bude psát pořád stejný kód, a pak jenom příznakem určí, že v daném případě by PS byly neefektivní a má se použít bindování na straně aplikace. Když to bude opačně a programátor bude používat escapování, je to jednak obvykle podstatně náchylnější k chybám, jednak bude muset kód přepsat, když bude chtít použít prepared statements.
Na jednu stranu máš v tomto pravdu, opravdu záměna kódování je nebezpečná.
Na druhou stranu,
1) záměna kódování se netýká jen escapování. Některé codepages se neshodují ani v 7bitovém ascii, takže při záměně kódování lze posílat do db nesmysly i bez escapování. Proto kontrola kódování na obou stranách spojení by měla být úplně stejná samozřejmost, jako escapování/používání placeholderů.
2) je pravda, že Tebou popsaný scénář je "nebezpečný" tím, že může prolézt testy. Nicméně, sám píšeš, že k tomu potřebuješ na obou stranách spojení dnes již obskurní kódování, což samo by mělo být už dnes "nedoporučené chování". Při rozumně nastavené db to nehrozí - a při blbě nastavené db tam mohu mít děr daleko více.
Takže - IMHO je dobře o takovýchto rizicích mluvit. Ale zachovávání "best practicies" odsouvá toto riziko do sféry akademických rizik, takže to nevnímám jako argument proti escapování. Schválně jsem se snažil najít reálný případ bugu na tomto principu, a jediné, co jsem našel, je přes deset let starou díru do wordpressu.
Obzvlášť, když používání placeholderů může zavést pouze "falešnou iluzi bezpečnosti", protože polovina databází dneska placeholdery nahrazuje nikoli server-side, ale na straně klienta, a tedy je zranitelná úplně stejně, jako když člověk escapuje sám.
Daleko podstatnější je podle mne potřeba zdůraňovat používání správných escapovacích funkcí (tzn. že "addslashes" nestačí, že vždy je třeba používat knihovní funkce), tam hrozí riziko díry i při nastaveném shodném kódování na obou stranách.
Logik:
ad 1) Samozřejmost by to měla být, ale není. Případy, kdy na vstupu a výstupu je UTF-8, ale uvnitř se s tím pracuje jako s nějakým osmibitovým kódováním, bohužel nejsou ojedinělé. A často se na to nepřijde, protože všechno zdánlivě funguje správně – protože jakákoli validní sekvence UTF-8 je zároveň i validní sekvencí v daném osmibitovém kódování.
ad 2) Na tom obskurním kódování je snadné ukázat příklad. Vůbec si ale nejsem jistý, zda podobné chyby nejde docílit třeba kombinací různých verzí UTF-8 nebo různého zacházení s neplatnými sekvencemi v UTF-8. O to je to zákeřnější – protože k zneužití teoreticky může dojít i tehdy, když máte na obou stranách stejné kódování.
Ano, problémy s kódováním už jsou hodně málo pravděpodobné, ale pořád existují. Protože escapování není řešení problému, je to jenom hack. SQL příkaz jsou strukturovaná data, je tam samotný příkaz (který by se dal také zapsat strukturovaně) a vedle toho data. Escapování je založené na tom, že se pokusím ta strukturovaná data serializovat do textu a následně je z textu zase obnovit. A to je proces, který je vždycky problematický a vždycky bychom se mu měli snažit vyhnout. Vždyť úplně stejně to děláme s daty uloženými v databázi – také je ukládáme strukturovaná do jednotlivých tabulek a sloupečků.
Proto bych se především snažil escapování úplně vyhnout, a použil bych ho jedině v případě, kdy jiná možnost neexistuje – a pak přicházejí na řadu ta pravidla jako používat pro escapování vždy funkci databázového driveru a escapovat vždy až na poslední vrstvě těsně před databází.
ad1) Ale to "zmatení kódování" je právě ten problém, nikoli to escapování. Prostor pro SQL injektáž může vzniknout v okamžiku, kdy posílám někam něco v jiném kódování, než jaké se tam očekává. A to je prostě blbě, ať už používám escapování nebo placeholdery - pokud mám zmatek v kódování, tak zapisuji do databáze něco jiného než chci. Žádné prepared statements mě v tomdle nepomůžou.
Řešení není nepoužívat escapování, co kdyby byla blbě nastavená databáze, ale mít databázi prostě nastavenou dobře. Tedy neléčit symptomy (navíc jen jeden symptom z mnoha), ale chorobu.
ad2) Nevím, co jsou různé verze UTF8 (to je podle mne jeden standard, takže verze nemá, třeba se pletu), ale v každém případě UTF8 je navrženo tak, aby k tomuto nedocházelo. Žádný znak v UTF8 se nekóduje do 7bit ASCII, všechny 128+ znaky se kódují pouze 128+ znakama. To je jedna z výhod utf8.
A zacházení s "neplatnými sekvencemi" UTF8? Opět řešení symptomů. Neplatné sekvence UTF8 nemám co zapisovat do databáze. Pokud to dělám, tak mám někde v aplikaci chybu, a to nemalou. Opět je třeba řešit příčiny, nikoli následky, když zapisuju do databáze nesmysly, tak už je to prostě špatně.
"Protože escapování není řešení problému, je to jenom hack. SQL příkaz jsou strukturovaná data, "
Právě že nikoli. Bohužel. SQL jazyk to neodděluje. Většina databází nad tímto nestrukturovaným SQL dělá abstrakci rozdělení dat a příkazů, ale v nemalém části případů je toto rozdělení jen iluze: na klientovi jsou data substitucí stringů daná do příkazů úplně stejně, jako když si sám escapuješ. Takže tvoje pravidlo bude kontraproduktivní. Člověk, co věci rozumí a umí si opravdu ohlídat, aby opravdu použil "pravé" prepared statements, tak si umí pohlídat i kódování. A bastlič, kterej si to pohlídat neumí, tak propadne falešnému dojmu, že používá placeholdery, takže to je přece bezpečné - a vyrobí díru, protože mu db knihovna ten injektovatelný dotaz na klientovi vyrobí úplně stejně, jako když "escapuješ".
===
Takže IMHO je jediné správné řešení pohlídat si kódování, a ne dělat "obezličky", aby nešlo chyby v práci s kódováním zneužít. Jakmile máš problémy v práci s kódováním, tak Ti to stejně mrší data.
Escapování je také léčení symptomů, nikoli choroby. Ten zmatek s kódováním je jenom jeden příkladů, jak je možné escapovací funkci zmást.
Unicode bylo několikrát rozšiřováno (původně bylo navrženo jako 16bitové), UTF-8 se tomu muselo přizpůsobovat. Takže to, co je podle starší verze neplatný codepoint, je v nové verzi už platný znak. Opět – že nemáte neplatnou sekvenci zapisovat do databáze je sice hezké, ale shodnou se obě strany na tom, co je neplatná sekvence? To, že mají všechny bajty vícebajtových znaků v UTF-8 nastavené nejvyšší bit, takže nekolidují se sedmibitovým ASCII, a že všechny řídící znaky SQL jsou (snad) v ASCII, platí pro UTF-8 a SQL. Existují i jiná kódování a jiné jazyky. Spoléhat se na to je opět spoléhání se na určité symptomy, ale nemoc se tím neřeší.
Člověk, co věci rozumí a umí si opravdu ohlídat, aby opravdu použil "pravé" prepared statements, tak si umí pohlídat i kódování.
Ne, neumí. Z toho, co jsem napsal, už je snad jasné, že pohlídat si vše, co může selhat při escapování (kódování je jenom jedna z věcí) není v lidských silách. Ano, je nízká pravděpodobnost, že bude nějaký problém s kódováním a útočníkovi se podaří na problém přijít a zneužít ho. Je nízká pravděpodobnost, že bude problém se dvěma různými verzemi parseru SQL a útočník na to přijde a dokáže to zneužít. Ale ta pravděpodobnost není nulová, a pokud má smysl něco rozvíjet a vylepšovat, tak je to bindování hodnot, protože tam se těmhle chybám dá vyhnout stoprocentně.
Takže IMHO je jediné správné řešení pohlídat si kódování, a ne dělat "obezličky", aby nešlo chyby v práci s kódováním zneužít.
A verzi parseru SQL a nastavení systému a locale a bůhvíco ještě dalšího, co může parsování SQL příkazu ovlivnit.
Jakmile máš problémy v práci s kódováním, tak Ti to stejně mrší data.
To právě nemusí být pravda. Třeba ta vlastnost UTF-8, kterou jste popisoval, vede také k tomu, že pokud se k proudu bajtů v UTF-8 v průběhu zpracování chováte jako k nějakému osmibitovému kódování, obvykle se nic nezmrší a na datech to nepoznáte.
- Escapování není žádné léčení choroby. Escapování je v SQL standardu stanovený způsob, jak se píší do SQL příkazu hodnoty.
- "Unicode bylo několikrát rozšiřováno"
Ovšem UTF-8 je uděláno tak, aby toto umožňovalo bez bezpečnostních problémů pro 7bitový subset. Ale to je vlastně vedlejší - při správné práci s databází to není problém ani při escapování, při špatné to je problém i při použití PS (protože ty by v lepším případě měli také selhat při zápisu neznámého znaku, v horším zapíšou do db něco, čemu nebudou rozumět a co bude v případě použití db funkcí na práci s řetězci dělat problém).
Nicméně hezké je, že se o několik řádek níž v podstatě touto vlastností UTF8 oháníš, ale tady ji nebereš v úvahu... :-)
- "ale shodnou se obě strany na tom, co je neplatná sekvence?"
Pokud máš správně nastavenou databázi, tak shodnou, protože knihovní funkce se s databází domluví. A pokud nemáš správně nastavenou databázi, tak kdoví, co Ti bude selhávat dalšího. Řešit blbě nastavenou db zavrhnutím escapování je rovnák na vohejbák.
Navíc, toto je opět problém společnej s prepared statements. I tam, pokud se blbě domluví na kódování, tak dojde k nekorektnímu běhu aplikace: do db se zapíše blbina. Nic z toho, co jsi napsal, není problém specifický pro escapování.
- "A verzi parseru SQL a nastavení systému a locale a bůhvíco ještě dalšího, co může parsování SQL příkazu ovlivnit."
To neřešíš ty, to řeší databázový klient. Pokud používáš blbě napsanýho db klienta, změň knihovnu či databázi. Opět, kdoví co dalšího bude mršit.
- "toho, co jsem napsal, už je snad jasné, že pohlídat si vše, co může selhat při escapování, není v lidských silách"
Negeneralizuj. Že nevíš, co máš zkontrolovat, ještě neznamená, že to neumí jiní. Pokud používáš správné knihovní funkce, tak jediné, co bys měl zkontrolovat, je, že data vkládáš do db ve správném kódování. A to musíš zkontrolovat i u escapování, i u prepared statements. Vše ostatní dělají knihovní funkce, a to i u mysql, kde to bývalo kolem verze 4.1 problém, protože MySQL do té doby měla kódování implementované prapodivně.
- "obvykle se nic nezmrší a na datech to nepoznáte."
"Obvykle" - takoví jsou nejlepší, co píšou tak, že to "obvykle" projde a je "radost" po nich aplikace opravovat. Právě to je společné všem "syptomatickým přístupům", že se řeší jen zjevné problémy, ale hromada dalších jich v aplikaci zůstane. Např. až se někdo připojí do db jiným (a z hlediska kódování správným) přístupem a něco tam zapíše, tak budeš mít z databáze neopravitelný guláš - a při troše štěstí na to nepřijdeš hned, ale až když si někdo ten guláš nechá opravit.
Když máš správně nastavenej "stack" aplikace - klient - databáze, tak tebou citovaná "nevýhoda" escapování zmizí. Pokud ten stack nastavenej nemáš, tak je nebezpečí SQL injekce jen jeden z mnoha průšvihů - stejně budeš do databáze psát nesmysly, i když použiješ PS.
A to si navíc vůbec nereagoval na to, že polovina knihoven s "prepared statements" dělá ve skutečnosti escapování, takže jejich používání je jen iluze bezpečnosti, teda spíše iluze rádobybezpečnosti.
Zdá se mi, že žiješ ve světě mysql 4.0, kde se kvůli blbému návrhu Mysql dokolečka problémy s kódováním opravdu vyskytovaly. MySQL je ovšem už dávno jinde a jiné databáze to měli zpravidla bezproblémově vyřešené od začátku.
Escapování není žádné léčení choroby.
Ano, je to jen „léčení“ příznaků.
Escapování je v SQL standardu stanovený způsob, jak se píší do SQL příkazu hodnoty.
Když ono před čtyřiceti lety ten problém s SQL injection přes internet byl přeci jen o něco menší.
Ovšem UTF-8 je uděláno tak, aby toto umožňovalo bez bezpečnostních problémů pro 7bitový subset.
Hezky píšete o 7bitovém subsetu. A co ten zbytek?
Pokud máš správně nastavenou databázi, tak shodnou, protože knihovní funkce se s databází domluví. […] To neřešíš ty, to řeší databázový klient. Pokud používáš blbě napsanýho db klienta, změň knihovnu či databázi. Opět, kdoví co dalšího bude mršit.
Jasně, současný databázový klient ví, jak bude upraveno parsování SQL v databázi třeba za dva roky. Navíc knihovní funkce může třeba pro interpretaci UTF-8 používat funkce systémové knihovny.
Navíc, toto je opět problém společnej s prepared statements. I tam, pokud se blbě domluví na kódování, tak dojde k nekorektnímu běhu aplikace: do db se zapíše blbina. Nic z toho, co jsi napsal, není problém specifický pro escapování.
Jenže k tomu problému nemusí u běžných dat nikdy dojít. Útočníci často zkouší vstup velmi vzdálený běžnému uživatelskému vstupu – a je dost podstatný rozdíl, jestli takový vstup jenom zapíše do databáze nějaké smetí, nebo databázi smaže.
Že nevíš, co máš zkontrolovat, ještě neznamená, že to neumí jiní.
No zatím se ukazuje, že já aspoň tuším, kde by mohly být chyby, na rozdíl od těch jiných. Vás nenapadlo ani to, že aplikace může používat pro přístup do databáze jinou (starší) verzi ovladače, nenapadlo vás, že ovladač může používat pro čtení UTF-8 systémovou knihovnu, a systémová knihovna na Windows může k nestandardnostem v UTF-8 přistupovat jinak, než systémová knihovna na Linuxu nebo na BSD. A to všechno jsou potenciální skuliny.
Pokud používáš správné knihovní funkce, tak jediné, co bys měl zkontrolovat, je, že data vkládáš do db ve správném kódování.
To, že vy ty problémy nevidíte, neznamená, že je nevidí jiní. Koho by před rokem napadlo, že půjde číst data z paměti sousedních virtuálních počítačů pomocí ovlivňování spekulativního provádění procesoru a měření doby vykonávání kódu.
Když máš správně nastavenej "stack" aplikace - klient - databáze, tak tebou citovaná "nevýhoda" escapování zmizí.
Jo. A pokud mám všude správně ušetřené vstupy do aplikace, tak nemusím SQL injection řešit, protože se do SQL nemůže vložit nic špatného. A pokud mám hodné uživatele, nemusím ošetřovat ani ty vstupy. Akorát zbývá vyřešit takový „detail“, jak zajistit splnění těch podmínek.
Pokud ten stack nastavenej nemáš, tak je nebezpečí SQL injekce jen jeden z mnoha průšvihů - stejně budeš do databáze psát nesmysly, i když použiješ PS.
Ovšem je podstatný rozdíl, jestli mi útočník do databáze zapíše zmršené jméno, nebo jestli tu databázi smaže.
A to si navíc vůbec nereagoval na to, že polovina knihoven s "prepared statements" dělá ve skutečnosti escapování, takže jejich používání je jen iluze bezpečnosti, teda spíše iluze rádobybezpečnosti.
Pořád je to lepší, protože s tím bindováním se dá pracovat, dá se to třeba přesunout na databázový server. S escapováním neuděláte nic.
Zdá se mi, že žiješ ve světě mysql 4.0, kde se kvůli blbému návrhu Mysql dokolečka problémy s kódováním opravdu vyskytovaly. MySQL je ovšem už dávno jinde a jiné databáze to měli zpravidla bezproblémově vyřešené od začátku.
Nikoli, pouze vím, že zpracování textu a převod mezi různými kódováními mohou být komplikovanější, než to na první pohled vypadá.
"Jasně, současný databázový klient ví, jak bude upraveno parsování SQL v databázi třeba za dva roky."
Ne, server za dva roky, pokud změní interpretaci SQL, tak navýší číslo protokolu a buďto starého klienta odmítne, nebo mu nastaví legacy mode. Pokud používáš databázi, jejíž vývojáři na takovéto základní programátorské postupy kašlou, pak jsi sebevrah - a možnost SQL Injection při použití obskurních kódování je ten nejměnší průšvih, kterej budeš muset řešit.
Pokud je v klientu/serveru takováto neřešená nekompatibilita, PAK PROSTĚ NEMŮŽEŠ POUŽÍVAT TAKOVÉHO KLIENTA A TO ANI S PREPARED STATEMENTS, PROTOŽE TI ZMRŠÍ JAK ESCAPOVANÁ DATA, TAK DATA PŘEDANÁ V PS.
"Navíc knihovní funkce může třeba pro interpretaci UTF-8 používat funkce systémové knihovny.""
Může, pokud je bude používat korektně a deterministicky. Pokud to její vývojář neumí zajistit, tak viz předchozí odstavec. Opět zde není rozdílu mezi PS a escapováním.
"A pokud mám všude správně ušetřené vstupy do aplikace, tak nemusím SQL injection řešit, protože se do SQL nemůže vložit nic špatného"
Pleteš si pojmy. Ošetření vstupů Ti zajistí, že ti webová aplikace nespadne na 500 pro zápis stringu do číselného políčka. Teda, že v proměnné, kde má být číslo, bude číslo. Pokud je v daném místě validní libovolný řetězec, tak ošetření vstupů SQL injekci fakt nezabrání.
To, co brání SQL injekci je escapování, tedy ošetření výstupu (jo, dá se na to nahlížet jako ošetření vstupu do databáze, ale z hlediska programu je to výstup), tedy formátování dat tak, aby ten, kdo s tebou komunikuje (v tomto případě db)
správně interpretovala Tebou poskytovaná data. A ano, pokud máš všude 100% zaescapováno (a dobře nastavenej stack), tak SQL injection nemusíš řešit. Už jsi ho totiž vyřešil.
"Ovšem je podstatný rozdíl, jestli mi útočník do databáze zapíše zmršené jméno, nebo jestli tu databázi smaže."
Ano, je to podstatný rozdíl. Smazanou databázi obnovím ze zálohy a díru zalepím. Zatímco s databází se silent data corruption kvůli zmatku v charsetech, na kterou se přišlo po roce provozu, můžu tak leda vyhodit a jít si hledat jiné místo.....
O tom, co je opravdu horší se můžem hádat hodiny. V každém případě je OBOJE špatně. A opatření, které řeší jen jednu
z těchto dvou věcí, za cenu, že nemohu používat standarní nástroje k práci s databází (což platí u většiny opensource nástrojů, kde ty standardní nástroje používají strandardní funkce, které ač emulují PS, tak ve skutečnosti escapují),
je defakto k ničemu. A jediné účinné opatření, které chrání proti oběma, je mít správně nastavenou databázi: a pokud ji mám, tak mi z escapování neplyne žádné riziko.
"Nikoli, pouze vím, že zpracování textu a převod mezi různými kódováními mohou být komplikovanější, než to na první pohled vypadá."
Ano, to třeba mohou. Ale jsou dvě možnosti: buďto to neumím. Pak je aplikace špatně a mrví data. Nebo to umím. A pak není escapování problém.
btw.: kolik děr za poslední dobu zalořené na Tebou raženém principu bylo v poslední době nalezeno, že to považuješ za takového strašáka.
Logik: Vy stále vycházíte z předpokladu, že všechny vrstvy softwaru pod vámi jsou dokonalé, a vy už k tomu jenom napíšete dokonalou aplikaci. Ne jen bezchybné, tedy plně v souladu se specifikací, ale dokonalé – tedy bezchybné a navíc tam, kde není specifikace dostatečně přesná, fungující přesně tak, jak by se vám hodilo.
Bindování proměnných odděluje kód od dat, escapování míchá vše do kódu. Je to jako rozdíl, když si z internetu stáhnete txt a exe. Ano, můžete mít chybu v prohlížeči txt souborů a zákeřný txt soubor povede ke spuštění útočníkova kódu. A můžete se pokoušet analyzovat exe antivirem a spouštět jen bezpečný kód. Ale principiálně data/dokument jsou bezpečná a musí být v programu chyba, aby bylo je možné zneužít, zatímco exe je principiálně nezabezpečené, a maximálně se můžete pokoušet zalepit díry, na které už jste přišel. Úplně stejně je to s bindováním uživatelských dat a escapováním SQL dotazu – bindování je principiálně bezpečné, a aby bylo možné ho zneužít, musí tam být nějaká chyba. Escapování je principiálně nezabezpečitelné, akorát můžete zalepit všechny místa, která vás napadnou, že by mohla být roziková – což ale nikdy nebudou všechna riziková místa.
To, že jsou někdy prepared statements implementovány escapováním je právě příklad té chyby v implementaci bindování – tu chybu je ale možné opravit. U escapování principiálně nelze mít nikdy jistotu, že neexistuje nějaká další kombinace, na kterou jste nemyslel – a ta kombinace navíc může vzniknout s každou novou kombinací všech zúčastněných komponent. Takže nejen driveru a databáze, ale také knihoven, systémových knihoven atd.
Takže správná otázka není, kolik děr v escapování bylo v poslední době nalezeno, ale jak snadné či obtížné je mezi všemi potenciálními chybami najít další takovou, která je prakticky zneužitelná.
> Databáze se nerozhoduje podle nálady, jak bude dělat implicitní konverze, má na to jasně definovaná pravidla.
OK, můžeme zkusit explain na reálné tabulce. Myslím, že to bude teoretický problém.
> Mně to připadá jako učebnicový příklad SQL injection, vám ne? A přitom aplikace pracovala s naprosto validním textem.
Aplikace vygenerovala naprosto správný SQL dotaz, driver ho pak ale zakódoval do jiné podoby než v jaké byl pak interpretován DB. To vypadá na bug v driveru nebo DB.
@Vít Šesták
On je to problém i v jiných jazycích než C. No a v Pythonu a Javě je sekvence znaků nakonec co? Taky sekvence nějakých bytů, nakonec bitů, v nějakém tvaru. Ono je jedno jestli s takovým kódováním API pracuje nebo ne. Nakonec to přečtě, jenom špatně vyhodnotí.
A ve finále, vidíš, místo přetypování na číslo, co že správně i pro vyhledávání v DB - pokud je tam jako číslo - není escapovací funkci potřeba použít vůbec, takže tato část debaty by odpadla, spoléhání se na escape funkci v této části debaty by odpadlo taky.
> No a v Pythonu a Javě je sekvence znaků nakonec co? Taky sekvence nějakých bytů, nakonec bitů, v nějakém tvaru. Ono je jedno jestli s takovým kódováním API pracuje nebo ne. Nakonec to přečtě, jenom špatně vyhodnotí.
Interně je to sekvence bytů. Když z neplatné sekvence ale chci udělat UTF-8 string, nepovede se mi to:
~% python3
Python 3.6.6 (default, Sep 23 2018, 14:46:00)
[GCC 4.2.1 Compatible Android (4751641 based on r328903) Clang 7.0.2 (https://a on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> bytes([25, 233]).decode("UTF-8")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 1: unexpected end of data
> A ve finále, vidíš, místo přetypování na číslo, co že správně i pro vyhledávání v DB - pokud je tam jako číslo - není escapovací funkci potřeba použít vůbec, takže tato část debaty by odpadla, spoléhání se na escape funkci v této části debaty by odpadlo taky.
Ale já píšu o něčem jiném. Dávat do dotazu čísla jako string samozřejmě není ideální, ale samo o sobě to není problém. Představte si, že toto dostanete v code review a rychle to prolítnete. Při rychlém pohledu nemusíte vidět nic podezřelého, escapuje se přece správně. Má to dvě roviny:
1. Jak takovou chybu neudělat?
2. Jak takovou chybu učinit viditelnější při code review?
Přetypování řeší rovinu #1, ale pokud budeme sypat do DB escapované dotazy ve stylu "WHERE city = '" +escape(city)+"'", podobná chyba (třeba od nového kolegy) bude vypadat podobně jako okolní kód a nebude nápadná, nevyřešíme tím tedy #2. Pokud budeme používat parametrizované dotazy, podobný kód bude asi celkem bít do očí a upoutá pozornost.
U quote je to možná trochu sporné – pokud budeme mít k dospozici jak quote tak escape, možná podobný kód až tak nápadný nebude.
@Vít Šesták
A to je právě to. Musí to někdo "převést" na utf, takže to je potenciální slabinka použití PS. Ale v Pythonu možná ne ... ok, nechme to.
Nemyslím si. Jsou vstupy kde to přetypovat jde. Např. výběr produktů z databáze podle pole idček které přijdou z databáze. IMO není důvod to nepřetypovat v kódu "explicitně" a tím pádem tudy se cesta k narušení dotazu uzavře dokud to přetypování někdo nesmaže. Případně další jasné vstupy, např. bool ...
Pak jsou vstupy kde to jednoznačně ošetřit nejde a tam už to pak záleží na jazyku, implementaci a databázi, případně použitý fw, def nastavení apod .... To takto obecě IMO nelze říct. Každopádně je potřeba dávat pozor na to co všechno daná funkce escapuje a jak, protože z quote se rádo utíká ;-)
> Musí to někdo "převést" na utf, takže to je potenciální slabinka použití PS
Nerozumím. V té ukázce jsem vzal pole bytů a snažil jsem se jej _dekóödovat_ jako UTF-8. Dál (kdybych tam dal něco dekódovatelného) bych měl string, u kterého mě nemusí zajímat, jestli původně šlo o UTF-8, Windows-1252 nebo třeba GBK. Případné špatné kódování ošetří funkce decode.
A pokud funkci decode nezavolám, nemám string, ale bytes nebo nějaký podobný objekt. A s ním nemohu moc pracovat jako se stringem:
>>> 'asds'+bytes([25])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: must be str, not bytes
Escapovací funkce by jej měla odmítnout nebo zpracovat speciálně. I kdyby ho escapovací funkce zpracovala nějak speciálně (třeba by vrátila 0xHEXA), maximálně by se mi povedlo předat nevalidní UTF-8 databázi (která by to snad odmítla), nepovede to k poškození integriti SQL dotazu.
@Vít Šesták
I tak je na programátorovi aby to dekódoval do správného (stejného) kódování aby to správně prošlo escapováním. A různé jazyky mají různé implementace PS. Nějak jsme do toho spadli, ale netvrdím že je to přímo problém Pythonu nebo v Pythonu, jenom obecně že je potřeba si dávat pozor na to co se escapuje, jak se escapuje a jak jsou implementovány PS.
Pokud je řeč přímo o Pythonu, tak to není pro mne ...
Je na programátorovi, aby dekódoval do správného kódování. Pak pracuje se stringy, knihovně pro DB nejspíš taky podá string (bylo by dost šílené, kdyby ho chtěla jako bytes…) a enkódování je na knihovně pro DB.
Pokud špatně dekóduje, může do DB poslat gibberish, ale nepošle nevalidní UTF-8 ani nic takového.
Pokud nedekóduje, pak mu to nebude sedět typově.
Java je to stejné v bledě modrým (akorát některá API místo výjimky nahrazují „otazníčkem“), JS patrně taky.
@Vít Šesták
Pokud se knihovně pro escapování podá string ve špatném kódování, tak je možní že správně neescapuje všechny znaky. Problémem pak může implementace PS, protože pokud se použije emulovaný PS, který obecně šetří komunikaci, tak si poskládá PS sám a escapuje string podle kódování které má nasatavené při inicializaci. Takže v případě kolize kódování nemusí escapovat nic a pak to pošle do databáze, která stringy může teoreticky číst v úplně jiném kódování. Jenom říkám, že i při pouhém poslání stringu do PS je potřeba dbát i na toto a přítomnost quotace už je jenom detail pokud se toto objeví ...
@Vít Šesták
Psal jsem to obecně " ... jenom obecně že je potřeba si dávat pozor na to co se escapuje, jak se escapuje a jak jsou implementovány PS". Jsou to obecná doporučení.
Nejsem hacker ale nevím o tom že by třeba v Javě nešlo načíst třeba latin1 a poslat to někam kde se to čte jako třeba latin3 - třeba xmlka ...
Pěkný článek.
Pokud je ale cílen na začátečníky (dělá na mne ten dojem), tak by to chtělo trochu přeformulovat tu část o prepared statements.
Úplný začátečník by mohl dostat dojem že použití prepared statements stačí k zajištění bezpečnosti.Přitom je to právě použití bindingu/parametrizovaných povelů co pomůže - i když to je spojeno s prepared statements, a občas se to zaměňuje.
Ale lze udělat i prepared statement bez bindingu a začátečníci mají občas sklony ke zjednodušování.
Také by bylo fajn zmínit "bindování" výstupu dotazu - jako protiklad k parsování hodnot z textu. Což je takový opačný problém částečně zmíněný jako " SQL injekce druhého řádu".
Aby to byl skutecny prepared statement (a ne jeho "emulace" na klientovi), tak to musi fungovat na strane serveru. Tj. serveru se posle template, on si ho naparsuje, udela query plan ad. a pak se do nej uz posilaji jen hodnoty.
Pokud to nejde (at uz na strane klientske knihovny nebo serveru), tak se da podobneho vysledku dosahnout pomoci stored procedury (v podstate obaleny dotaz) za cenu vice rucni prace.
Ne. Stored procedure je kód uložený na serveru, je to imperativní kód (posloupnost příkazů), a není v SQL, ale buď v nějakém jazyku podobném SQL, rozšířením pro potřeby psaní imperativího kódu – např. Pl/SQL. Nebo mohou být psané i v jiných jazycích, třeba Python nebo Java. Prepared statement je normální SQL příkaz (třeba SELECT), který se jenom rozparsuje a připraví předem a pak se může volat opakovaně, většinou s různými parametry (předanými hodnotami).
Ne, kód procedury je naprosto to samé "čisté" SQL, ve kterém se v PostgreSQL píše "přímo".
Není to žádné "rozšíření" postgresího SQL jazyka alá PL/pgSQL - teda kromě placeholderů za parametry, což je ale právě to, co to dělá hodně příbuzné k prepared statements.
Jo, striktně ve standardu SQL to není, ale ve standardu SQL není co vím specifikován jazyk uložených procedur vůbec, ani žádný ze SQL dialektů v libovolné databázi neodpovídá žádnému SQL standardu, takže v tomto ohledu by bylo rozšíření SQL vše. Naopak postgresí SQL je z množství SQL dialektů jeden z těch, co se standardu drží co možná nejvíce.
Definice uložených procedur JE součástí SQL standardu, konkrétně featura T-321 SQL 1999, a to dokonce přímo "Core SQL"....
Takže ano, rozumím Ti . Snažíš se svoje předchozí vyjádření překroutit, abys nemusel uznat omyl
- ale - protože evidentně toho o SQL procedurách zas tolik nevíš - tak do toho jen víc zbředáváš :-)
PS: Sorry za sarkastický tón, ale naběh sis...
Když chcete být puntičkář, měl byste být důsledný a netvářit se, že kód (implementace) procedury, celá procedura a definice procedur ve standardu je jedno a to samé. Jinak se klidně můžeme dohadovat, zda
CREATE PROCEDURE select_data() LANGUAGE SQL AS $$ SELECT * FROM tbl; $$;
je SQL dle standardu, ale dohadujte se beze mne.
Snažíš, se vybruslit, snažíš..... Až na to, že původně jsme se bavili o SQL a ty jsi tvrdil, že se v SQL vůbec procedury nepíšou, následně pak že SQL procedury je nějaké rozšíření. Co tvrdíš vlastně teď jsem z Tvého postu odhalit nedokázal, evidentně jsi se poučil a radši nepíšeš nic konkrétního, aby Ti to zas nebylo vyvráceno.
Pravda, že zrovna zápis SQL procedur v Postgresu není 100% konformní se SQL standardem, ale to není v podstatě žádný SQL dialekt: To bys pak musel tvrdit, že se v reálu nikde nepíše v SQL, protože každá implementace SQL má nějaké nestandardnosti.
Prostě postgresql je dialekt SQL a SQL funkce do něj naprosto inherentně patří, stejně jako patří do standardu SQL, takže Tvé (implicitní) tvrzení, že se v SQL nepíšou uložené procedury bylo blbina, stejně jako byla nepravda, že je to nějaké rozšíření SQL.
A to, že místo uznání, jo, to jsem nevěděl, sorry, se z toho snažíš takhle slovíčkařením vybrušovat, když už jsi do toho zabrušoval.... To je tvoje vizitka, ne moje.
Už to chápu, vy jenom špatně čtete. Já jsem nepsal, že se v SQL vůbec procedury nepíšou, napsal jsem, že se nepíšou v (čistém) SQL, ale mohou být v jazyce podobném SQL. Například jazyk, který vezme z SQL některé konstrukce (a jiné zakáže), je jazyk podobný SQL. Ale uznávám, že se to dá nazvat i tak, že je to SQL, akorát si prostě použití v uložených procedurách vyžádá nějaká omezení. Že „SQL procedury je nějaké rozšíření“ jsem také nepsal. Příklad kódu mi připadá dost konkrétní. Howgh.
Standard SQL, dokonce i v nejužší "core standard" definuje SQL funkce, které nazývá SQL funkce. Takže podle Tebe standard SQL definuje jazyk podobný SQL a mylně ho nazývá SQL? Nebo jak to mám chápat?
Tvůj názor je podobně "divnej", jako kdybys tvrdil, že funkce v Cčku nejsou napsané v Cčku, protože uvnitř funkcí Cčka nemůžeš použít veškeré konstrukce Cčka.....
Tady máš odkaz na gramatiku SQL 1999, kde jsou SQL uložené procedury definované:
https://ronsavage.github.io/SQL/sql-99.bnf.html
To je samozřejmě nesmysl. Nevím, co myslíte čistým SQLkem - patrně jeho relační část . Důvodem existence uložených procedur je rozšíření funkcionality volané na serveru o procedurální kód - a pokud bych vzal nejrozšířenější Oracle syntax, tak do těch 99% to bude hodně daleko. Příkaz CALL, kterým se uložené procedury spouštěly dlouho nebyl částí SQL - a asi bych lehko spočítal lidi v republice, kteří vědí o SQL/PSM.
Ano, svým způsobem jde o totéž. V obou případech je v databázi oddělená fáze přípravy prováděcího plánu a fáze samotného provedení.
Prepared statement se vytváří v SQL jazyce SQL příkazem PREPARE a spouští se SQL příkazem EXECUTE. Připravený dotaz můžete spustit opakovaně. Díky chybějící fázi přípravy prováděcího plánu tak může být při opakovaném volání dotaz vyvolaný příkazem EXECUTE rychlejší.
Stored procedure se liší způsobem vytvoření, použitým jazykem a způsobem vyvolání.
U rozumné databáze jde prostředky databáze samotné a neměl by na to mít vliv aplikační jazyk.
Prepared statements - předpřipravené dotazy - je technologie, která původně umožňovala (a umožňuje dodnes) redukovat režii optimalizátoru. SQL příkaz si můžete představit jako zdrojový kód programu ze kterého se generuje strojový kód. Je tam celá řada optimalizací - kde se mají použít indexy, v jakém pořadí se mají spojovat relace, jaké se mají použít algoritmy pro spojování relací, agregaci, .. Výsledkem práce kompilátoru je strojový kód - výsledkem práce SQL optimalizátoru je prováděcí plán dotazu.
U komerčních databází se implicitně ukládá do plan cache. U open source databází se po vykonání zahazuje. Opakované použití plánu si lze vynutit vytvořením tzv předpřipraveného dotazu (PREPARE) - kdy se prováděcí plán pojmenuje a uloží do plan cache. Speciálním příkazem (EXECUTE) se dohledá plán v plan cache a nechá se vykonat. Poté, co se objevil problém SQL injection se ukázalo, že PS jsou principiálně imunní vůči těmto útokům.
Jelikož PP používají komplikovanější protokol, jehož použití může být pro dotazy, které se neopakují neefektivní, přišlo JDBC s tzv. client side PP. API je pro vývojáře stejné, implementace úplně jiná - dotaz je rozparsovaný na klientské strane, a před vlastním voláním JDBC znovu sestaví dotaz a parametry nahradí oescapeovanými hodnotami. Dotaz se pak vykoná klasicky. Např. Postgres JDBC prvních 5 iterací dotazu řeší na klientské straně, a až pro 6 iteraci se vytvoří server side PS.
Uložené procedury je kód, který se spouští na serveru z "user space". Dost často se může jednat o kód, který obsahuje SQL příkazy, a pokud se použije nativní jazyk pro uložené procedury - PLpgSQL, PL/SQL, SQL/PL - tak pro embedované SQL příkazy jsou automaticky vytvořené PS.