Site search Web search

powered by FreeFind
Visit my Guestbook

Privacy now! PGP

Freedom for Links

Weiter Zurück [Inhalt] Online Suche im Handbuch LITTLE-IDIOT NETWORKING

22.7 Warum Filter anfällig gegen buffer overflows sind

Das folgende Beispiel stammt von Eric Dumazet und findet sich auf ftp://ftp.ris.fr/pub/linux/proxy/. Es ist ein Beispiel für einen Proxy, wie man ihn auf vielen Firewalls implementiert finden kann. Viele Firewall - Hersteller benutzen oft Code aus dem Internet, um Ihrer Firewall noch ein paar spezielle Protokolle, hier einem VDO Proxy, hinzuzufügen. Dies ist nicht der in dem LINUX Kernel implementierte Proxy, sondern einer, der auf jedem UNIX einfach zu installieren ist. Der Autor hat auch einen transparenten HTTPD-PROXY geschrieben, der auch auf o.a. URL zu finden ist. Das Problem mit diesem Proxy ist folgendes. Es fehlen überall Begrenzungen für die maximal zulässige Länge der Übergabeparameter. Um feststellen zu können, welche Daten von wo aus an den Filter übergeben werden, muß man eine vollständige Flußanalyse des (hier noch überschaubaren Programmes) durchführen. Es müssen also folgende Fragen geklärt werden. Ein einziger Fehler im Quellcode ist bereits für einen Angreifer ausreichend, um in das Netzwerk hinter dem Proxy vorzudringen. Wo ist der Fehler ?:

  • Werden ARGC() ARGV() abgefangen ?
  • Wie fordern die Unterroutinen in stdio, netdb, netinet u.s.w Speicher an (malloc, realloc, vmalloc). Welche Routinen sind buffer overflow gefährdet ?
  • Welche Arrays sind statisch angelegt, welche Informationen können dort hineingeschrieben werden ? Ist ein buffer overflow möglich ?
  • Werden unzulässig Lange Strings vor dem Speichern in statische Arrays auf Überlänge abgefragt ? Wo ist ein Beispiel im Code ?
  • Welche Pointer auf Arrays oder Funktionen können mit unzulässigen Werten gefüllt werden. Wo werden diese Werte auf Zulässigkeit untersucht ?
  • Welche Fehlermeldungen werden an den SYSLOGD oder KLOGD bei welchen Fehlern übergeben ? Welche Fehler können vom Systemadministrator entdeckt werden ?
  • Nach welchem Mechanismus arbeitet der VDO Proxy ? Welche Ports (TCP/UDP) sind zu welchem Zeitpunkt geöffnet, welche bleiben unnötig lange geöffnet ? Welche TCP Ports bleiben offen ? Welche Angriffe sind möglich ?
  • Welche Ports werden nach Absprache mit dem VDO Video-Server im Internet in der Firewall bzw. in dem Proxy geöffnet ? Ist der Proxy manipulierbar dahingehend, daß eventuell die Firewall auch für andere Protokolle transparent wird ?
  • Wieviel Speicher verbraucht der Proxy in Abhängigkeit der Zahl der Video Datenströme ? Ist ein DoS möglich ?
  • Wie fängt der Proxy Spoofing Angriffe ab ? Wird ein double reverse lookup durchgeführt ?
  • Wird der IDENTD mit aktiviert ? Welche Informationen könnte dieser liefern ?
  • Kann man den Proxy mit einem TCP Wrapper betreiben ?
  • Kann der PROXY in einer CHROOT() Umgebung gestartet werden ?
  • Was passiert, wenn der das Format der Videodaten plötzlich verändert wird. Kann sich der Eingangspuffer dynamisch anpassen, oder ist ein buffer overflow möglich ?
  • Können Funktionen, wie strcpy() mit unzulässigen Werten aufgerufen werden (altes strcpy() Pointer Problem) ?
  • Ist die Library gefixt ?
  • Gibt es Parameter, die bei Übergabe an den PROXY diesen Killen (DoS) ?
  • Wieviele gleichzeitige Video Datenströme verträgt der PROXY ?

Wer sich intensiver mit dieser Materie auseinandersetzt, wird beim Lesen dieses Artikels über sicheres Programmieren unter UNIX, siehe http://www.whitefang.com/sup/secure-faq.html, daß viele Programme unter LINUX weit davon entfernt sind, sicher zu sein....

Für C-Spezialisten hier nun der vollständige Quellcode, der, bevor er auf einer Firewall eingesetzt werden kann, nach obigen Punkten abgesucht werden sollte. Wie schwer dieses ist, davon kann man sich an diesem sehr kleinen Beispiel selber überzeugen. Man bekommt vielleicht dann einen Eindruck davon, wieviel Mist oft Sicherheitsexperten erzählen (Windows NT ist sicher !, u.s.w.) und wie schwierig es ist, den Quellcode von 1500 Programmierern (Microsoft) und mehrere millionen Zeilen Quellcode nach Fehlern zu durchleuchten. Spätestens nach dieser kleinen Übung sollte auch der letzte Verfechter von Software ohne freien Quellcode davon überzeugt sein, daß viele Augen mehr sehen, als wenige, und daß es immer besser ist, nach dem KISS (Keep It Small and Simple) Prinzip die Software auszuwählen:


/*

 * vdoproxy.c

 *

 * AUTHOR : Eric Dumazet  edumazet@ris.fr

 * DATE : 19961015

 * This is a VDO live proxy for LINUX (with TRANSPARENT PROXY enabled)

 * Usage :

 *   First you must insure that connections for port 7000 are redirected

 *  ipfwadm -I -a acc -P tcp -S yournet/24 -D any/0 7000 -r 7000

 *  Then, launch vdoproxy (no arguments)

 */

#include    <stdio.h>

#include    <sys/types.h>

#include    <sys/time.h>

#include    <sys/socket.h>

#include    <netdb.h>

#include    <netinet/in.h>

#include    <arpa/inet.h>

#include    <signal.h>

#include    <syslog.h>

#include    <string.h>

#include    <stdlib.h>

#include    <ctype.h>

#include    <unistd.h>



#define VDO_TCP_PORT 7000



int  listen_port = 7000 ;

int  dflg ; /* debug flag */

int lflg ; /* log flag */

int  dest_port = 7000 ;

struct sockaddr_in dest_addr;

int trace_fd = 1 ;



void hexdump_err(p, len)

const char *p ;

int len ;

{

char _aux_bb[128] , c ;

int i , n ;

int dep = 0 ;

    while (len > 0) {

        sprintf(_aux_bb, "%3X: ", dep) ; dep += 16 ;

        i = 5 ;

        for (n = 0 ; n < 16 ; n++) {

            if (n < len) sprintf(&_aux_bb[i], "%02X ", p[n]&255) ;

            else strcpy(&_aux_bb[i], "   ") ;

            i+=3 ;

            }

        for (n = 0 ; n < 16 ; n++) {

            if (n < len) {

                c = p[n] ;

                _aux_bb[i] = (' ' <= c && c < '\177') ? c : '?' ;

                }

            else _aux_bb[i] = ' ' ;

            i++ ;

            }

        _aux_bb[i++] = '\n' ;

        write(trace_fd, _aux_bb, i) ;

        p += 16 ,

        len -= 16 ;

        }

}

/*

 * Basic functions : allocates a socket, and does a connect to the server,

 * on port 7000

 */

int serverconnect(struct sockaddr *nm)

    {

    int fd ;



    fd = socket(AF_INET, SOCK_STREAM, 0) ;

    if (fd == -1) {

                if (lflg) fprintf(stderr, "couldnt allocate socket\n") ;

                return -1 ;

                }

    dest_addr.sin_port = htons(VDO_TCP_PORT);

    dest_addr.sin_family = AF_INET ;

    dest_addr.sin_addr.s_addr = ((struct sockaddr_in *)nm)->sin_addr.s_addr

;

    if ( connect(fd, (struct sockaddr *)_addr, sizeof(dest_addr)) < 0 )

{

                if (lflg) fprintf(stderr, "couldnt connect to server :

%s\n", strerror(errno)) ;

        close(fd) ;

        return -1 ;

        }

    return fd ;

    }

/*

 * Open a socket in order to receive UDP datagrams

 */

int alloue_server_udp(int *sockudp, int *server_udp_port)

    {

    struct sockaddr_in name ;

    int len ;

        int res ;



    *sockudp = socket(AF_INET, SOCK_DGRAM, 0) ;

    if (*sockudp == -1) return -1 ;

    memset(, 0, sizeof(name)) ;

    name.sin_family = AF_INET;

    name.sin_addr.s_addr = INADDR_ANY;

/*    name.sin_port = 0 ;*/

    res = bind(*sockudp, (struct sockaddr *), sizeof(name)) ;

        if (res == -1) {

                perror("bind") ;

                close(*sockudp) ;

                return -1 ;

                }

        if (dflg) fprintf(stderr, "Avant getsockname port=%d\n",

ntohs(name.sin_port)) ;

    len = sizeof(name);

    getsockname(*sockudp, (struct sockaddr *) , ) ;

    *server_udp_port = ntohs(name.sin_port) ;

    if (dflg) fprintf(stderr, "port %d allocated\n", *server_udp_port) ;

    return 0 ;

    }

/*

 * Each connection is handled by a separate process.

 */

void do_child(int newfd)

    {

    struct  sockaddr name, namepeer ;

    struct  sockaddr_in where ;

        struct  sockaddr_in outudp ;

    struct sockaddr_in from;       /* Sending host address */

    fd_set rfd ;

        int already_bind = 0 ;

    char buffer[4096] ;

    char zone[64] ;

    int pos = 0 ;

    int namelen , fromlen, i ;

    int fd_to_proxy ;

    int maxfd ;

    int lu , res ;

    int sockudp , sockcl ;

    int client_udp_port, server_udp_port ;



    namelen = sizeof(namepeer) ;

    i = getpeername(newfd, , ) ;



    /* This hack, is the TRANSPARENT PROXY magic :

     * We want to know wich destination the client want to connect

     */

    namelen = sizeof(name) ;

    i = getsockname(newfd, , ) ;



        if (lflg || dflg) {

                time_t tnow ;

                struct tm *tm ;



                time() ;

                tm = localtime() ;

                fprintf(stderr, "%02d:%02d:%02d %d.%d.%d.%d:%d ->

%d.%d.%d.%d ",

                        tm->tm_hour,

                        tm->tm_min,

                        tm->tm_sec,

                        namepeer.sa_data[2] & 255,

                        namepeer.sa_data[3] & 255,

                        namepeer.sa_data[4] & 255,

                        namepeer.sa_data[5] & 255,

                        ntohs(((struct sockaddr_in *))->sin_port),

                        name.sa_data[2] & 255,

                        name.sa_data[3] & 255,

                        name.sa_data[4] & 255,

                        name.sa_data[5] & 255) ;

                }

    fd_to_proxy = serverconnect() ;

    if (fd_to_proxy == -1) exit(1) ;



    lu = read(newfd, buffer, sizeof(buffer)) ;

    if (dflg) {

        printf("From client : (%d)\n", lu) ;

        fflush(stdout) ;

        hexdump_err(buffer, lu) ;

        }

/*

 * On extrait de la demande du client le port UDP qu'il desire employer.

 */

    /*

     * Recherche "VDO Live" 

     */

    for (i = 0 ; i < lu ; i++) {

        if (memcmp(buffer + i, "VDO Live", 8) == 0) {

                        i += 8 ;

                    client_udp_port = ((buffer[i+2] & 255)<< 8) +

(buffer[i+3] & 255) ;

                    buffer[i+2] = server_udp_port >> 8 ;

                    buffer[i+3] = server_udp_port ;

                        break ;

                        }

                }

    alloue_server_udp(, _udp_port) ;



        /* on fait en sorte que les paquets que nous emettons aient comme

adresse source */

        /* l'adresse du serveur */

    sockcl = socket(AF_INET, SOCK_DGRAM, 0) ;







    where.sin_family = AF_INET ;

    where.sin_addr.s_addr = ((struct sockaddr_in

*))->sin_addr.s_addr ;

        if (dflg) fprintf(stderr, "s_addr %x ", 

                ntohl(where.sin_addr.s_addr)) ;

    where.sin_port = htons(client_udp_port) ;

    if (dflg) fprintf(stderr, "port udp du client : %d\n", client_udp_port)

;

    /*

     * Recherche 2eme "VDO Live" 

     */

    for ( ; i < lu ; i++) {

        if (memcmp(buffer + i, "VDO Live", 8) == 0) {

            pos = i + 10 ;

            break ;

            }

        }

    if (pos) {

        buffer[pos] = server_udp_port >> 8 ;

        buffer[pos+1] = server_udp_port ;

                if (lflg) fprintf(stderr, "%s\n", buffer + pos + 10) ;

        }



    write(fd_to_proxy, buffer, lu) ;

    if (dflg) {

        printf("To server : (%d)\n", lu) ;

        fflush(stdout) ;

        hexdump_err(buffer, lu) ;

        }



    FD_ZERO() ;

    maxfd = (fd_to_proxy > newfd) ? fd_to_proxy : newfd ;

    if (sockudp > maxfd) maxfd = sockudp ;

    maxfd++ ;

    for (;;) {

        FD_SET(newfd, ) ;

        FD_SET(fd_to_proxy, ) ;

        FD_SET(sockudp, ) ;

        i = select(maxfd, , 0, 0, 0) ;



        /* Is there a DATAGRAM ? */

        if (FD_ISSET(sockudp, )) {

                        fromlen = sizeof(from) ;

            res = recvfrom(sockudp, buffer, sizeof(buffer), 0,

                        (struct sockaddr *) , ) ;

            if (res > 0) {

                if (dflg) fprintf(stderr, "UDP (%d) %x:%d %x\n", res,

                        ntohl(from.sin_addr.s_addr),

                        ntohs(from.sin_port),

                        ntohs(from.sin_port)) ;

                                if (!already_bind) {

                                        int on = 1 ;

                                        already_bind = 1 ;

                                    outudp = from ;

                                    if (setsockopt(sockcl, SOL_SOCKET,

SO_REUSEADDR, (char *) , sizeof(on)) < 0) {

                                        perror("setsockopt(REUSEADDR)

problem") ;

                                        }

                                    if (bind(sockcl, (struct sockaddr *)

, sizeof(outudp)) == -1)

                                        perror("bind sockl") ;

                                        }

                sendto(sockcl, buffer, res, 0, (struct sockaddr *),

                    sizeof(struct sockaddr_in)) ;

                }

            }

        /* Is there any data from the client ? */

        if (FD_ISSET(newfd, )) {

            lu = read(newfd, buffer, sizeof(buffer)) ;

            if (lu <= 0) break ;

            write(fd_to_proxy, buffer, lu) ;

            if (dflg > 2) {

                printf("From client: (%d)\n", lu) ;

                fflush(stdout) ;

                hexdump_err(buffer, lu) ;

                }

            }

        /* Is there any data from server ? */

        if (FD_ISSET(fd_to_proxy, )) {

            lu = read(fd_to_proxy, buffer, sizeof(buffer)) ;

            if (lu <= 0) break ;

            write(newfd, buffer, lu) ;

            if (dflg > 2) {

                printf("From Proxy (%d):\n", lu) ;

                fflush(stdout) ;

                hexdump_err(buffer, lu) ;

                }

            }

        }

    _exit(0) ;

    }



/*

 * This function setups the listen port, and forks a child for each

 * connection

 */

void wait_conn(void)

    {

    struct  sockaddr_in addr;

    int sock, newfd;

    int on ;



    if( (sock = socket(AF_INET,SOCK_STREAM,0)) < 0 ) {

        perror("socket problem");

        exit(1);

    }

    memset(,0,sizeof(addr));

    addr.sin_port = htons(listen_port);

    addr.sin_family = AF_INET;

    on = 1 ;

    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) , sizeof(on))

< 0) {

        perror("REUSEADDR problem") ;

        }



    if (bind(sock, (struct sockaddr *), sizeof(addr)) ) {

        perror("bind problem");

        exit(1);

    }

    if( listen(sock, 5) < 0 ) {

        perror("listen problem");

        exit(1);

    }

    signal(SIGCLD, SIG_IGN) ;

    while (1) {

        if ((newfd=accept(sock, 0, 0) ) < 0) {

            perror("accept");

            continue ;

/*          exit(1);*/

            }

        if (fork() == 0) {

            close(sock) ;

            do_child(newfd) ;

            }

        close(newfd) ;

        }

    }



void usage(int exitcode)

{

fprintf(stderr, "Usage : vdoproxy [-V] [-d] [-l]\n") ;

fprintf(stderr, " -V : Display usage and version.\n") ;

fprintf(stderr, " -d : increase debug level.\n") ;

fprintf(stderr, " -l : log\n") ;

exit(exitcode) ;

}



int main(int argc, char **argv)

{

int c ;

extern int optind ;

extern char *optarg ;



while ((c = getopt(argc, argv, "Vdl")) != EOF) {

    switch (c) {

        case 'V' : usage(0) ; break ;

        case 'd' : dflg++ ; break ;

        case 'l' : lflg++ ; break ;

        default : usage(1) ;

        }

    }

wait_conn() ;

return 0 ;

}

Als Vergleich mag der Abschnitt über die Absicherung von PERL Skripten dienen: Kapitel PERL Sicherheit bei WWW-Servern....


Weiter Zurück [Inhalt] Online Suche im Handbuch LITTLE-IDIOT NETWORKING