Category Archives: Programare

Categorie dedicata subiectului programare … in ce limbaj imi vine mie pe chelie

Importanța unui PHP framework bine scris

Spuneam la GeekMeet-ul din octombrie (damn it, iarăși fac referință la el) în timpul prezentării mele, pentru cei absenți, o abordare de la OS până la coder în privința Web Security, despre importanța folosirii unui framework ce să facă în mod implicit filtrare XSS și SQL Injection (SQLi) a input-ului, în biblioteca ce se ocupă de baza de date. Iar pentru chestii riscante, chiar o filtrare XSS cu HTML Purifier, o bibliotecă a cărui filtru XSS a fost creat să treacă de XSS (Cross Site Scripting) Cheat Sheet.

Știu, nu vreau să fac “carieră” din astfel de post-uri pe blog și nu prea cred că o să mă vedeți vreodată să trimit chestii către http://hackersblog.org/. Sunt preocupat de a proteja, de a-i învăța pe alții cum să se protejeze, și mai puțin de a demonstra vulernabilități. Sunt orientat către ce nu ar trebui să facă aplicația și mai puțin înspre a demonstra cum ajungi acolo. De altel aș prefera ca lumea să nu se ia de ‘junk’-ul de WordPress ce rulează pe blog, sunt prea ocupat pentru a scrie un engine sigur cu un număr echivalent de facilități. Mă rog, va urma un contra-exemplu pentru a susține cele din paragraful anterior.

Povestea începe de la faptul că am avut o mică discuție cu necenzurat despre importanța folosirii unui framework, dar el o susținea pe a lui cu alea 10 kile de cod. Doar pentru ce îți trebuie un framework de 1.25 megi (dimensiunea minimă, maxim 1.49) pentru o aplicație banală? Pai în primul rând bune sunt cele de PHP5 folosind OOP și clase cu auto-load. Poți face multiple instalări folosind aceleași surse ale framework-ului exceptând aplicația în sine. Folosești ce încarci. A da, și ai filtrare implicită. De ce? Pentru că nimeni nu este perfect. Greșeli apar și în codul programatorilor ce au trecut de fazele tatonării. Vorbesc din experiența lucrului și mai mult din experiența lucrului în echipă. Dacă tu ca programator nu o dai de gard, se prea poate să dea altul.

Acu recunosc, am trișat puțin. M-am uitat puțin prin cele 10 kile de cod astfel încât buba a fost oarecum imediată. Dar nu imposibil de descoperit cu puțină răbdare și stil având în vedere regulile simple de URL rewrite, destul de evidente, și faptul că era vorba de un singur parametru vulnerabil. De fapt am reușit 3 in 1: XSS, blind SQLi și disclosure în același input.

A da, a folosi mysql_error() în producție este una dintre cele mai proaste idei. XSS-ul și disclosure-ul au fost posibile prin intermediul acestei funcții magice ce ar trebui folosite doar pentru dezvoltare, nu și pentru producție.

Momentan nu dăm poze, așteptăm să aplice patch-ul trimis :).

urlencode()/rawurlencode() complet

Exceptând faptul ca Mr. Google bușește validarea blogului (deși W3 nu citește resursele externe = se validează), deasemenea exceptând faptul că WordPress mai trage câte o gherlă și refuză să pună conținut valid, este cunoscut faptul că am un oarecare fetiș cu standardele. În concluzie depun eforturi reale pentru a le respecta. Din păcate există situații rare în care caractere arbitrare din UTF-8 duc la invalidarea paginilor. Un exemplu bun este atunci când se compun link-uri ce conțin caractere non-ASCII. Soluția evidentă este URL encoding, dar din păcate funcția urlencode() din PHP, precum și rawurlencode() are boala de a omite caracterele menționate mai sus. Din moment ce dezvoltatorii PHP refuză introducerea unui flag pentru a putea face o codare completă a unui șir de caractere, există soluții ce pot fi aplicate la nivel de PHP. Soluția propusă de către subsemnatul depinde doar de componente din core (PCRE este parte a PHP core!).

function url_encode_all($text)
	{
		return preg_replace('/./e', "sprintf('%%%02X', ord('\\0'))", $text);
	}

MySQL, arbori, o singură tabelă, cheile străine si un exemplu în Kohana

Aparent am o poveste de spus. Ei bine și de data aceasta aparențele nu înșeală. M-am lovit de suficiente ori de problema arborilor stocați în baze de date, în special atunci când este vorba de o structură de categorii. Cum nu sunt un mare fan al procedurilor stocate și al trigger-elor, sunt de părere că impunând o constrângere de cheie străina problema se rezolvă mult mai elegant atunci când vine vorba să se șteargă toată ierarhia. MyISAM nu știe el de chei străine deci aici vine în ajutor InnoDB. InnoDB știe de chei străine, dar cel mai probabil prin transformarea tabelei problema arborelui nu se rezolvă de la sine deoarece nu se pot impune constrângerile de cheie străina.

Ideea este următoarea: pentru a impune o cheie străina folosind MySQL și InnoDB este nevoie ca structura arborelui să fie corecta și aceasta să convină lui InnoDB. A doua parte este partea spinoasă. Cel mai probabil un arbore corect definit are un lucru esențial ce îi lipsește. Se dă următoarea structură de bază:

CREATE TABLE `trees` (
`id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`parent_id` INT( 11 ) UNSIGNED NOT NULL DEFAULT '1',
`data` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL ,
INDEX ( `parent_id` )
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_unicode_ci

Precum se observă schema de date este corectă iar aparent nu pot apărea probleme. Se dă chiar următorul arbore:

1, 0, a
2, 0, b
3, 1, c
4, 1, d
5, 3, e
6, 2, f

Deși (aparent) la prima vedere structural este corect, dacă se rulează interogarea ce ar trebui sa pună constrângerea:

ALTER TABLE `trees` ADD FOREIGN KEY ( `parent_id` ) REFERENCES `trees` (
`id`
) ON DELETE CASCADE ;

o să dea o eroare de MySQL de toată frumusețea, ceva gen:

#1452 - Cannot add or update a child row: a foreign key constraint fails
([...]`, CONSTRAINT `[...]` FOREIGN KEY (`parent_id`)
REFERENCES `trees` (`id`) ON DELETE CASCADE)

Cei cu privirea mai ageră poate că s-au prins de soluție. Nu întâmplător i-am dat valoare implicită 1 lui parent_id, ba chiar mai mult, exemplul dat de mine nu este UN arbore, ci mai mulți arbori stocați în aceeași tabelă. Am dat acest exemplu pentru că am întâlnit de suficiente ori în practică asemenea implementări ce mai încolo îmi dădeau bătăi de cap. Ba chiar am moștenit o astfel de baza de date pentru un proiect. Ceea ce confirmă zicala care spune faptul că web developerii sug la SQL – iar cum sunt și eu în breasla lor mă auto-includ. Dar din când în când, mai există și momente de deșteptare.

Soluția sună cam asa: se face backup la date. Cheile primare nu se salvează, coloana parent_id ce va deveni cheie străina se incrementează cu o unitate dacă este vorba de o implementare precum cea din exemplul meu, dacă nu, atunci se adaptează. Se golește tabela. Se aplică iarăși codul de mai sus pentru crearea cheii străine (ALTER TABLE bla, bla, bla). În mod deloc surprinzător, aceasta va funcționa fără probleme. Nu va mai da eroarea #1452. Dar, pentru a continua, este imperativ ca această înregistrare să se găsească în tabelă:

INSERT INTO `trees` (
`id` ,
`parent_id` ,
`data`
)
VALUES (
'1', '1', 'root'
);

‘root’ nu e musai să fie ‘root’. Poate fi și ‘rădăcină’, dar cum personal scriu mai rar soft pentru România, prefer denumirile în Engleză. Aceasta trebuie să fie rădăcina arborelui. O înregistrare de genul (0, 0, ‘root’) este invalidă, deci nu va putea fi folosită pentru a valida implementarea originală. De aici se pot insera mai departe înregistrările, fără bătăi de cap atâta timp cât se păstrează integritatea relației cheie primară – cheie străină. De altfel, această înregistrare NU trebuie ștearsă. Dacă se șterge, se va șterge automat tot arborele. Este evident … dacă tai un copac de la rădăcina, cade cu totul. Dacă tai doar o creangă, restul copacului nu este afectat. În concluzie, dacă datele sunt manipulate corect, nu pot apărea probleme. Mai departe se poate insera noul arbore:

INSERT INTO `trees` (
`parent_id` ,
`data`
)
VALUES (
1, 'a'
), (
1, 'b'
), (
2, 'c'
), (
2, 'd',
), (
4, 'e'
), (
3, 'f'
);

Se va obține rezultatul dorit inițial. Iar constrângerea își face magia: spre exemplu dacă se șterge (2, 1, ‘a’), atunci automat se vor șterge și (4, 2, ‘c’), (5, 2, ‘d’) și (6, 4, ‘e’) pentru că ierarhic au ca părinte pe (2, 1, ‘a’).

Pentru că tot m-a prins microbul MVC, mai bine zis Kohana, o să dau ca exemplu un model ce tratează din punctul de vedere al aplicației problema de mai sus. Din moment ce în mod implicit Kohana este destul de restrictiv cu manipularea variabilelor, altfel spus chestii ce în mod normal PHP le tratează ca ‘Notice’ în Kohana pot da in ‘Runtime Error’, metodele sunt puțin mai stufoase în sensul că verifică integritatea înainte de a acționa. Exemple asupra a ceea ce am spus mai sus: nu se poate șterge sau actualiza o înregistrare ce este deja ștearsă. În mod normal aceste operații returnează 0, fie ca e vorba de ‘affected rows’ sau de ‘deleted rows’. Cel puțin așa se întâmplă în mod implicit în ambele situații descrise, nu am cercetat dacă este configurabil acest comportament și nici nu am de gând. Kohana prin strictețe impune modele sănătoase de programare ce nu și le însușește orice cocalar ce pune mâna pe PHP pentru faptul că variabilele încep cu $ și a auzit că poate să se simtă și el h4x0r pentru că poate programa ceva. Deci să îi dau bâte cu modelul:

class Tree_Model extends Model {
 
	public function __construct($id = NULL)
	{
		parent::__construct($id);
	}
 
	public function add_child($parent_id, $data)
	{
		$check_record = $this->db->where('id', $parent_id)->get('trees');
 
		if($check_record->count() > 0)
		{
			return $this->db->from('trees')->set(array('parent_id' => $parent_id, 'data' => $data))->insert();
		}
		else
		{
			return false;
		}
	}
 
	public function update_child($id, $parent_id, $data)
	{
		$check_record1 = $this->db->where('id', $id)->get('trees');
		$check_record2 = $this->db->where('id', $parent_id)->get('trees');
 
		if($check_record1->count() === 1 AND $check_record2->count() > 0)
		{
			return $this->db->from('trees')->set(array('parent_id' => $parent_id, 'data' => $data))->where('id', $id)->update();
		}
		else
		{
			return false;
		}
	}
 
	public function delete_child($id)
	{
		$check_record = $this->db->where('id', $id)->get('trees');
 
		if($check_record->count() === 1 AND $id > 1)
		{
			return $this->db->from('trees')->where('id', $id)->delete();
		}
		else
		{
			return false;
		}
	}
 
}

PS: clasa este portabilă și pentru alte baze de date având în vedere faptul că am folosit ‘Database Query Builder’. Mergea si cu ORM, dar prefer o abordare puțin mai directa a problemei. În ORM nu mă mai simt stăpân pe situație. Pentru cârcotași: având în vedere că aceste tipuri de structuri de date nu prea sunt dedicate modificărilor dese, impactul de performanță datorat numărului mărit de interogări pentru a verifică integritatea este minim și merită efortul pentru a face o abordare corectă în loc să se apeleze la reguli mai puțin stricte și a depinde de erorile eventuale returnate de către baza de date (FK constraint error).

Nota: nu pot corecta comportamentul cretin al WordPress pentru a afisa corect apostrof si ghilimele.

Instalarea Apache 2.2 + PHP 5.2 + MySQL 5.1 sub Windows

Ultima actualizare: 19 Aprilie 2009

Introducere

Desi exista o droaie de pachete de astea ce le contin pe toate si au o gramada de arome, pe zi ce trece ajung la concluzia ca un developer serios nu se incurca cu mizerii si isi seteaza singur mediul de dezvoltare a aplicatiilor web. Ma rog, nu toate sunt mizerii, dar majoritatea fie sunt prea stufoase, fie inutile sau inutilizabile fara lucru manual – deci mai bine faci lucru manual din start. Asta in cazul in care nu vrei sa ramai o mimoza pentru tot restul vietii care transpira cand vine vorba sa adauge module noi mediului respectiv, sau pur si simplu sa se blocheze in parametrii de configurare relativ simpli. Nu sustin faptul ca este usor. De altfel patrunderea in tainele configurarii fine necesita timp si multa documentatie citita. Dar macar vreo cateva chestii de baza ar trebui cunoscute. Iar chestiile de baza pornesc cu instalarea.

Propozitie cheie: daca iti este lene sa iti configurezi acest mediu (lucru ce nu se intampla zilnic, iar experienta acumulata este benefica) atunci oare nu iti este lene sa te apuci sa programezi catusi de cat mai mult decat aplicatii gen “hello world”? Pentru a implementa solutii de o complexitate relativ mare ce necesita varii module este nevoie de mult mai multa munca pentru documentare decat pentru a configura un mediu de dezvoltare. In plus, din moment ce nu iti cunosti bine mediul in care ruleaza aplicatiile tale, cum poti avea nesimtirea sa sustii ca ai idee foarte bine ce face propria aplicatie? De unde vei sti ca va functiona corect si este portabila pe alta masina? Intrebarile acestea sunt multe, si nu, nu am de gand sa le transform in intrebari retorice. Raspunsurile sunt mai mult sau mai putin evidente.

O sa structurez acest articol in cativa pasi destul de simplu de urmat. Este ca in cazul in care se construieste o casa: se pleaca de la fundatie, si se termina treaba cu acoperisul. Deci ordinea va respecta logica si bunul simt, cu notiunea ca desi o sa incep cu Apache, MySQL deasemenea poate fi primul pas deoarece baza de date si serverul web nu sunt interdependente. PHP se integreaza cu Apache, deci regulile anterior mentionate indica faptul ca va fi instalat dupa el – aceasta pentru a nu fi nevoit sa faci instalarea PHP de doua ori. Continue reading

ID-uri numerice curate in PHP

Adesea m-am lovit de problema sanitizarii variabilelor care provin din input, si nu zic asta facand referire la PHP, ci la programare in general. Din moment ce am avut boala sa studiez catusi de cat bazele securitatii, mi-a ramas in cap un principiu foarte bine formulat: “all user input is evil!”. Adevarat grait. Deci am inceput sa gandesc aplicatiile dupa metoda user proof. In primul rand cand testez ceva nou, nu testez dupa cum ar trebui sa fie utilizata aplicatia in mod corect, ci cum NU ar trebui sa fie folosita, si ce sa se intample in acele cazuri. In primul rand pentru ca o aplicatie nu este o chestie democratica unde userul face ce vrea el si adesea face prost. Deasemenea intervine si cealata extrema. Unde nu este prostie – este viclenie – deci se poate trezi cate un h4x0r de asta cu mai mult timp liber la dispozitie sa te caute la oo (a se citi: securizare).

Problema ID-urilor numerice in PHP desi este una destul de banala, rezolvarea este multipla. De-alungul timpului am testat mai multe chestii, dar doar recent am ajuns la o forma finala care sa ma multumeasca deplin. De ce ID-uri numerice? Pentru ca este foarte usor de lucrat cu ele cand se interactioneaza cu o baza de date. Orice tabela care trebuie sa stocheze in mod unic niste chestii, are o cheie primara. Pana acum cea mai desteapta chestie in materie de chei primare este cheia intreaga care se seteaza cu auto-increment – ceea ce asigura complet unicitatea datelor. Recomandarea ar fi ca auto incrementul sa inceapa de la 0 (chestia asta o sa se lege de ce o sa zic mai jos). Bonus ar fi faptul ca fiecare tabela de acest gen ar trebui sa aiba cheia primara indexata pentru a reduce considerabil timpul de cautare al ei.

Adesea aceste chei primare din baza de date sunt trimise prin intermediul requesturilor de tip GET, deci se vor regasi in componenta URL-ului. Problema se pune atunci cand pe langa acel ID numeric, utilizatorul (in mod intentionat) introduce si altceva pe langa acel ID. Treaba poate sa se imputa de la XSS (Cross Site Scripting) – atunci cand acea cheie se regaseste in pagina, sau la atacuri de tip SQL injection (mai grav).

Desi exista functii native in suportul de MySQL pentru PHP, acestea sunt destul de triste la ID-uri numerice, deci am preferat propria metoda:

function clean_id($id)
{
$id=preg_replace("@[^0-9]@", "", $id);
if (empty($id))
	$id=0;
return $id;
}

Chestia asta de mai sus face vreo doua chestii … prima este aceea ca da strip la tot inputul care nu e numeric. A doua verifica ce a mai ramas, iar daca are valoarea 0 (orice 0 din PHP), atunci returneaza un 0 numeric. Este important acest zero numeric … ziceam ceva mai sus de el … acest zero numeric are grija ca interogarea sa nu dea de o chestie gen <<WHERE id=”>> si sa bubuie o eroare de SQL (care in anumite cazuri … ar putea la randul ei sa divulge alte chestii).

Actualizare:

Datorita observatiei lui AgLiAn am ajuns la concluzia ca functia mea initiala nu rezolva o problema: interger overflow, chestie ce mai departe se traduce in interpretarea ca float de catre PHP – si de unde pot aparea alte probleme. Si pentru ca am mentionat pe undeva mai sus de auto-increment (si acest obicei nu o sa se transforme peste noapte pana la proba contrara ce demonstreaza un obicei prost), functia clean_id(); devine:

function clean_id($id)
{
return abs(intval($id));
}