Sicherheitsaspekte der CGI-Programmierung


von cg1h3r0@illegalaccess.org

Grundüberlegung bei der CGI-Programmierung sollte immer die Annahme sein, dass man niemals dem Browser und/oder dem daran hantierenden User trauen sollte. Aus diesem Grund sollte man die Verwendung der Shell vermeiden und zusätzlich den Taint-Modus aktivieren. Dies sollte in Einklang mit einem Systemdesign erfolgen, welches so gestaltet sein sollte, dass ein Eindringling (Cracker) keinen Vorteil aus einem Einbruch gewinnt. Er soll höchstens Spuren hinterlassen, die zu seiner Überführung dienen. WWW-Server werden häufig als Angriffspunkte gewählt, weil sie den exponiertesten Punkt einer Unternehmung (buchstäblich im Wohnzimmer des Kunden) bilden. Auch sind die Komponenten schnell ausspioniert und Schwächen bekannt. Eigene Programme sollten deshalb aus diesen Fehlern lernen und die Sicherheitsmöglichkeiten des darunterliegenden Softwarestacks auch nutzen. Wichtige Daten sollten aus dieser latenten Gefahr nicht physikalisch auf demselben Rechner wie der Web-Server gespeichert werden. Zudem ist in der Netzkonfiguration darauf zu achten, dem Web-Server wenig "Vertrauen" entgegenzubringen. Ein Eindringling wird sich zuerst den Rechner mit dem Web-Server unter seine Kontrolle bringen, und von dort aus seine Angriffe auf das Inhouse-Netz starten. Grundsätzlich gilt bei jeder Anwendung, die für andere mit eigenen Privilegien läuft, die Beachtung strenger Sicherheitsrichtlinien. Das betrifft insbesondere ausführbare Dateien, die mit einem s-bit ausgestattet sind (das heißt sie laufen unter UNIX mit anderen Privilegien als denen des aufrufenden Benutzers), und insbesonderem Maße für Internet-Anwendungen, da sie für jeden Informationsanbieter einen möglichen Angriffspunkt darstellen.

Gründe zur Nutzung des Taint-Modus

Neben den üblichen Techniken zur Vermeidung von Programmierfehlern (``-w'' auf der Kommandozeile und use strict), gibt es hierfür noch eine weitere wertvolle Unterstützung: den ``taint mode'', der entweder automatisch eingeschaltet wird (bei Skripten mit s-bit) oder auch explizit gewünscht werden kann (Option ``-T'' auf der Kommandozeile).

Der Taint-Modus von Perl bewirkt von sich aus erst einmal nichts Magisches, er achtet nur wie ein Coach darüber , dass der Programmierer keine großen Löcher in den sicheren Schutz des Systems reisst. Diese Aufgabe könnte der Programmierer auch selbst übernehmen, aber dieses eingebaute Feature kostet keine Mark mehr. Deshalb kann man auch die rhetorische Frage stellen "Warum sollte der Taint-Modus nicht genutzt werden ?"

Viele CGI-Entwickler scheuen den Taint-Modus, jeder aus eigenen Motiven, z.B. weil er als zu kompliziert erscheint, zu schwer zu durchschauen oder er legt dem Programmierer zu viele Ketten an. Nun, nicht viel ist umsonst, aber der Taint-Modus schon.

Diese Urteile kommen oft aus einem Unverständnis über den Taint-Modus heraus. Es ist halt einfacher, die mahnende Instanz einfach auszustellen, anstatt lernen zu wollen wie die von Perl aufgedeckten Sicherheitslücken gestopft werden können.

Performance wird oft als Argument genannt, den Taint-Modus auszustellen, aber Untersuchungen haben gezeigt, dass keine nennenswerten Einbussen in der Geschwindigkeit zu erwarten sind. Dies kann der interessierte Leser auch selbst mit dem Modul "Benchmark" ausprobieren.

Die Änderungsfrequenz an CGI-Programmen ist oft sehr hoch, aus diesem Grund verbleibt selten ein CGI-Programm in seiner Ursprungsform, denn Wartungsmassnahmen und Programmerweiterungen sind oft nötig und werden zudem nicht immer von derselben Person durchgeführt. Zusammenfassend kann man den Taint-Modus als Vorfeld-Sicherheitscheck bezeichnen, den es bei Perl als besonderen Service gibt.

Grundprinzipien des Taint-Modus

Die erste Quelle für den nach Sicherheit strebenden Perl-Programmierer sollte immer das Perlsec-Manual sein. Dort wird in angenehmer Tiefe auf den Taint-Modus eingegangen, der Perl-Programme automatisch Sicherheits-Checks unterzieht. Dieser Laufzeit-Modus wird durch das Flag -T aktiviert, und auch immer dann wenn sich die ID der realen und der effektiven Gruppe oder des Users unterscheidet. Für serverseitige Programme (z.B. CGI-Scripts) sollte man den Taint-Modus als Pflicht ansehen. Es wird sichergestellt, dass die Pfad-Verzeichnisse nicht von anderen beschrieben werden dürfen. Und es werden Variablen daraufhin gecheckt, ob sie von außerhalb gefüllt werden, und somit den Programmablauf (unsinngemäß) beeinflussen können. So ist der Fall denkbar in dem über eine Kommandozeilenoption oder einen `echo $ENV{īVARī} eine Variable gefüllt wird, deren Inhalt in einem system-Aufruf weiterverwendet wird. Würde es jetzt einem Angreifer gelingen, diese Variable zu beeinflussen und dort ein "| rm -r *" hineinzuschreiben, so würde dieser Befehl ausgeführt werden. Eine blitzblank leere Festplatte wäre bei entsprechend gesetzten Rechten das Ergebnis eines solchen Angriffs (manche Leute lassen ihren Webserver ja immer noch als Root laufen).

Externe Daten die auf diese Art in ein CGI-Programm gelangen, werden als kontaminiert (engl. Taint = Schmutz) bezeichnet. Sie dürfen im Taint-Modus nicht direkt oder indirekt an Subshells übergeben oder in Befehlen verwendet werden, die auf Dateien und Prozesse zugreifen. Aufgabe des aufmerksamen CGI-Programmierers ist es, diese Daten zu reinigen. Dies kann durch die Entnahme von "gutartigen" Daten aus den Taint-Daten geschehen. Man benutzt zu diesem Zweck die Textfilter-Mechanismen von Perl, mit denen man Teilzeichenketten aus Strings gewinnen kann. Werden aus Taint-Daten die bekannten Substrings $1, $2 usw. gewonnen, so werden diese als "gereinigt" angesehen. Der Perl-Compiler vertraut an dieser Stelle der Wirksamkeit des vom Programmierer eingesetzten Filter. Geht dieser verantwortungsvoll vor, wird er z.B. alle Pipe-Symbole entfernen, um den Aufruf von ungewünschten Dritt-Programmen zu vermeiden. Er kann sich aber auch schnell seiner Aufgabe entledigen und mit $taint_data =~ (.*) Daten die Daten aus der unsauberen Variable schnell eins zu eins in die von Perl als sauber angesehene Variable kopieren. Der Sinn des Taint-Modus wäre an dieser Stelle ad absurdum geführt worden. Zu beachten ist außerdem, dass die Weitergabe von Werten die in Listen stehen nicht auf Taint-Effekte überprüft wird.

Beispiel:

#!/usr/local/bin/perl -wT
$cmd = ; # $cmd ist 'tainted'
system($cmd); # wird wg. -T abgewiesen.

Die Befehle Open, Glob und der Aufruf von Programmen mittels Backticks führt ebenfalls zu Sub-Shell-Aufrufen (zur Auflösung der Wildcards ?, * und ~ ). Oft kann der Befehl Open durch Sysopen() ersetzt werden, der einen viel höheren Sicherheitslevel ermöglicht.

Probleme mit dem Taint-Modus

Wenn man sich zum ersten Mal mit dem Taint-Modus beschäftigt, kann das schon etwas frustrierend sein. Perl beschwert sich anscheinend über jedes kleine Detail. Wenn man ein wenig Erfahrung gewonnen hat, achtet man aber schon von Anfang auf diese Details und schreibt fast automatisch sicheren Code ohne sich erst im Nachhinein große Gedanken darüber zu machen.

Anbei einige Tips, die bei der Lösung der häufigsten Probleme bei der Anwendung des Taint-Modus auftreten, erleichtern:

  • Der Pfad (Envirormentvariable PATH) muss sicher sein. Wenn man schon externe Programme zugreifen muss (kann man die Aufgabe nicht in Perl lösen ?), sollte man sicher sein, dass der Hash-Wert $ENV{PATH} keine Verzeichnisse enthält, die von jemand anderem als ihrem Eigentümer. Eine trügerische Sicherheit, liegt darin dass man das externe Programm über seinen vollständigen Pfad aufruft. Sollte das externe Programm jetzt DLLs (oder bei einem Unix-System Shared-Objects) laden, so wird der (modifizierte ?) Pfad des Aufrufes benutzt.

  • Das Array @INC beinhaltet nicht das aktuelle Arbeitsverzeichnis. Sollte das Skript den Code in einer Datei im aktuellen Verzeichnis benötigen (z.B. über require) muss dieses Verzeichnis explizit zu @INC hinzugefügt werden, oder es muss der gesamte Pfad zum auszuführenden Kommando angegeben werden.

  • Der Pfad ist nicht die einzige Umgebungsvariable die Problemursache sein kann. Weil einige Shells die Variablen IFS, CDPATH, ENV und BASH_ENV in Ihrer Suche nach Executables berücksichtigen, checkt Perl ob diese entweder leer oder nicht-kontaminiert sind. Aus diesem Grund ist es sinnvoll, alle riskanten Environment-Variablen zu entfernen. Aus diesem Grund sollte in jedem CGI-Script eine Routine wie die folgende angewandt werden (mit Anpassung des Pfades an die eigenen Gegebenheiten):

$ENV{PATH} = "/bin:/usr/bin"
delete @ENV{ 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}

 

Kontaminierte Variablen

Der Taint-Modus betrachtet folgende Variablenkonstellation als kontaminiert:

  • alle Kommandozeilen-Argumente,

  • alle Betriebssystem-Umgebungs-Variablen,

  • Locale-Informationen (locale),

  • Systemaufrufsresultate wie readdir oder readlink,

  • das gecos-Feld aus der Passwort-Datei (beim Zugriff über die getpw*-Prozeduren),

  • Resultate von `...` und alle Eingaben von Dateien, Netzwerkverbindungen oder anderen Kanälen

  • Eine abhängige Variable, d.h. wenn eine Variable von einer kontaminierten Variable gesetzt wird, wird ebenfalls kontaminiert -- selbst wenn die Operation garantiert ungefährlich ist (z.B. weil der ursprüngliche Wert konstant ist).

  • Die Kontaminierung ist nur bei mit einzelnen skalaren Werten prüfbar und nicht bei größeren Datenstrukturen. So können bei einer Liste einige Elemente kontaminiert sein, während andere davon frei sind.

 

Dekontaminierung

#!/usr/bin/perl -Tw
 # ...
 my $dateiname= shift @ARGV; # tainted
 unless ($dateiname =~ /^(\w+)$/) {
 die "Ungültiger Dateiname: $ dateiname \n";
 }
 $ dateiname = $1; # no longer tainted
 open(OUT, ">$ dateiname ") || # OK
 die "kann die Datei nicht öffnen: $ dateiname: $!\n"
 # ...
       
 

Die mit Klammern erfaßten Teile eines regulären Ausdrucks werden als frei von Kontaminierung betrachtet -- auch dann wenn die Zeichenkette, die dem regulären Ausdruck zugeführt wurde, kontaminiert ist. Damit ist es möglich, Zeichenketten nach einer Überprüfung gegen einen regulären Ausdruck normal zu verwenden. Natürlich muß darauf geachtet werden, dass diese Überprüfung gewissenhaft geschieht. Wenn ein regulärer Ausdruck nicht der Sicherheitsüberprüfung dient und erkannte Teile davon verwendet werden, sollten sie ggf. mit dem Taint-Befehl als kontaminiert gekennzeichnet werden:

  
if ($adresse =~ /(.*)@(.*)/) {
    use Taint qw(taint);
    $benutzer = $1; $domaene = $2;
    if (tainted $adresse) {
       taint $benutzer; 
       taint $domaene;
    }
 }

Do's and Don'ts

Folgende Operationen sind aus Sicherheitsgründen zu vermeiden und deshalb im Taint-Modus unzulässig , wenn sie von einer kontaminierten Variable abhängen:

  • Kommandos, die direkt oder indirekt eine Shell aufrufen,

  • Operationen, die Dateien oder Verzeichnisse modifizieren und

  • Operationen, die Prozesse beeinflussen.

  • Es ist zwar nicht möglich, eine Datei mit einem kontaminierten Namen schreibend zu öffnen, aber doch lesend, obwohl dies auch ein Risiko darstellen kann (z.B. bei /etc/shadow). Insofern ist es sehr sinnvoll, selbst bei kritischen Operationen mit Hilfe des Taint-Moduls zu überprüfen, ob Abhängigkeiten vorliegen:

croak "Unsicherer Zugriff" if tainted $var;

Sehr restriktiv ist Perl bei der Ausführung von externen Kommandos und Skripten-- das ist praktisch unmöglich, solange die Umgebungsvariable $ENV{'PATH'} nicht auf einen nicht-kontaminierten Wert gesetzt worden ist.

Zusammenfassend läßt sich feststellen, dass viele Techniken, die der Fehlerreduzierung dienen, sich auch auf die Sicherheitsproblematik übertragen lassen.

Literatur