Anfang und Mitte der 90er Jahre war IP-basierte Authentifikation via RSH und Rlogin verbreitet, weil kryptografische Verfahren wie SSH noch nicht verfügbar waren. Die Kombination vorhersagbarer TCP-Initial-Sequence-Numbers (ISN) und einfaches IP-Spoofing machten Attacken gegen diese Dienste damals ebenso einfach wie populär. Als Reaktion wurde im Jahr 1996 im Request for Comments (RFC) 1948 ein Hash-basiertes Verfahren zur Erzeugung der ISNs vorgeschlagen, welches als MD4-Hash auch in den Linux Kernel wanderte. Anfang August erfolgte die Umstellung dieser Hash-Funktion im Linux Kernel von MD4 auf das sicherere MD5 und erschwerte so das Erraten von ISNs ungemein.
TCP ist im Gegensatz zu UDP ein verbindungsorientiertes Protokoll, das für zuverlässige Kommunikation zum Einsatz kommt. Um eine Verbindung aufzubauen verwendet TCP einen gebräuchlichen Drei-Wege-Handschlag, der wie folgt zwischen zwei Parteien A und B abläuft:
(1) A -> B: SYN, ISN_A
(2) B -> A: SYN, ISN_B, ACK(ISN_A)
(3) A -> B: ACK(ISN_B)
SYN und ACK sind hierbei Bits, die in einem 6 Bit langem Control-Flag des TCP-Headers gesetzt werden. Darin lassen sich noch weitere Bits wie URG, ACK, PSH, RST, SYN und FIN kodieren. Die einzelnen Schritte des Handschlags sehen so aus:
(1) A sendet seine ISN an B mit gesetztem SYN-Flag
(2) B bestätigt den Empfang von ISN_A via ACK und sendet seine eigene ISN (ISN_B)
(3) A bestätigt den Empfang von ISN_B via ACK
Danach ist der Handschlag abgeschlossen und die TCP-Verbindung ist korrekt initialisiert. Die genaue Erzeugung der TCP ISN ist in RFC 793 beschrieben, wonach die ISN einem 32-Bit Counter folgen soll, der alle 4 Mikrosekunden um eins erhöht wird. Die meisten Betriebssystem halten sich grob an diese Vorgabe, weichen aber teils leicht davon ab, so auch der Linux Kernel, der das Inkrement wie folgt realisiert:
static u32 seq_scale(u32 seq)
{ /* * As close as possible to RFC 793, which * suggests using a 250 kHz clock. * Further reading shows this assumes 2 Mb/s networks. * For 10 Mb/s Ethernet, a 1 MHz clock is appropriate. * For 10 Gb/s Ethernet, a 1 GHz clock should be ok, but * we also need to limit the resolution so that the u32 seq * overlaps less than one time per MSL (2 minutes). * Choosing a clock of 64 ns period is OK. (period of 274 s) */ return seq + (ktime_to_ns(ktime_get_real()) >> 6);
}
Wie beschrieben versucht der Kernel damit so konform wie möglich zu RFC 793 zu sein.
Das Problem des Drei-Wege-Handschlags bei Verwendung IP-basierter Authentifikation besteht darin, dass ein Angreifer im Prinzip ISNs vorhersagen kann und damit durch Kombination mit einer IP-Spoofing-Attacke das unberechtigte Ausführen von Befehlen erlaubt. IP-Spoofing ist einfach zu realisieren, weil lediglich die Quelladresse im Header zu modifzieren ist. Angenommen ein Angreifer X möchte via RSH Befehle auf B ausführen, dann baut er zunächst eine reguläre TCP Verbindung zu B auf (beispielsweise zum SMTP-Dienst, falls verfügbar). Damit gelangt er an eine gülte ISN von B und hat gleichzeitig einen Anhaltspunkt über den aktuellen Zustand des ISN-Generators des TCP-Subsystems. Darauf basierend errät er die nächste von B verwendete ISN, ISN_B_guess. Anschließend versucht er eine RSH-Verbindung über TCP Drei-Wege-Handschlag damit aufzubauen:
(1) X -> B: SYN, ISN_X
(2) B -> A: SYN, ISN_B, ACK(ISN_X)
(3) X -> B: ACK(ISN_B_guess)
Im ersten Schritt sendet X getarnt als A seine ISN (ISN_X) mit gesetztem SYN-Bit an B. B schickt daraufhin eine Nachricht an A (nicht X), X sieht diese Nachricht also nicht. Das hindert X aber nicht daran eine ACK-Nachricht mit dem geratenem Wert ISN_B_guess an B zu schicken. Falls “ISN_B==ISN_B_guess”, ist der Handschlag erfolgreich abgeschlossen und die RSH-TCP-Verbindung ist etabliert. Auch wenn X keine Nachrichten von B empfangen kann, ist es dem Angreifer damit zumindest möglich, beliebige Befehle via RSH an B zu schicken. Eine Schwierigkeit in diesem Szenario besteht für Angreifer darin, dass A beim Empfang von Nachrichten von B feststellen kann, dass etwas faul ist, und daraufhin RST-Pakete an B sendet, um die Verbindung zu schließen. Es gibt aber mehrere Möglichkeiten dieses Problem zu umgehen. Die einfachste besteht darin als Angreifer X einfach zu warten, bis A offline ist und keine störenden RST-Pakete mehr schicken kann. Früher wurde dies öfter mit Denial-of-Service-Attacken gegen A beschleunigt.
Durch das einfache Inkrementieren des ISN-Counter ist es für den Angreifer leicht, die ISN vorherzusagen und erfolgreiche Attacken zu reiten. Als Verteidigungsstrategie schlug Steve Bellovin 1996 im RFC 1948 (Defending Against Sequence Number Attacks) vor, ISNs sicherer zu generieren. Die erste und naheliegenste Idee, gänzlich zufällige ISN zu verwenden, funktionierte nicht, weil Kollisionen verschiedener Verbindungen auftreten können sobald der Pseudo-Zufallszahlen-Generator die gleichen Zahlen ausspuckt. Darum schlug Bellovin vor die ISNs wie folgt zu generieren:
ISN = M + F(lokale IP, lokaler Port, entfernte IP, entfernter Port)
wobei M der alte RFC 793 ISN-Counter ist und F eine für einen Angreifer nicht berechenbare schlüsselbasierte kryptografische Hash-Funktion. Solche Hash-Funktionen sind immer injektiv (nicht bijektiv) und im Falle kryptografischer Hash-Funktion sind sie auch kollisionssicher. Die Idee hinter diesem Verfahren: Jedem Kommunikationskanal wird ein durch IP-Adresse und Port-Nummer aufgespannter ISN-Zählraum zugeordnet. Die Basis für das Zählen in einem dieser Räume ist durch F(lokale IP, lokaler Port, entfernte IP, entfernter Port) gegeben. Wird die Hash-Funktion F mit einem dem Angreifer unbekannten Schlüssel erzeugt, vermeidet das sowohl Kollisionen als auch ein Vorhersagen der ISN. Dieses Verfahren hat in zahlreiche TCP-Implementierungen Einzug gehalten und wurde praktisch zum Standard. Bellovin schlug ursprünglich MD5 als Hash-Funktion vor, da diese im Vergleich zu simpleren Varianten wie MD4 kollisionssicherer ist. Allerdings ist es auch aufwändiger einen MD5-Hash zu berechnen. Da es TCP-ISNs häufig zu erzeugen gilt, verwendete der Linux Kernel aus Performance-Gründen entgegen der Empfehlung MD4 statt MD5. Was damals einen Kompromiss zwischen Sicherheit und Performance darstellte, hat sich lange gehalten. Erst Anfang August 2011 fiel der Entschluss, in Anbetracht der leistungsfähigen modernen Prozessoren, von MD4 auf MD5 umzusteigen um ISNs zu erzeugen. Um die MD5-Funktionalität in den TCP-Teil des Kernels zu implementieren, galt es zahlreiche Source-Dateien umzustrukturieren und einige Dateien wie secure_seq.c hinzuzufügen. In secure_seq.c finden sich nun die MD5-basierten Routinen zum Erzeugen RFC 1948-konformer TCP-ISNs sowohl für TCPv4
_u32 secure_tcp_sequence_number(__be32 saddr, __be32 daddr, __be16 sport, __be16 dport)
{ u32 hash[MD5_DIGEST_WORDS]; hash[0] = (__force u32)saddr; hash[1] = (__force u32)daddr; hash[2] = ((__force u16)sport << 16) + (__force u16)dport; hash[3] = net_secret[15]; md5_transform(hash, net_secret); return seq_scale(hash[0]);
}
als auch für TCPv6:
__u32 secure_tcpv6_sequence_number(__be32 *saddr, __be32 *daddr, __be16 sport, __be16 dport)
{ u32 secret[MD5_MESSAGE_BYTES / 4]; u32 hash[MD5_DIGEST_WORDS]; u32 i; memcpy(hash, saddr, 16); for (i = 0; i < 4; i++) secret[i] = net_secret[i] + daddr[i]; secret[4] = net_secret[4] + (((__force u16)sport << 16) + (__force u16)dport); for (i = 5; i < MD5_MESSAGE_BYTES / 4; i++) secret[i] = net_secret[i]; md5_transform(hash, secret); return seq_scale(hash[0]);
}
Diese Funktionen haben Quell- und Zieladressen als Argumente, da der Hash RFC 1948 konform über diese Angaben berechnet werden muss. Weiter wird ein Schlüssel namens net_secret verwendet, um den Hash zu konstruieren:
static u32 net_secret[MD5_MESSAGE_BYTES / 4] ____cacheline_aligned;
Dieser net_secret Schlüssel zum Erzeugung des MD5-Hashes wird zuvor in net_secret_init() basierend auf Zufallszahlen erzeugt:
static int __init net_secret_init(void)
{ get_random_bytes(net_secret, sizeof(net_secret)); return 0;
}
Diese Änderungen sind seit August sowohl im 2.6er als auch 3.0er Kernel vorzufinden.

