Alexander (HH): /Sicherheit: Untaint my Vars und per sudo Ausführen

Beitrag lesen

Moin Moin!

Lektüre: http://cr.yp.to/ucspi-tcp.html, insbesondere http://cr.yp.to/ucspi-tcp/tcpserver.html, das fast genauso arbeitet. Allerdingss wird dort erst die fertig aufgebaute Client-Verbindung weitergereicht, nicht schon der offene Server-Port.

Wo finde ich denn den Quellcode der Programme? Dieser Ansatz ist sinnvoller.

Exakt dort. How to install ucspi-tcp.

Die daemontools solltest Du auch kennen, die machen das Schreiben eines Daemons samt zuverlässigem Logger zu einem Kinderspiel. Für einen trivialen Daemon kommst Du mit 10 Zeilen Shell-Script aus. Siehe auch the djb way. Beachte dass die daemontools dank der Dickköpfigkeit von DJB nicht Hilfe unter Linux compilieren und typischerweise bei errno auf die Nase fallen. Das liegt daran, dass DJB die entsprechenden Standards nicht verstehen will. Abhilfe.

Hiermit fällt dann nämlich auch das Problem mit den Pfaden und Dateinamen weg. (BTW: Wie bekomme ich denn unter Unix/Linux den kompletten Pfad eines ausgeführten Programms? $0 oder argv[0] in C enthalten nur den Namen, wie er auf der Konsole eingegeben wird.)

Im Zweifel gar nicht.

Je nach Aufrufer kann argv[0] beliebige Werte annehmen. Viele Shells nehmen, was eingetippt wurde, andere Shells blasen argv[0] auf den vollen Namen auf. Manche lösen Symlinks auf (was z.B. für Busybox fatal wäre), andere nicht. Wieder andere Programme setzen auch mal ganz andere Dinge in argv[0] ein. login startet die Shell (in /bin/sh) typischerweise mit argv[0]="-/bin/sh".

Viele, aber nicht alle Unixe haben ein /proc-Dateisystem, von denen haben wiederum einige den Symlink /proc/self, andere nicht, dort mußt Du Dir die PID über getpid() besorgen. Schließlich findest Du in /proc/self bzw. /proc/$PID gelegentlich einen Symlink, der auf das zum Prozess gehörende Executable zeigt. Unter Linux heißt der Symlink exe, andere Unixe haben andere Namen. /proc muß übrigens nicht zwingend gemountet sein, und andere Unixe verstecken diese Information auch gerne mal an anderer Stelle.

Übrigens: Rate mal, was folgendes Programm unter Linux ausspuckt. Und zwar egal, wie Du es aufrufst. Nicht mogeln! Erst mal aufschreiben, dann ausprobieren:

  
#!/usr/bin/perl  
print readlink("/proc/self/exe"),"\n";  

1. chmod +x /tmp/self.pl && /tmp/self.pl
2. ( cd /tmp && ./self.pl )
3. perl /tmp/self.pl
4. ( cd /tmp && perl self.pl )

Du kannst ja spaßeshalber auch mal $0 und $^X ausgeben lassen:

  
#!/usr/bin/perl  
print "/proc/self/exe=",readlink("/proc/self/exe")," \$0=$0 \$^X=$^X\n";  

Du suchst den absoluten Pfad des aktuellen Scripts, und der ist nicht immer leicht zu ermitteln. FindBin schafft das oft, aber nicht immer. Einige Probleme sind in der Doku beschrieben, aber wohl nicht alle.

Ich denke, wenn Du dich auf den absoluten Pfad verlassen mußt, hast Du ohnehin ein konzeptionelles Problem. Du rennst dann fast automatisch in irgendwelche Race Conditions / TOCTOU rein.

Der Vollständigkeit halber die weiteren Bugs:

untainting $0 and @ARGV:

if ($0 =~ /^(./[[:alpha:]]+)/) {      # lasse nur lokale Dateien zu
        $app = $1


> >   
> > Was genau läßt Du hier zu?  
>   
> Oha, da fehlte noch ein $:  
>   
> ^(\.\/[[:alpha:]]+)$  
  
Richtig, und an dieser Stelle hilft Dir selbst der Taint-Mode nicht mehr. Du hast zu schlecht geprüft, und mir fällt auf Anhieb keine Automatik ein, die das prüfen oder wenigstens erkennen kann.  
  
Genau aus diesem Grund sollte man sicherheitskritischen Code nicht alleine konzipieren oder schreiben, und schon gar nicht "zwischen Tür und Angel". Man übersieht so etwas viel zu leicht.  
  

> > >                 # schlaegt fehl falls @ARGV nicht untainted werden konnte  
> >   
> > Und im Fehlerfall ignorierst Du den Fehler komplett  
>   
> Mein perl 5.8.8 terminiert ein Skript bei der unzulässigen Verwendung einer tainted var. Etwas Anderes wäre an dieser Stelle auch von mir aus nicht sinnvoll.  
  
Was passiert, wenn exec() fehlschlägt?  
  
Weder Linux noch POSIX garantieren, dass exec() immer funktioniert und nie zurückkehrt. Perl übrigens [auch nicht](http://perldoc.perl.org/functions/exec.html).  
  
Und übrigens: Nicht jedes Unix hat sudo an Bord. Windows schon mal gar nicht. Dann schlägt exec() garantiert fehl. Solaris beispielsweise hat stattdessen [pfexec](http://docs.sun.com/app/docs/doc/816-5165/pfexec-1?a=view), natürlich mit völlig anderen Parametern. Wäre sonst ja auch zu einfach.  
  
In Deinem kleinen Script rennst Du in ...  
  
~~~perl
  
my $socket = new IO::Socket::INET (  
        Listen          => 5,  
        LocalPort       => $port,  
        Proto           => 'tcp',  
        Reuse           => 1,  
        );  
die $! unless $socket;  

... rein. Und das fällt unter Linux wegen $port<1024 fürchterlich auf die Nase. Glück gehabt. ;-)

Im Ernst: Ich würde (ohne tcpserver und ähnliche Helferlein) das ganze "ich mach mich selbst zu root"-Zeug ersatzlos streichen und das Programm an exakt dieser Stelle ins Messer laufen lassen. Wenn das Programm auf einem OS läuft, dass die Low Ports auf root beschränkt, fällt es ohne root-Rechte auf die Nase. Das das so ist, steht in der Dokumentation, ebenso der Hinweis, dass man sich in dem Fall irgendwie root-Rechte verschaffen muß. Wenn das Programm auf einem OS oder dem Versuch eines OS läuft, dass diese Einschränkung nicht hat, wie z.B. Windows, brauchst Du das ganze "ich werde root"-Theater ohnehin nicht.

Damit wird Dein Code drastisch kürzer und portabler. Was nicht da ist, kann keine Fehler enthalten. Letztlich machst Du es einem Mit-Arbeiter leichter, den Code zu lesen, zu verstehen und Fehler zu finden.

Wieso schreibst Du eigentlich die $! unless $socket? Erstens ist das nicht das gewohnte Pattern ... or die, zweitens fehlt da noch Text. Hast Du schlecht bei perlipc/TCP Servers with IO::Socket abgeschrieben? Dort fehlt übrigens seit Jahren die Fehlermeldung ($!).

Und die Indirect Object Notation $object=new CLASSNAME (@args); sollte man sich auch verkneifen, weil die nicht immer eindeutig ist und daher mehr Arbeit beim Parsen macht.

Schreib's so:

  
my $socket=IO::Socket::INET->new(  
        Listen          => 5,  
        LocalPort       => $port,  
        Proto           => 'tcp',  
        Reuse           => 1,  
) or die "Failed to create server socket: $!";  

Alexander

--
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".