Veilige scripts schrijven

Soorten scripts

Scripts zijn niet alleen leuk, ze zijn ook erg handig. De meeste administratieve taken op een systeem (backups, logs verwerken) kunnen geimplementeerd worden door middel van een script. Er zijn een paar variaties van scripts, waarbij de bekendste shell- en perlscripts zijn. Deze scripts wonen rustig in je systeem, totdat ze aangeroepen worden. Als dat gebeurt, wordt de script-interpreter gestart (bash, perl) en wordt het script aan de interpreter gevoerd. Alle commando's in het script worden hierdoor uitgevoerd.

Verder zijn er nog scripts die op een webserver draaien. Zodra je een bepaalde pagina opvraagt worden ze opgestart. Ze worden ofwel gewoon door de interpreter uitgevoerd, zoals hierboven is uitgelegd, of ze worden door de webserver uitgevoerd. Scripts die aan te roepen zijn via een webpagina worden CGI scripts genoemd. Overigens zijn ook gewone programma's aan te roepen via een webpagina.

Veiligheid van scripts

Een script wat je uitvoert vanaf de CLI heeft vaak geen veiligheidsgaten, omdat het zo simpel opgezet is. Zeker als je script onder een normaal gebruikersaccount (dus niet als root) wordt uitgevoerd, zal het veiligheidsrisico nihil zijn. Toch kennen zulke scripts ook gaten in de beveiliging, zoals symlink-attacks of kritieke races.

Een script wat uitgevoerd wordt door de webserver is veel gevaarlijker, omdat er interactie moet zijn tussen de websitebezoeker en het programma. De websitebezoeker kan een goedaardige meneer zijn die alleen uw website wilt bezoeken, maar het kan net zo goed iemand zijn met minder goede bedoelingen.

Om u te wapenen tegen slecht geschreven scripts zal ik hier een poging doen uit te leggen wat er mis kan gaan bij het schrijven van een script. Het eerste deel gaat over alle scripts, verderop worden de gaten die specifiek zijn voor CGI scripts behandeld.

Algemene aanvallen

Deze aanvallen zijn niet specifiek voor scripts. Sommige gewone programma's hebben (of hadden) er ook last van. Toch zullen ze meer in scripts voorkomen omdat de programmeurs van scripts vaak een beetje aanrommelen, terwijl programma's meestal door echte programmeurs worden geschreven.

Symlink attack

#!/bin/sh

grep "Chapter" chap01.txt > /tmp/chapters.tmp
echo "Number of chapters:"
wc -l /tmp/chapters.tmp
echo "Names of chapters:"
cat /tmp/chapters.tmp
rm /tmp/chapters.tmp

Dit script telt het aantal hoofdstukken in chap01.txt en meld vervolgens de titels van deze hoofdstukken. Hiervoor gebruikt het een tijdelijk bestand /tmp/chapters.tmp. Helaas kan elke gebruiker in de /tmp directory schrijven, en gebruiker h4x0r maakt hier gebruik van: hij maakt een symlink van /tmp/chapters.tmp naar bijvoorbeeld /etc/shadow en wacht tot het script uitgevoerd wordt door de root (die bezig is een boek over veilige scripts schrijven). Zodra dit gebeurt, wordt /etc/shadow overschreven door wat onnuttige hoofdstuk-namen en is het onmogelijk geworden om in te loggen op het systeem.

De root-gebruiker had natuurlijk nooit zijn root-account mogen gebruiken voor het schrijven van een boek, maar het bovenstaande script is natuurlijk ook een voorbeeld. Bedenk de gevolgen als een script data naar een bestand schrijft wat door de gebruiker te beinvloeden is:

#!/bin/sh

grep "porn" /var/spool/mail/* >> /tmp/email.log
cat /tmp/email.log | mail -s "Someone is mailing porn around!" sysadmin
rm /tmp/email.log

Bovenstaand script kan gebruikt worden door de systeembeheerder om tegen te gaan dat mensen tijdens werktijd zich vermaken met onnodig bloot. Als een hacker van dit script af weet kan hij vrij gemakkelijk het systeem aanvallen. Het enige wat hij hoeft te doen is een symlink te maken van /tmp/email.log naar /etc/passwd en iemand een e-mail te sturen met de volgende tekst op een regel:

porn::0:0::/:

De persoon die het mailtje leest zal waarschijnlijk niet eens doorhebben wat dit is en het negeren. De hacker heeft ondertussen een account "porn" erbij gemaakt, met superuser rechten.

Kritieke races

TEMPFILE=/tmp/bla.$$
test -x TEMPFILE && exit 1
...

Om bovenstaande aanval met symlinks onmogelijk te maken, wordt in dit script getest of het bestand al bestaat. Als het niet bestaat gaat het script gewoon door, maar als het al bestaat is dat een teken van een mogelijke aanval en stopt het script meteen.

Dit lijkt misschien heel veilig, maar als het een hacker lukt om de symlink te installeren precies nadat de test is uitgevoerd, lukt het hem alsnog om een bepaald bestand te overschrijven. Het vereist goede timing om deze actie tot een succes te leiden, maar het is zeker niet onmogelijk.

Gaten in CGI scripts

Commando's met gebruikersinput

<?php
echo 'Words found with '.$_GET['query'].' in it:<pre>';
passthru('grep '.$_GET['query'].' /usr/share/dict/words');
echo '</pre>';
?>

Dit script doorzoekt het bestand /usr/share/dict/words met het programma grep op woorden die een string bevatten die door de gebruiker opgegeven kan worden.

Een gebruiker kan echter ook de volgende pagina opvragen:

http://www.example.org/page.html?query=;cat%20/etc/passwd;

In dat geval wordt het volgende uitgevoerd:

grep ;cat /etc/passwd; /usr/share/dict/words

De tekens %20 zijn vervangen door een spatie en een puntkomma is het scheidingsteken van commando's in de meeste shells. Wat er nu gebeurt is dat de gebruiker keurig de inhoud van /etc/passwd op zijn scherm krijgt, informatie die helemaal niet bedoeld was om via deze pagina de rest van de wereld te bereiken. Als shadow-passwords niet zijn ingeschakeld of als de server als root draait heeft de hacker alle informatie die hij nodig heeft voor root toegang tot uw computer.

De oplossing hier is om te controleren of de invoer wel geldig is. Dit kan door middel van reguliere expressies en PHP heeft zelfs een expliciet commando wat hierop controleert (escapeshellarg, escapeshellcmd).

Variabelen manipuleren

<?php
if ($happy_mode=="y") $cmd="cat /var/www/happy";
if ($happy_mode=="n") $cmd="cat /var/www/sad";
...

In PHP wordt de gebruikersinput, ingegeven via GET op POST, gelijk tot een variabele gemaakt (bij een bepaalde configuratie). Bij bovenstaand voorbeeld wordt er vanuit gegaan dat de variabele happy_mode door de gebruiker gegeven wordt, bijvoorbeeld door een formulier waarmee de gebruiker op deze pagina kwam. Natuurlijk kan de gebruiker andere dingen naar dit script sturen. Wat nu als een hacker de volgende pagina opvraagt:

http://www.example.org/happy_script.html?happy_mode=a&cmd=cat%20/etc/passwd

Geen van de bovenstaande if-statements komen overeen met de waarde van happy_mode, en de variabele cmd wordt ingesteld, terwijl deze alleen door het script ingesteld had mogen worden.

U kunt om dit probleem op te lossen de configuratie van PHP aanpassen, waarna u alleen nog van de gebruikersinput kunt genieten door variabelen als $_GET[], $_POST[] en $_COOKIE[] te gebruiken. Zet hiervoor register_globals uit in php.ini. De PHP handleiding heeft een deel over veiligheid, lees het als u PHP gebruikt.

Een vergelijkbare fout in Perl ziet er als volgt uit:

@@params = $query->param();
foreach $param (@params) {
${$param} = $query->param($param);
}

Ook deze code maakt van iedere invoer een variabele. Zo kan een gebruiker iedere willekeurige variabele aanmaken, wat gevaarlijk kan zijn als u niet geheel veilige scripts schrijft.

Verbogen velden

Veel pagina's maken gebruik van verborgen velden om informatie naar de volgende pagina te loodsen in een serie pagina's:

<form action="/scripts/checkout.html">
Credit card number: <input type="text" name="ccnumber">
<input type="hidden" name="price" value="22.50">
<input type="submit">
</form>

Hoe gemakkelijk zou het hier zijn om het artikel voor minder dan 22,50 te bestellen? Wellicht hoeft u alleen het verborgen veld aan te passen.

Een oplossing hiervoor is om de gegevens op de server op te slaan. Sommige scripttalen en webservers ondersteunen sessies, waar u variabelen in op kunt slaan zonder dat de gebruiker ze ooit te zien krijgt. Dit kan echter de applicatie zwaarder maken dan nodig en de gebruiker moet hier waarschijnlijk cookies voor hebben aanstaan. De informatie in cookies stoppen is overigens een slecht idee, want cookies kunnen ook vervalst worden zonder al te veel moeite. U kunt wel de informatie opslaan in bestanden op de server, maar dan moet u wel bijhouden van welke bezoeker welk bestand is.

Een andere methode is om een checksum (bijvoorbeeld een MD5 checksum) te maken van een geheim wachtwoord en de data. Het wachtwoord slaat u op op de server, de data en de checksum stuurt u gewoon door zoals hierboven is getoond. Als er nu met de data gerommeld wordt, is de checksum ongelijk aan de checksum die meegestuurd is en kunt u van een inbraak uitgaan.

Door de gebruiker ingevoerde code

Wat op veel pagina's terugkomt is een forum of een gastenboek. Altijd leuk. Maar wat als een gebruiker HTML code op deze pagina post? Veel webmasters zullen dit toelaten, zodat de gebruikers hun tekst kunnen onderstrepen of vet maken. Toch kan dit ook een veiligheidsgat zijn.

Een gebruiker kan bijvoorbeeld een afbeelding op de achtergrond van uw pagina neerzetten die u daar helemaal niet bedoeld had. Met een afbeelding van enkele MiBi's zal uw pagina snel onbruikbaar worden. Ernstiger wordt het nog als u ook JavaScript, PHP of Perl code op uw pagina toe laat. JavaScript kan leiden tot ernstige problemen met uw pagina, maar PHP of Perl kan uw hele systeem in gevaar brengen.

Een oplossing is om HTML helemaal uit te schakelen. Wilt u dat gebruikers nog wel opmaak kunnen gebruiken, overweeg dan nep-tags zoals [b]bla[/b] te gebruiken en die te parsen tot echte HTML. Schakel alle echte HTML uit en geef niet toe aan het simpelweg vervangen van de vierkante haakjes ([]) in driehoekige haakjes (<>).

Conclusie

Veilig programmeren is een zorgvuldige bezigheid. Bedenk bij alles wat u schrijft of iemand het zou kunnen misbruiken en geef iemand niet meer toegang tot uw computer dan noodzakelijk is. Dat het script niet toegankelijk is van buitenaf is geen reden om slecht te programmeren: als de hacker toch via andere manieren binnen komt, is uw programma misschien de manier om meer toegang te krijgen.

Leesvoer

Einde