[LinuxFocus-icon]
Home  |  Map  |  Index  |  Zoek

Nieuws | Archieven | Links | Over LF
Dit artikel is beschikbaar in: English  Castellano  Deutsch  Francais  Nederlands  Portugues  Russian  Turkce  

convert to palmConvert to GutenPalm
or to PalmDoc

[image of the authors]
door Frédéric Raynal, Christophe Blaess, Christophe Grenier
<pappy(at)users.sourceforge.net, ccb(at)club-internet.fr, grenier(at)nef.esiea.fr>

Over de auteur:

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.



Vertaald naar het Nederlands door:
Hendrik-Jan Heins <hjh(at)passys.nl>

Inhoud:

 

Het vermijden van veiligheidslekken bij het ontwikkelen van een applicatie - Deel 6: CGI scripts

[article illustration]

Kort:

Het opvragen van een bestand, het draaien van een programma via een slecht geschreven Perls script ... "Er zijn meer wegen naar Rome"

Vorige arikelen uit deze serie:



 

Web server, URI en configuratie problemen

 

Een (te korte) inleiding over hoe een webserver werkt en hoe een URI te maken is

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][...] [&param_n=val_n]]
De argumentenlijst is opgeslagen in de 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
Dit is eigenlijk veel eenvoudiger dan het lijkt. Laten we deze URL eens analyseren:

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 "\".

 

Apache configuratie met "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:



In het bijgevoegde script guestbook.cgi, wordt de tekst die de gebruiker intyped toegevoegd aan een HTML-bestand, zonder '<' en ' >' omzetting van tekens in &lt; en &gt; HTML code. Een nieuwsgierig persoon zou een van de volgende instructies kunnen geven:

Met het eerste,
guestbook.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
krijg je een paar regels met informatie over het systeem:
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.

 

Perl Scripts

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.

 

De "null byte"

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;


 

Het gebruik van pipes

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%2Fpasswd
Je 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|
 

"Line feed"

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 werkt hier niet, omdat ze beschermd worden door de "regular expression". Maar laten we nu eens een manier vinden om dit op te lossen.  

"Backslash" en puntkomma

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/passwd
De 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/passwd
De commandoregel wordt in twee verschillende instructies gescheiden:
  1. /usr/bin/finger kmaster\ Dat waarschijnlijk niet werkt.... maar dat maakt niet uit ;-)
  2. cat /etc/passwd dat het wachtwoordbestand wergeeft.
De oplossing is eenvoudig: de "backslash" '\' moet ook omzeild worden.

 

Het gebruik van een onbeschermd " karakter

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%22
De 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.

 

Schrijven in Perl

 

Waarschuwings- en corruptie opties

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;


 

De open() aanroep

Veel 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 je bestanden nooit ongespecifeerd.

Het 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.

 

Invoer vermijden en filteren

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:

Iedere aanvraag die een karakter bevat die niet voorkomt in een van de twee sets wordt onmiddelijk verworpen.

 

PHP scripts

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.

 

Conclusie

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.


 

Enkele URI Encoded karakters

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 >
Tab 1 : ISO 8859-1 en corresponderende Karakters

 

Links


 

Het guestbook.cgi programma met fouten

#!/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);
}


 

Talkback voor dit artikel

Elk artikel heeft zijn eigen talkback pagina. Daar kan je commentaar geven of commentaar van anderen lezen:
 talkback pagina 

Site onderhouden door het LinuxFocus editors team
© Frédéric Raynal, Christophe Blaess, Christophe Grenier, FDL
LinuxFocus.org

Klik hier om een fout te melden of commentaar te geven
Vertaling info:
fr --> -- : Frédéric Raynal, Christophe Blaess, Christophe Grenier <pappy(at)users.sourceforge.net, ccb(at)club-internet.fr, grenier(at)nef.esiea.fr>
fr --> en: Georges Tarbouriech <georges.t(at)linuxfocus.org>
en --> en: Lorne Bailey <sherm_pbody(at)yahoo.com>
en --> nl: Hendrik-Jan Heins <hjh(at)passys.nl>

2002-06-08, generated by lfparser version 2.28