

| |
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....
|