SQL injection, počet dotazů, kódovani souboru

Zdravím všechny,

snažím se splichtit RS pro blog, a řeším pár problémů, na něž jsem zatím odpověď nenašel...

Jako první bych se chtěl zeptat, jak zabezpečit SQL dotazy před SQL injection. Přečetl jsem o tom pár článků, ale moc jsem z nich moudrý nebyl... Všechny SQL dotazy mám v nějakém takovémto tvaru:

výběr konkrétního článku podle /?text=pekny-nazev-clanku
$vypis = @mysql_query("SELECT * FROM `clanky` WHERE `permalink`= '" . addslashes($_GET['text']) . "'");

a pokud má být v GETU pouze číslo, tak potom např.:

if (!isset($_GET['page'])) { $_GET['page'] = 0; }

// tohle jsou myslim kategorie :)
$vypis = mysql_query("SELECT * FROM `clanky` WHERE `catid`= '" . $cat[0] . "' order by `id` desc limit " . intval($_GET['page']) . ", " . intval($p) . "");

$p je nastavitelná proměnná, určující limit článků na jedné stránce, a podle $page začne výpis - prostě klasický stránkování LIMIT 0,10

Budu tedy rád, pokud mi poradíte lepší řešení, tedy pokud tohle není dostačující. Zatím mě jen napadlo napsat si fci, která by zkontrolovala přímo REQUEST_URI na nepovolené znaky, a proměnné kt. mohou nabývat jen číselných hodnot (kdybych to náhodou zapomněl projet přes intval() a při jakékoli nekalosti hned hodila header 404 Not Found. A pokud tedy kontrolovat záludné znaky, tak které? IMHO addslashes by mělo stačit...

Druhý dotaz je takový: potřeboval bych nějakou fci, která by spočítala všechny provedené SQL dotazy... mí rádcové hledali ale nenašli... :(

A nakonec možná trapný dotaz, ale přesto... mám všechny soubory v UTF, akorát bych potřeboval, aby config (kde jsou JEN definované konstanty, údaje pro připojení k db a samotné připojení k db) mohl být uložen v jakémkoli kodování. Jenže mi ho asi začarovala zlá baba jaga :D a když soubor uložím v čemkoli jiném, začně mi to zasílat nějaký headery... Tím pádem warning, notice atp, a headery v dalších souborech začnou zlobit.

No raděj ještě jednou: lze nějak dolcílit toho, aby mohl být jeden soubor v jiném kódování, aniž by zasílal header? Sám nic neechuje, tedy pokud není přímo zavolána jeho adresa v url, pak jen echo "pápá"; a die();

PS.: Error reporting je zatím na E_ALL, register globals off.

PPS.: ještě řeším bezpečnost administrace, ale o tom si zatím nechám zdát pěkný sny, a možná na to přijdu sám ;-)

Díky za odpovědi,
Mike
Nevím, jestli jsi četl toto: http://php.vrana.cz/vypnuti-magic_quotes_gpc.php

Po převrácení fce by to mělo na SQL injection stačit. No a k tomu "pokud má být v GETU pouze číslo" tak bych asi kontroloval is_numeric(), případně is_int().

No a k tomu souboru v jiném kódování: http://www.dgx.cz/trine/item/autoczech-aneb-automaticka-detekce-kodovani

K zabezpečení administrace by mělo stačit $_SESSION mírně rozšířené o session_regenerate_id(); a nějaké ty vychytávky jako javascriptový MD5 pro přenos šifrovaného hesla už od uživatele, atd ...
» Tom
díky za odpověď :) no, k tomu článku na php.vrana jsem se nedostal, díky.

pro kontrolování čísla v getu mi připadne vhodnější právě intval, protože vrací přímo hodnotu, kdežto s použitím is_numeric a is_int bych musel přidat další podmínku... navíc za většinou takovýchto sql dotazů následuje podmínka

if (@mysql_num_rows($vypis)>0) { }
else { /* error 404 */ }

takže by to snad mělo svůj účel splnit... dík za link na překodování, ale musím teda říct, že jsem z toho nějak nepochopil, jak překódovat includovanej soubor...
if (@mysql_num_rows($vypis)>0) { }
else { /* error 404 */ }

Ten zavináč je snad nejhorší, co můžeš udělat. Buďto vypni hlášení chyb pomocí error_reporting(0), vlastního error handleru, nebo to napiš tak, aby to nevracelo chybu. Takhle si akorát přiděláš starosti při případném hledání chyb - osobní zkušenost.
Teď to řeším tak, že nepoužívám přímo MySQL_Query(...SQL...), ale mám vlastní funkci, která toto obsahuje a podle výsledku buďto stopne zbytek skriptu, vypíše chybovou hlášku a vloží patičku stránky, nebo předá data dál.

K AutoCzechu: jen tak mě napadá toto: neincludovat, ale načíst do proměnné, tu překódovat a:
a) uložit zpět do souboru s požadovaným kódováním a teprve teď includovat
b) zpracovat proměnnou jako PHP kód
víš o tom, že s těma zavináčema máš naprostou pravdu? :-) dám je pryč. error_reporting bude ve finále na 0.

ke struktuře stránky - řeším to obdobně, akorát nic nezastavím... index sám o sobě nic nevypisuje, akorát obsahuje asi ze 5 podmínek, které podle GETu vykonají patřičný dotaz, při úspěchu naincludují ./template/index.php, který obsahuje už jen fce na výpis článků, sidebaru atp, tohle else { /* error 404 */ } zase naincluduje 404ku...

asi takto:

<?php
require_once "./config.inc.php";
require_once "./functions.php";

time1();
sqlerror();

if (isset($_GET['text'])) {

 $vypis = mysql_query("SELECT * FROM `e_clanky` WHERE `permalink`= '" . addslashes($_GET['text']) . "'");

 if (mysql_num_rows($vypis)>0) {
  the_comment_insert();
  require ABSPATH . "template/post.php";
 } else {
  # to do: error
 }

} else if (isset($_GET['feed'])) {

 if ($_GET['feed']=="rss") {
  $start = 0;
  $vypis = mysql_query("SELECT * FROM `e_clanky` WHERE `status` = 0 order by `id` desc limit " . intval($start) . ", " . intval($p) . "");
  $pocet = mysql_num_rows(mysql_query('SELECT `id` e_clanky'));
  require ABSPATH . "template/rss.php";
 } else if ($_GET['feed']=="rss-comments") {

  if (isset($_GET['clanek']) && !empty($_GET['clanek'])) {
   $vypiscommenty = mysql_query("SELECT * FROM `e_komentare` WHERE `clanekpermalink`= '" . addslashes($_GET['clanek']) . "'");
  } else {
   $vypiscommenty = mysql_query("SELECT * FROM `e_komentare` order by `id` asc");
  }

  require ABSPATH . "template/rss-comments.php";

 } else {
  # to do: error
 }

/* atd... */

tohle je zhruba základní struktura indexu, kterou už měnit nebudu... nainkluduju config, funkce, a až podle getu provedu konkrétní sql dotaz a inkludnu správnej soubor...

a co se týče toho překódování, tak nápad to není špatný, ale už teď mi to připadne zbytečně složité, takže nevím, jak tohle budu řešit...

PS.: snad jsem to správně odsadil... :)
Neprochazel jsem to nejak detailne, ale nejlepsi na praci s databazi (a celym webem) je asi nejlepsi pouziti trid.

Na mem prave vyvijejicim se projektu (vyviji se sam, ja jen diktuji kod (-: ) mam velkou tridu ktera pri inicializaci kontroluje ban, stav webu.

Zaroven funkce sql(..) pocita SQL dotazy a v pripade chyby do footeru pripise (obarvene) chybu v dotazu a co hlasi mysql_error().

Staci jen znat (aspon zaklady) trid (pisu pro PHP4, nechapu jaky je rozdil PHP4<>5, to moje funguje na obojim :D ) a trosku premyslet..
OndraSter:
rozdíl mezi kódem pro PHP4 a PHP5 je asi tento:
- pokud píšeš pro PHP4, funguje to v PHP4 i 5
- pokud píšeš pro PHP5, nefunguje to v PHP4 a někdy ani v PHP5 ;)

Ne, vážně: rozdíl je jen v pár nových funkcích, několika jazykových konstruktech a v rychlosti. I když se všude pětka oslavuje jako rychlejší, není to pravda, rychlejší je čtyřka.

No, nevím jak ty, ale já si myslím, že uživateli může být úplně ukradené, jaká chyba se na webu objevila. Obsah chyby si můžeš jako admin zaznamenat pomocí error handleru a potom v klidu a teple domova pročíst.

Mike: nějak mi asi uniká souvislost mezi SQL chybou a 404kou?!?
Tom: já tam něco takového psal? :)

no... 404ku naicluduju, pokud má sql dotaz nulový výsledek. jak si říkal, návštěvníkovi to bude šumák, jestli se někde vypíše mysql_error()... a pokud narážíš na mou fci sqlerror(); tak ta je zavolána pouze v případě, že se nepovede připojit k databázi... (výpadek... atp)

OndraSter: řešení to samozřejmně je, ovšem prvně by o tom musel něco vědět :)

vše tvořím pouze s pár skušenosmi a trochou logickýho myšlení... sám jsem totiž překvapen, že jsem došel až takto daleko, bez nekonečného otravování na diskusních forech, jak mívají ve zvyku jiní :-D pouze jedno téma tu a druhé na jnw ;-)
Mike: No já jsem to z toho pochopil takto:
při úspěchu dotazu vložím šablonu s výpisem článků, při neúspěchu vložím 404ku. Je to sice pěkný, ale když už si hrát se stavovým kódem HTTP, tak bych asi radši volil "pětistovku" (500 - Internal Server Error) a vypsat uživateli že je to rozbitý.

Já to sice momentálně mám na manual.wz.cz všelijaký, ale pomalu pracuju na updatu, který to takto sjednotí:
301 - Moved Permanently => Trvale přemístěné stránky, včetně Headeru na nový cíl.
403 - Forbidden => Pokud se někdo snaží dostat tam, kde nemá co dělat
404 - Not Found => Pouze při pokusu načíst neexistující soubor
410 - Gone => Trvale odstaněná položka ze serveru
500 - Internal Server Error => Jakékoliv selhání skriptu, které předčasně ukončí generování stránky + rozlišení na SQL chybu (Text "dotaz na databázi vrátil chybu"), neúspěšné provedení operace (Text "Požadovaná stránka nemohla být zobrazena, protože se během jejího načítání objevila závažná chyba") no a dle okolností možná další ...
503 - Service Unavailable - Pokud se zrovna budu vrtat v systému tak, že cizí přítomnost na webu bude nežádoucí.
Tom: díky za výčet errorů, a vysvětlení i těch méně používaných :) sám používám pouze 301, 401, 403 a 404...

můj výpis si pochopil správně, ovšem mám pocit, že tvoje řešení by návštěvníky budoucího blogu přivedlo do pekel... volat při každé chybně zadané adrese či nulovému stavu výpisu z db (například prázdná kategorie) pětikilo se mi vůbec nelíbí... no nejde o to, že si hraju s headerema... naicluduju vždy jen soubor 404.php ze složky s šablonou, kde nějaká funcke vypíše "Článek nenalezen", "kategorie nenalezena nebo je prázdná", "nezadávejte hovadiny do url" atp... samotnej error document 404 bude jinej. pak by stačil obyčejnej překlep při zadávání adresy ručně, a pech...

takhle: co se týče té kostry indexu, tak je asi taková: pod každou podmínkou se zavolá sql dotaz, naincluduje patřičný soubor:

if (podminka1) {
     jedná-li se o konkrétní existující článek, vypiš ho,
     jinak error.
} else if (podmínka2) {
     kategorie.
     zda-li je výsledek větší než 0 už čekne funkce. (myslím)
} else if (podmínka3) {
    RSS článků || globální RSS komentářů,
    RSS komentářů ke kontrétnímu článku || error
} else if (podmínka4) {
    hledáme.
} else if (podmínka5) {
    archívy, nemám je zatím hotové.
    princip bude podobný.
} else {
    pokud se dostanem až sem, půjde o index a výpis nejnovějších článků.
}

no a otázka je taková... nevím, zda-li je dobrý mít "čistej" index až v else, takže mě napadlo, že bych přidal podmínku úplně na začátek, kde bych třeba porovnal REQUEST_URI (s konstantou, ve kt. je definovaná složka, kde je blog umístěn nebo jen / když jde o root). tzn jedna podmínka úplně na začátek, pokud je request uri "čistá", vypiš index. a do toho posledního else by šla skutečná 404ka...

:)
Jo takhle ... tak to bych zase nevymýšlel nějaký šílenosti a jednoduše:
návštěvník se pokusí otevřít příspěvek který neexistuje a neřešim nějaký hlavičky nebo co, prostě standardní stránka (pokud chceš tak http1/1: 200 OK) a místo příspěvku něco jako "tady nic není a ani nebylo"

To hraní s hlavičkami bych si schoval jen pro případ, že by jsi měl něco na způsob downloadu souborů a někdo by si hrál s URL a chtěl by stáhnout něco co neexituje (404) nebo nějakou část systému (403)


OT: nechci ti kazit chuť k práci na tvém RS, ale:
řekl bych, že podobných redakčních systémů je na internetu mraky, v čem bude ten tvůj lepší než ty ostatní?
do těch hlaviček jsme se nějak zamotali, ale necpu je všude... zatím házím jen 401čku při chybné autorizaci, a 404 / 403 bude asi pouze v souboru, na kterej odkáže přímo ErrorDocument. jinak to tedy řeším tak, jak říkáš.

chuť mi nekazíš, a tvoje otázka je na místě...

já samozřejmně neříkám, že můj systém je lepší, než ty ostatní. v něčem možná ano, v něčem jiném ne... třeba pro mě je důležité, že se tímto spoustu naučím, a příště se vrhnu třeba na něco složitějšího :) a mimo jiné, v budoucnu tím snad někdy nahradím wordpress na svým blogu...

nebude to open-source v pravým slova smyslu, prostě využití pro omezený počet lidí... a výhody?

a) jednoduchost - zatím je systém fakt dost jednoduchej, jasně dané šablonové soubory, html output se bude snadno upravovat.

b) při problému okamžitá technická podpora - můj rs budou pravděpodobně využívat spíš přátelé, kterým kdykoli rád poradím :)

c) cool uris - moc různých rs sice neznám, ale pokud vím, umí tohle jen wordpress. no a podobně jako tam i v mým systému si může uživatel nastavit pěkné nebo škaredé adresy :) + poměrně slušnej kód udělá základ SEO optimalizace (o zbytek se samozřejmně musí postarat již uživatel, a nepsat články o hovadinách :) ...

:)
Popravdě řečeno, pracuju na něčem podobném ;)
Jenom s tím rozdílem, že já vytvářím systém, který bude maximálně přizpůsobitelný abych nemusel pokaždé, když mě někdo požádá o web upravovat své starší výtvory nebo psát nové.
No a ve výsledku by to mělo umět víc, než jen vytvářet kategorie, nebo vytváření příspěvků, jak to umí většina dnešních RS.

k těm cool-urls: myslíš, že by bylo možné se o něj podělit? Já mám jenom takový jednoduchý a ještě ke všemu ne zrovna kvalitní. Takže než nad tím sedět dva dny a vymýšlet něco, co někdo už vymyslel ... :)
no, na tohle jsem taky nějak nemyslel :) ta snadná přizpůsobitelnost není nikdy od věci...

o cool uris se rád podělím, navíc by to mohlo pomoct i jiným... akorát teda nevím, jakou máš strukturu systému, takže to možná bude chtít dost úprav...

kostra indexu je výše, takže kus htaccessu:



RewriteEngine On
RewriteBase /

# umazani indexu
RewriteRule ^index\.php$ $1 [R=301,QSA]

# ?text=pekny-nazev-clanku
# /pekny-nazev-clanku/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/]+)/?$ ?text=$1 [L]

# ?page=10
# /page/10/ (stránkování)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^page/([^/]+)/?$ ?page=$1 [L]

# ?kategorie=nazev-kategorie
# /kategorie/nazev-kategorie/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^kategorie/([^/]+)/?$ ?kategorie=$1 [L]

# ?kategorie=nazev-kategorie&page=10
# /kategorie/nazev-kategorie/10/ (stránkování)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^kategorie/([^/]+)/([^/]+)?/?$ ?kategorie=$1&page=$2 [L]

# ?feed=
# /feed/rss/
# /feed/rss-comments/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^feed/([^/]+)/?$ ?feed=$1

# ?feed=rss-comments&clanek=nazev-clanku
# /feed/rss-comments/nazev-clanku/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^feed/([^/]+)/([^/]+)?/?$ ?feed=$1&clanek=$2 [L]



je to pořád to samé, takže to myslím, že i snadno pochopitelné ;-) archívy zatím teda nemám... v configu jsou nadefinované konstanty, které určují, jesli se budou tisknout pěkné, nebo škaredé odkazy:



define("ABSHTMLPATH", "/");



define("L", "/"); # define("L", "");
define("PERMALINK", ""); # define("PERMALINK", "?text=");
define("PERMALINKHEAD", ""); # define("PERMALINKHEAD", "?text=");
define("CAT", "kategorie/"); # define("CAT", "?katagorie=");
define("RSS", "feed/rss"); # define("RSS", "?feed=rss");
define("RSSCOM", "feed/rss-comments/"); # define("RSSCOM", "?feed=rss-comments");
define("RSSCOMTEXT", ""); # define("RSSCOMTEXT", "&clanek=");



kde to L je celkem důležité, páč funguje jako ukončující lomítko, a pokud se jedná o škaredé adresy, tak je prostě prázdné.

no a linky pak echuju:



<a href="' . ABSHTMLPATH . PERMALINK . $row[2] . L . '" title="Čti celý článek "' . $mw[3] . '"">



;-)