Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr to en Georges Tarbouriech
en to en Lorne Bailey
en to nl Hendrik-Jan Heins
Christophe Blaess is een onafhankelijke luchtvaart ingenieur. Hij is een Linux fan en werkt veel met dit systeem. Hij coordineert de vertaling van de man pages zoals die te vinden zijn op de site van het Linux Documentation Project.
Christophe Grenier is een 5e jaars student aan de ESIEA, hij werkt daar ook als systeembeheerder. Hij is gek van computer beveiligingssystemen.
Frédéric Raynal gebruikt Linux nu al jaren omdat het niet vervuilend is, niet opgepept wordt met hormonen, MSG of beendermeel... maar alleen met bloed, zweet, tranen en kennis.
Wanneer een client om een HTML-bestand vraagt, stuurt de server de
aangevraagde pagina (of een foutmelding). De browser interpreteert de
HTML-code naar het weer te geven format en laat het bestand zien.
Bijvoorbeeld door het volgende te in te typen
http://www.linuxdoc.org/HOWTO/ HOWTO-INDEX/howtos.html
URL (Uniform Request Locator), wordt de client verbonden met de
www.linuxdoc.org
server en vraagt om de
/HOWTO/HOWTO-INDEX/howtos.html
pagina(called URI -
Uniform Resource Identifiers), met behulp van het HTTP
protocol. Als de pagina bestaat, stuurt de server het gevraagde bestand door.
Als het bestand aanwezig is op de servers, zal deze, met dit statische
model, het bestand "zoals het is" sturen naar de client, zoniet dan zal de
server een foutmelding sturen (Het welbekende 404 - Not Found).
Helaas staat dit systeem geen interactiviteit tussen de server en de gebruiker toe, waardoor concepten als e-business, e-reserveringen voor vakanties of e-wat-dan-ook niet mogelijk zijn.
Gelukkig zijn er oplossingen die de dynamische creatie van HTML pagina's mogelijk maken. Een van die methodes is het CGI (Common Gateway Interface) script. In dit geval worden de URI web pagina's op een iets andere manier gegenerneerd:
http://<server><pathToScript>[?[param_1=val_1][...] [¶m_n=val_n]]
QUERY_STRING
(omgevings)variabele. In deze context is een CGI script niets anders
dan een uitvoerbaar bestand. Het maak gebruik van de stdin
(standaard input) of van de omgevingsvariabele QUERY_STRING
om argumenten te kunnen verwerken. Na het uitvoeren van de code wordt het
resultaat getoond op de stdout
(standaard output) en
vervolgens doorgestuurd naar de web client. Vrijwel iedere programmeertaal
( gecompileerde C programma's. Perl. shell scripts...) kan worden gebruikt
om CGI scripts te schrijven. Laten we bijvoorbeeld eens opzoeken wat de HOWTOs van
www.linuxdoc.org
weten over ssh :
http://www.linuxdoc.org/cgi-bin/ldpsrch.cgi?
svr=http%3A%2F%2Fwww.linuxdoc.org&srch=ssh&db=1& scope=0&rpt=20
www.linuxdoc.org
;/cgi-bin/ldpsrch.cgi
;?
is het begin van een lange lijst van argumenten
:
srv=http%3A%2F%2Fwww.linuxdoc.org
is de
server waar de aanvraag vandaan komt;srch=ssh
bevat de vraag zelf;db=1
betekent dat de aanvraag alleen de
HOWTOs betreft;scope=0
betekent dat de zoekopdracht
de inhoud van het gehele document behelst en niet alleen de titel;rpt=20
limiteert het aantal te tonen antwoorden.Over het algemeen zijn de namen en waardes van argumenten expliciet genoeg om hum betekenis te kunnen begrijpen. De inhoud van de pagina die de antwoorden weergeeft maakt ook veel duidelijk.
Nu weet je dat de positieve kant van CGI scripts inhoudt dat de gebruiker argumenten kan aangeven... maar de negatieve kant van het CGI script is dat een slecht geschreven script een veiligheidslek kan genereren.
Je hebt waarschijnlijk de vreemde karakters die de browser gebruikt
zoals bij de voorgaande aanvraag. Deze karakters zijn gecodeerd met de
ISO 8859-1 tekenset (zie hiervoor ook de >man iso_8859_1
pagina's. De tabel 1 geeft de betekenis van sommigen
van deze codes. Hierbij willen we opmerken dat de IIS 4.0 en IIS 5.0 servers
een zeer gevaarlijke achilleshiel hebben die de unicode bug heet en
gebaseerd is op de uitgebreide representatie van "/" en "\".
SSI Server Side
Include
"Server Side Include
is een deel van
de functionaliteit van een web server. Het staat de integratie
van instructies in een web pagina toe, hetzij door een bestand "zoals het is"
te integreren of door een commando uit te voeren (shell of CGI script).
In het Apache configuration file httpd.conf
, activeert de
"AddHandler server-parsed .shtml
" instructie
dit mechanisme. Om het verschil tussen .html
en .shtml
te vermijden, wordt vaak de .html
extensie gebruikt. Dit vertraagt de
server natuurlijk wel.... Dit kan beheerd worden op directory niveau met de volgende
instructies:
Options Includes
activeert alle SSI ;OptionsIncludesNoExec
verbiedt exec
cmd
en exec cgi
.In het bijgevoegde script guestbook.cgi
,
wordt de tekst die de gebruiker intyped toegevoegd aan een HTML-bestand,
zonder '<' en ' >' omzetting van tekens in < en > HTML code.
Een nieuwsgierig persoon zou een van de volgende instructies kunnen geven:
<!--#printenv -->
(let op de spatie achter
printenv
)<!--#exec cmd="cat /etc/passwd"-->
guestbook.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
DOCUMENT_ROOT=/home/web/sites/www8080 HTTP_ACCEPT=image/gif, image/jpeg, image/pjpeg, image/png, */* HTTP_ACCEPT_CHARSET=iso-8859-1,*,utf-8 HTTP_ACCEPT_ENCODING=gzip HTTP_ACCEPT_LANGUAGE=en, fr HTTP_CONNECTION=Keep-Alive HTTP_HOST=www.esiea.fr:8080 HTTP_PRAGMA=no-cache HTTP_REFERER=http://www.esiea.fr:8080/~grenier/cgi/guestbook.cgi? email=&texte=%3C%21--%23include+file%3D%22guestbook.cgi%22--%3E HTTP_USER_AGENT=Mozilla/4.76 [fr] (X11; U; Linux 2.2.16 i686) PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin REMOTE_ADDR=194.57.201.103 REMOTE_HOST=nef.esiea.fr REMOTE_PORT=3672 SCRIPT_FILENAME=/mnt/c/nef/grenier/public_html/cgi/guestbook.html SERVER_ADDR=194.57.201.103 SERVER_ADMIN=master8080@nef.esiea.fr SERVER_NAME=www.esiea.fr SERVER_PORT=8080 SERVER_SIGNATURE=<ADDRESS>Apache/1.3.14 Server www.esiea.fr Port 8080</ADDRESS> SERVER_SOFTWARE=Apache/1.3.14 (Unix) (Red-Hat/Linux) PHP/3.0.18 GATEWAY_INTERFACE=CGI/1.1 SERVER_PROTOCOL=HTTP/1.0 REQUEST_METHOD=GET QUERY_STRING= REQUEST_URI=/~grenier/cgi/guestbook.html SCRIPT_NAME=/~grenier/cgi/guestbook.html DATE_LOCAL=Tuesday, 27-Feb-2001 15:33:56 CET DATE_GMT=Tuesday, 27-Feb-2001 14:33:56 GMT LAST_MODIFIED=Tuesday, 27-Feb-2001 15:28:05 CET DOCUMENT_URI=/~grenier/cgi/guestbook.shtml DOCUMENT_PATH_INFO= USER_NAME=grenier DOCUMENT_NAME=guestbook.shtml
Met deze exec
instructie, krijg je vrijwel het zelfde met een
commandoregel equivalent:
guestbook.cgi?email=ppy&texte=%3c%21--%23exec%20cmd="cat%20/etc/passwd"%20--%3e
Probeer het volgende niet "<!--#include
file="/etc/passwd"-->
", het pad is relatief aan de directory
waarin de het HTML-bestand en dit mag geen "..
" bevatten.
Het Apache error_log
bestand, bevat dan een bericht
dat aangeeft dat er een poging tot het openen van een afgesloten bestand
heeft plaatsgevonden. De gebruiker krijgt dit bericht ook te zien
[an error occurred while processing this directive]
op de HTML pagina.
SSI wodt niet vaak gebruikt, dus het is slim om het te deactiveren op de eigen server. Het probleem is namelijk de combinatie van de kapotte gastenboek applicatie en de SSI.
In deze sectie laten we veiligheidslekken zien die gerelateerd zijn aan CGI scripts die geschreven zijn met Perl. Om de zaak te verduidelijken geven we niet de gehele code weer in de voorbeelden, maar alleen de delen die van belang zijn om het probleem aan te duiden.
Al onze scripts zijn volgens de volgende sjabloon gebouwd:
#!/usr/bin/perl -wT BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD>"; print "<TITLE>Remote Command</TITLE></HEAD>\n"; &ReadParse(\%input); # now use $input e.g like this: # print "<p>$input{filename}</p>\n"; # #################################### # # Begin van de probleemomschrijving # # #################################### # # ################################## # # Einde van de probleemomschrijving # # ################################## # form: print "<form action=\"$ENV{'SCRIPT_NAME'}\">\n"; print "<input type=texte name=filename>\n </form>\n"; print "</BODY>\n"; print "</HTML>\n"; exit(0); # Het eerste argument moet refereren aan een hash. # De hash wordt gevuld met gegevens. sub ReadParse($) { my $in=shift; my ($i, $key, $val); my $in_first; my @in_second; # Lees in de tekst if ($ENV{'REQUEST_METHOD'} eq "GET") { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'}); }else{ die "ERROR: unknown request method\n"; } @in_second = split(/&/,$in_first); foreach $i (0 .. $#in_second) { # Converteer plussen naar spaties $in_second[$i] =~ s/\+/ /g; # Deel op in key een waarde. ($key, $val) = split(/=/,$in_second[$i],2); # Converteer %XX van hex nummers naar alphanummeriek $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Associeer key en waarde # \0 is de "multiple separator" $$in{$key} .= "\0" if (defined($$in{$key})); $$in{$key} .= $val; } return length($#in_second); }
Meer over argumentgebruik in Perl (-wT
) later.
We beginnen met het opschonen van de $ENV
en $PATH
omgevingsvariabelen en we sturen de HTML header (dit is een onderdeel
van het HTML protocol tussen browser en server. Je kan dit echter niet zien
ip de pagina die weergegeven wordt op de browser). De ReadParse()
functie leest de argumenten uit het script. Dit kan eenvoudiger gerealiseerd
worden met modules, maar op deze manier kan je de hele code zien.
Nu geven we de voorbeelden. Uiteindelijk eindigen we met het HTML-bestand.
Perl kent ieder karakter even veel waarde toe, in tegenstelling tot bijvoorbeeld C functies. Voor Perl is het "null karakter" om een string te eindigen een gewoon karakter, dus wat is het probleem?
We voegen de onderstaande code toe aan ons script om
showhtml.cgi
te maken:
# showhtml.cgi my $filename= $input{filename}.".html"; print "<BODY>File : $filename<BR>"; if (-e $filename) { open(FILE,"$filename") || goto form; print <FILE>; }
De ReadParse()
functie vindt slechts een argument:
De naam van het weer te geven bestand. Om ervoor te zorgen dat een
"ongewenste gast" meer dan de HTML-bestanden kan lezen, voehen we de
".html
" extensie toe aan het einde van de bestandsnaam. Maar,
onthoud, de "null byte" is een gewoon karakter...
Dus als wij het volgende opvragen
showhtml.cgi?filename=%2Fetc%2Fpasswd%00
het bestand
heet my $filename = "/etc/passwd\0.html"
en tot onze
verbazing zien we een bestand dat geen HTML-bestand is.
Wat is er gebeurd? Het strace
commando laat zien hoe Perl
een bestand opent:
/tmp >>cat >open.pl << EOF > #!/usr/bin/perl > open(FILE, "/etc/passwd\0.html"); > EOF /tmp >>chmod 0700 open.pl /tmp >>strace ./open.pl 2>&1 | grep open execve("./open.pl", ["./open.pl"], [/* 24 vars */]) = 0 ... open("./open.pl", O_RDONLY) = 3 read(3, "#!/usr/bin/perl\n\nopen(FILE, \"/et"..., 4096) = 51 open("/etc/passwd", O_RDONLY) = 3
De laatste open()
gerepresenteerd door strace
,
correspondeert met het systeemcommando dat in C geschreven is. We kunnen zien dat
de .html
extensie verdwijnt, en dat we daardoor /etc/passwd kunnen
openen.
Dit probleem kan worden opgelost met een enkele gewone uitdrukking die alle "null bytes" verwijdert:
s/\0//g;
Hieronder volgt een script zonder enige beveiliging. Het geeft een bepaald bestand uit de /home/httpd/ directory boom weer:
#pipe1.cgi my $filename= "/home/httpd/".$input{filename}; print "<BODY>File : $filename<BR>"; open(FILE,"$filename") || goto form; print <FILE>;
Lach niet om dit voorbeeld! Ik ben dergelijke scripts tegen gekomen.
Het eerste uit te buiten lek is duidelijk:
pipe1.cgi?filename=..%2F..%2F..%2Fetc%2FpasswdJe hoeft alleen maar naar een hoger niveau in de boom om ieder willekeurig bestand te openen. Maar er is nog een andere, veel interessantere, mogelijkheid: Het uitvoeren van een commando naar keuze. In Perl opent het
open(FILE, "/bin/ls")
commando het binaire "/bin/ls
"
bestand....maar open(FILE, "/bin/ls |")
voert het aangegeven
commando uit. Door een enkele "pipe" |
toe te voegen, verandert het
gedrag van open()
. Een ander probleem komt van het feit dat het bestaan van het bestand niet
wordt gecontroleerd, waardoor we ieder commando kunnen uitvoeren, maar ook
ieder willekeurig argument kunnen gebruiken:
pipe1.cgi?filename=..%2F..%2F..%2Fbin%2Fcat%20%2fetc%2fpasswd%20|
displays the password file content.
Controleren op het bestaan van het te openen bestand limiteert de vrijheid:
#pipe2.cgi my $filename= "/home/httpd/".$input{filename}; print "<BODY>File : $filename<BR>"; if (-e $filename) { open(FILE,"$filename") || goto form; print <FILE> } else { print "-e failed: no file\n"; }Het vorige voorbeeld werkt nu niet meer. De "
-e
"
test werkt niet meer, omdat het "../../../bin/cat
/etc/passwd |
" bestand niet meer gevonden kan worden. Laten we nu het /bin/ls
commando eens proberen. De uitput zal
dezelfde zijn als hiervoor. Tenminste, als we bijvoorbeeld proberen de inhoud van
de /etc
directory oproepen, de "-e
"
controleert het bestaan van het "../../../bin/ls /etc |
bestand,
maar dat bestaat ook niet. Zolang we geen fictieve bestandsnaam aangeven,
zullen we niets interessants krijgen :(
Er is echter nog steeds een "oplossing", ook al is het resultaat
hiervan niet zo mooi. Het /bin/ls
bestaat in de meeste
systemen, maar wanneer open()
wordt aangeroepen met deze bestandsnaam,
dan wordt het commando niet uitgevoerd, maar dan wordt het de binaire inhoud getoond.
We moeten dus een manier vinden om een "pipe" '|
' aan het einde
van de naam toe te voegen, zonder dat deze gebruikt wordt tijdens de controle
door "-e
". We kennen de oplossing hiervoor al: de "null byte".
Wanneer we "../../../bin/ls\0|
" als naam sturen, is de controle op het
bestaan van het bestand een succes omdat deze alleen naar "../../../bin/ls
"
kijkt, open()
echter, kan de "pipe" zien en daarna het commando uitvoeren.
Uiteindelijk geeft de URI aan dat de inhoud van de huidige directory het volgende is:
pipe2.cgi?filename=../../../bin/ls%00|
Het finger.cgi script voert het finger
commando uit op onze computer:
#finger.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; $CMD= "/usr/bin/finger $login|"; open(FILE,"$CMD") || goto form; print <FILE>
Dit script neemt tenminste een bruikbare voorzorg: het zorgt er voor dat
enkele vreemde karakters niet geinterpreteerd worden in een commandoregel
door er een '\
' voor te plaatsen. Daardoor wordt een puntkomma dus
veranderd in "\;
" door deze uitdrukking. Maar de lijst bevat niet
alle belangrijke karakters. Onder andere de "line feed" '\n
'
mist.
In de commandoregel schil van jouw keuze, kan je een instructie
valideren door op RETURN
of ENTER
te drukken.
Deze toets stuurt een '\n
' karakter. In Perl kan je hetzelfde
doen. We hebben al gezien dat de open()
instructie ons
toestond om een commando uit te voeren zodra de regel werd beeindigd
met een "pipe" ('|
').
Om dit gedrag na te bootsen kunnen we een "carriage-return" en een instructie ingeven na de login die gestuurd is naar het finger commando:
finger.cgi?login=kmaster%0Acat%20/etc/passwd
Andere karakters zijn interessant om verscheidene instructies na elkaar uit te voeren:
;
: Dit beeindigt de eerste instructie
en gaat naar de volgende;&&
: Als de eerste instructie uitgevoerd
kan worden (i.e. geeft 0 in een commandoregel), dan wordt de volgende
uitgevoerd;||
: Als de eerste instructie niet kan worden
uitgevoerd (i.e. geeft een waarde die geen "null" is op dew commandoregel),
dan wordt de volgende uitgevoerd.Het voorgaande finger.cgi
script vermijdt problemen
met wat vreemde karakters. Daardoor werkt de URI
<finger.cgi?login=kmaster;cat%20/etc/passwd
niet
wanneer de puntkomma vermeden wordt. Een karakter wordt echter niet
beschermd: de "backslash" '\
'.
Laten we als voorbeeld een script nemen dat er voor zorgt dat we niet
naar een hoger gelegen directory kunnen komen door gebruik te maken
van de normale opdracht s/\.\.//g
met behulp van "..
".
Dit maakt niets uit! Commandoregels kunnen meerdere aanroepen
van '/
' tegelijk aan (probeer het volgende maar eens: cat
///etc//////passwd
dit moet overtuigend zijn).
In het bovenstaande scriot pipe2.cgi
bijvoorbeeld, is de
$filename
variabele geinitialiseerd vanaf de
"/home/httpd/
" aanzet. Door gebruik te maken van de voorgaande
"gewone" expressie is het mogelijk om het verplaatsen door de directories te
vermijden. Deze expressie beschermt de "..
", maar wat gebeurt er
als de het '.
' karakter beschermd wordt?
Dit betekent: de expressie voldoet niet als de bestandsnaam
.\./.\./etc/passwd
is.
We willen bij deze nog even zeggen dat dit zeer goed werkt met system()
(of ` ... `
), maar niet met open()
of "-e
".
Laten we terug gaan naar het finger.cgi
script. Door gebruik
te maken van de puntkomma geeft de finger.cgi?login=kmaster;cat%20/etc/passwd
URI niet het verwachtte resultaat, omdat de puntkomma wordt omzeild door
de "gewone" expressie. Dit betekent dat de commandoregel de volgende
opdracht ontvangt:
/usr/bin/finger kmaster\;cat /etc/passwdDe volgende fouten worden gevonden in de webserver logs:
finger: kmaster;cat: no such user. finger: /etc/passwd: no such user.Deze berichten zijn identiek aan degene die je krijgt als je dit op een commandoregel intyped. Het probleem komt voort uit het feit dat het beschermde '
;
' karakter wordt gezien als onderdeel
van de string "kmaster;cat
". We willen beide instructies schieden, de instructie uit het script
en de instructie die we willen gebruiken. We moeten nu de ';
'
beveiligen: <A
HREF="finger.cgi?login=kmaster\;cat%20/etc/passwd">
finger.cgi?login=kmaster\;cat%20/etc/passwd</A>
. De
"\;
string, wordt daarna door het script veranderd in
"\\;
", en daarna naar de commandoregel gestuurd.
Dit laatste ziet er als volgt uit:
/usr/bin/finger kmaster\\;cat /etc/passwdDe commandoregel wordt in twee verschillende instructies gescheiden:
/usr/bin/finger kmaster\
Dat waarschijnlijk niet
werkt.... maar dat maakt niet uit ;-)cat /etc/passwd
dat het wachtwoordbestand wergeeft.\
' moet ook
omzeild worden. Soms wordt de parameter "beschermd" door gebruik te maken van aanhalingstekens.
We hebben het voorgaande finger.cgi
script veranderd om op die
manier de $login
variabele te beschermen.
Echter, zolang de aanhalingstekens niet omzeild worden, is dit nutteloos. Zelfs wanneer er een wordt toegevoegd, zal de aanvraag een foutmelding genereren. Dit gebeurt omdat het eerste aanhalingsteken dat gestuurd wordt het openende aanhalingsteken van het script sluit. Nu geef je het commando en een tweede aanhalingsteken opent de sluitende aanhalingstekens van het script.
Het finger2.cgi script illustreert dit:
#finger2.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/\0//g; $login =~ s/([<>\*\|`&\$!#\(\)\[\]\{\}:'\n])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; #New (in)efficient super protection : $CMD= "/usr/bin/finger \"$login\"|"; open(FILE,"$CMD") || goto form; while(<FILE>) { print; }
De URI om dit commando uit te voeren wordt dan:
finger2.cgi?login=kmaster%22%3Bcat%20%2Fetc%2Fpasswd%3B%22De commandoregel ontvangt het commando
/usr/bin/finger "$login";cat
/etc/passwd""
en de aanhalingstekens zijn geen probleem meer. Dus, als je de parameters wilt beschermen met aanhaligstekens, is het belangrijk om ze op de zelfde manier als de puntkomma of de "backslash" te omzeilen.
Asl je programmeert in Perl, gebruik dan de w
optie of
gebruik "use warnings;
" (Perl 5.6.0 and later), dit informeert
je over pontentiele problemen, zoals niet geinitialiseerde variabelen
of overbodige expressies / functies.
De T
optie ( taint mode) geeft meer veiligheid.
Deze modus activeert verschillende tests. De belangrijkste zorg hierbij is
een mogelijke corruptie (tainting) van variabelen. Variabelen zijn
of schoon en zuiver of corrupt. Gegevens die van buiten het programma komen
worden gezien als corrupt, zolang deze nog niet opgeschoont is. Zo'n corrupte
variabele mag geen waardes toewijzen aan dingen buiten het programma
(aanroepen aan andere commandoregel opdrachten).
In corrupte modus, worden commandoregel argumenten, omgevingsvariabelen,
enkele systeemaanroep resultaten (readdir()
,
readlink()
, readdir()
, ...) en de gegevens
die uit bestanden komen, gezien als verdacht en daarom corrupt.
Om een variabele op te schonen, moet je deze filteren met behulp van
een gewone expressie. Het mag duidelijk zijn dat get gebruik van .*
niets uithaalt. Het doel hiervan is je te dwingen om gebruik te maken
van de meegeleverde argumenten. Gebruik altijd de expressie die het beste
aansluit bij je doel.
Deze modus beschermt echter niet tegen alles: het corrumperen
van argumenten die doorgegeven worden aan system()
of
exec()
als een lijst van variabelen, wordt niet gecontroleerd.
Je moet dan ook heel voorzichtig zijn als een van je scripts deze functies
gebruikt. De exec "sh", '-c', $arg;
instructie
wordt als veilig gezien, of $arg
nu corrupt is of niet :(
Het is ook aangeraden om "use strict;" toe te voegen aan het begin
van je programma's. Dit verplicht je om variabelen op te geven;
sommige mensen vinden dit vervelend, maar het is verplicht als je
mod-perl
gebruikt.
Dus, je eigen Perl CGI scripts moeten beginnen met:
#!/usr/bin/perl -wT use strict; use CGI;or with Perl 5.6.0 :
#!/usr/bin/perl -T use warnings; use strict; use CGI;
open()
aanroepVeel programmeurs maken gebruik van open(FILE,"$filename") || ...
om een bestand te openen. We hebben de risico's van dit type code
al gezien. Om het risico te verkleinen, moet je de open modus specificeren:
open(FILE,"<$filename") || ...
for read
only;open(FILE,">$filename") || ...
for write
onlyHet is aan te raden om voor een bestand te openen te controleren of het bestaat. Dit voorkomt het "race conditions" type probleem zoals behandeld in het voorgaand e artikel niet, maar het vermijdt enkele valkuilen, zoals commando's met argumenten.
if ( -e $filename ) { ... }
Vanaf Perl 5.6, is er een nieuwe syntax voor
open()
: open(FILEHANDLE,MODE,LIST)
. Met
de '<' modus is het bestand open voor lezen; met de '>'
modus, wordt het bestand getrunkeerd of, als het nodig is, gecreeerd
en geopend voor schrijven. Dit wordt interessant voor modi die
communiceren met andere processen. Als de modus '|-' or '-|'is, dan
wordt het LIST argument geinterpreteerd als een commando en kan het
respectievelijk voor of na de "pipe" gevonden worden.
Voor Perl 5.6 en open()
met drie argumenten, gebruikten
sommige mensen het sysopen()
commando.
Er zijn twee methodes: Of je specificeert de verboden karakters, of je definieert de toegestane karakters expliciet door gebruik te maken van reguliere expressies. De voorbeeldprogramma's zouden je overtuigd moeten hebben dat het vrij eenvoudig is om te vergeten een filter in te bouwen voor potentieel gevaarlijke karakters, dat is waarom de tweede methode aangeraden wordt.
Dit houdt practisch het volgende in: controleer eerst of de aanvraag alleen bestaat uit toegestane karakters. Vermijd en verwijder vervolgens de karakters die als gevaarlijk worden gezien.
#!/usr/bin/perl -wT # filtre.pl # Respectievelijk de $safe and $danger variabelen definieren # de karakters met en zonder risico's. # Voeg enkelen toe of verwijder ze om het filter te veranderen. # De enige toegestane karakters in $input worden gevalideerd # door de definities. use strict; my $input = shift; my $safe = '\w\d'; my $danger = '&`\'\\|"*?~<>^(){}\$\n\r\[\]'; #Let op: # '/', spatie en tab zijn met opzet geen deel van de definities. if ($input =~ m/^[$safe$danger]+$/g) { $input =~ s/([$danger]+)/\\$1/g; } else { die "Bad input chars in $input\n"; } print "input = [$input]\n";
Dit script definieert twee karaktersets:
$safe
bevat degenen die als niet gevaarlijk worden
gezien (hier zijn dat alleen letters en cijfers);$danger
bevat de karakters die omzeild moeten worden,
omdat ze toegestaan zijn, maar potentieel gevaarlijk.Ik wil hier geen controversiele menig geven, maar ik denk dat het beter
is om scripts in PHP te schrijven in plaats van Perl. Of eigenlijk:
als een systeembeheerder heb ik liever dat mijn gebruikers scripts schrijven
in PHP in plaats van in Perl. Iemand die inveilig programmeert in PHP zal
net zo gevaarlijk zijn als iemand die onveilig programmeert in Perl,
dus waarom zou je PHP prefereren? Wanneer je programmeerproblemen hebt
met PHP kan je de veilige modus activeren (safe_mode=on
) of
bepaalde functies deactiveren (disable_functions=...
).
Deze modus maakt het onmogelijk om bestanden te openen die niet van
de gebruiker zijn, of om omgevingsvariabelen te veranderen tenzij dat expliciet
wordt toegestaan, of commando's uit te voeren, enz.
Standaard informeert de Apache banner je over het gebruik van PHP.
$ telnet localhost 80 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. HEAD / HTTP/1.0 HTTP/1.1 200 OK Date: Tue, 03 Apr 2001 11:22:41 GMT Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a PHP/4.0.4pl1 mod_perl/1.24 Connection: close Content-Type: text/html Connection closed by foreign host.Write
expose_PHP = Off
into
/etc/php.ini
to hide the information :
Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a mod_perl/1.24
Het /etc/php.ini
bestand (PHP4) en
/etc/httpd/php3.ini
hebben veel parameters die behulpzaam kunnen
zijn bij het verstevigen van het systeem. Bijvoorbeeld de
"magic_quotes_gpc
" optie die quotes toevoegt aan argumenten
die ontvangen worden door GET
, POST
methoden en
door cookies; hierdor worden een aantal problemen vermeden die bestonden
in onze Perl voorbeelden.
Dit artikel is waarschijnlijk het beste te begrijpen als onderdeel
van deze serie. Het laat de zwakke plekken zien die dagelijks geexploiteerd
worden op het internet. Er zijn nog veel meer zwakke plekken, meestal
gerelateerd aan slecht programmeerwerk (bijvoorbeeld een script dat een
e-mailtje stuurt en het From:
veld als argument neemt, is
een ideaal slachtoffer voor spammers). Er zijn talloze voorbeelden.
Zodra er een script op een website verschijnt, kan je er vanuit gaan dat er
tenminste een persoon is die het op een verkeerde manier probeert te
gebruiken.
Dit artikel betekent het einde van de serie over veilig programmeren. We hopen dat we je hebben kunnen helpen bij het ontdekken van de belangrijkste veiligheidslekken in veel te veel applicaties, en dat je rekening zal houden met de "veiligheidsparameters" wanneer je zelf een applicatie ontwerpt en programmeert. Veiligheidslekken worden vaak verwaarloosd door de tijdelijke aard van de ontwikkeling en gebruik van een applicatie (intern gebruik, prive gebruik op een afgesloten netwerk, als tijdelijk model, etc.). Desalniettemin kan een module die in eerste instantie is ontworpen voor beperkt gebruik de basis worden voor een veel grotere applicatie en later zullen de noodzakelijke veranderingen veel hogere kosten met zich meebrengen.
URI Encoding (ISO 8859-1) | Karakter |
%00 | \0 (end of string) |
%0a | \n (carriage return) |
%20 | space |
%21 | ! |
%22 | " |
%23 | # |
%26 | & (ampersand) |
%2f | / |
%3b | ; |
%3c | < |
%3e | > |
man perlsec
: Perl man pagina's over
beveiliging;#!/usr/bin/perl -w # guestbook.cgi BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD><TITLE>Buggy Guestbook</TITLE></HEAD>\n"; &ReadParse(\%input); my $email= $input{email}; my $texte= $input{texte}; $texte =~ s/\n/<BR>/g; print "<BODY><A HREF=\"guestbook.html\"> GuestBook </A><BR><form action=\"$ENV{'SCRIPT_NAME'}\">\n Email: <input type=texte name=email><BR>\n Texte:<BR>\n<textarea name=\"texte\" rows=15 cols=70> </textarea><BR><input type=submit value=\"Go!\"> </form>\n"; print "</BODY>\n"; print "</HTML>"; open (FILE,">>guestbook.html") || die ("Cannot write\n"); print FILE "Email: $email<BR>\n"; print FILE "Texte: $texte<BR>\n"; print FILE "<HR>\n"; close(FILE); exit(0); sub ReadParse { my $in =shift; my ($i, $key, $val); my $in_first; my @in_second; # Read in text if ($ENV{'REQUEST_METHOD'} eq "GET") { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'}); }else{ die "ERROR: unknown request method\n"; } @in_second = split(/&/,$in_first); foreach $i (0 .. $#in_second) { # Convert plus's to spaces $in_second[$i] =~ s/\+/ /g; # Split into key and value. ($key, $val) = split(/=/,$in_second[$i],2); # Convert %XX from hex numbers to alphanumeric $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Associate key and value $$in{$key} .= "\0" if (defined($$in{$key})); $$in{$key} .= $val; } return length($#in_second); }