Linux Kernel route_localnet Einstellung
Der Linux Kernel hat Einstellungen für Netzwerk Schnittstellen die es
erlauben, das lokale Netz (127.0.0.0/8 für IP Version 4, auch oft
Loopback Adresse bezeichnet weil sie typischerweise der lo
Loopback
Schnittstelle zugewiesen ist) zu routen. Diese Einstellung kann man über
die Datei
/proc/sys/net/ipv4/conf/eth0/route_localnet
für die typische eth0
Netzwerk Schnittstelle erreichen. Die
Einstellung kann man ändern, indem man dort entweder 0
(Standardeinstellung) für kein Routing oder 1 für Routing
hineinschreibt. Einstellungen kann man bei den üblichen Linux
Distributionen persistent machen, indem man einen Eintrag in
/etc/sysctl.conf
vornimmt. Dabei wird das Präfix /proc/sys/
vom
obigen Pfad entfernt und alle /
Zeichen durch einen Punkt ersetzt,
das Resultat für obiges Beispiel wäre dann:
net.ipv4.conf.eth0.route_localnet = 1
Was kann man mit dieser Einstellung tun?
Im Folgenden verwende ich Beispiele für die IP Version 4, das gleiche kann man aber für die IP Version 6 local host Adresse ::1 erreichen.
Im Linux Netzwerk-Stack werden IP Pakete die entweder von einer Adresse
aus dem lokalen Netz 127.0.0.0/8
kommen oder eine Zieladresse dort
haben verworfen, wenn sie über eine Schnittstelle kommen die nicht für diesen
Adressbereich konfiguriert ist. Wir verifizieren das im Folgenden zuerst
mal.
Für einen einfachen UDP Server in Python der UDP Pakete empfängt und deren IP Adresse und Port (und Länge) ausdruckt können wir das folgende (gekürzte) Script verwenden:
import socket [...] sock = socket.socket (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.bind ((args.address, args.port)) while True: buf, adr = sock.recvfrom (1024) l = len (buf) print ('Received %(l)s bytes from %(adr)s' % locals ())
Alle Werte in der Variable args
kommen von der Kommandozeile mit der
Klasse ArgumentParser
. Die Adresse auf die gebunden wird (bind) ist
im folgenden standardmässig 0.0.0.0
was bedeutet dass Pakete von
allen Netzwerk-Schnittstellen empfangen werden.
Wir testen das erstmal mit einem einfachen Client-Programm. Im folgenden haben wir zwei Maschinen, .9 sendet und .5 empfängt. Zum Versenden von UDP Paketen können wir den folgenden einfachen Code verwenden:
import socket sock = socket.socket (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.sendto (b'This is a test', (args.address, args.port))
Auch hier werden die Parameter auf der Kommandozeile angegeben. Wenn wir von .9 an .5 senden bekommen wir vom obigen Server
Received 14 bytes from ('XXXXXXX.9', 38232)
wobei der der Quell-Port 38232 eine zufällige vom Linux Kernel erzeugte
Portnummer ist und XXXXXXX
die oberen Bytes der IP-Adresse
des Senders repräsentiert. Wenn wir auf der empfangenden Maschine an
127.0.0.1
senden bekommen wir:
Received 14 bytes from ('127.0.0.1', 58196)
Wir sehen dass der Server sowohl auf der Ethernet Schnittstelle als auch auf der Loopback Schnittstelle empfängt.
Soweit nichts neues. Schauen wir mal was passiert wenn wir Adressen fälschen. Um beliebige IP Pakete (oder sogar Nicht-IP Pakete) zu erzeugen verwende ich im Folgenden das Python Paket Scapy.
Ein Ausschnitt aus dem Script:
p = Ether (dst = args.mac_address, src = args.mac_address) \ / IP (dst = args.address, src = args.source_address) \ / UDP (dport = args.port, sport = args.source_port) \ / Raw (b'This is a test') sendp (p, iface = args.interface)
Das baut ein Ethernet Paket (Ether)
mit IP
Inhalt. Das IP Paket
wiederum enthält ein UDP
Paket was wiederum einen Raw
Zeichenfolge enthält. Das Paket wird über einen Netzwerk Layer 2 Socket
versendet. Wiederum kommen die Parameter von der Kommandozeile.
Standard für alle Ports ist 22222
, die Quell-IP ist XXXXXXX.9
und die Ziel-IP ist XXXXXXX.5
. Ziel- und Quell MAC Adresse werden
beide auf die MAC-Adresse der Zielmaschine gesetzt. Wir wiederholen
was wir mit dem einfachen Client ausprobiert haben:
sudo scapyclient.py
Was in der folgenden Ausgabe resultiert:
Received 14 bytes from ('XXXXXXX.9', 22222)
Hier wird standardmässig immer Port 22222 verwendet es sei denn wir
setzen den Port über eine Kommandozeilen-Option des Scripts. Wir müssen
sudo
verwenden weil nur der Superuser beliebige Pakete auf Layer-2
des Netzwerk-Stacks senden darf.
Was passiert nun wenn wir Pakete fälschen? Damit wir sehen was gesendet
(und empfangen) wird starten wir tcpdump
wie folgt:
sudo tcpdump -n -i eth0 udp port 22222
Natürlich muss das Argument der -i
Option geändert werden wenn die
Netzwerk-Schnittstelle anders heisst als eth0
. Die Option -n
sagt
tcpdump
dass keine DNS-Auflösung von IP Adressen erfolgen soll.
Wir senden ein Paket mit einer localhost Adresse als Quell-Adresse:
sudo scapyclient.py --source-address=127.0.0.1
Auf beiden Maschinen sehen wir in der tcpdump
Ausgabe
TT:TT:TT.TTTTTT IP 127.0.0.1.22222 > 10.23.5.5.22222: UDP, length 14
wobei TT:TT:TT.TTTTTT
ein Zeitstempel ist. Das Paket geht also auf
der Ethernet-Schittstelle der sendenden Maschine raus und wird von der
empfangenden Maschine empfangen (weil die MAC-Adresse richtig ist) aber
von unserem Server wird nichts ausgegeben.
Das gleich passiert wenn wir die Zieladresse ändern:
sudo scapyclient.py --address=127.0.0.1
Die tcpdump
Ausgabe ist jetzt:
TT:TT:TT.TTTTTT IP 10.23.5.9.22222 > 127.0.0.1.22222: UDP, length 14
Wieder wird das von beiden Maschinen von tcpdump
ausgegeben aber
nichts wird von unserem Server ausgegeben.
Schauen wir nun was passiert wenn wir route_localnet
für unsere
Ethernet Schnittstelle einschalten (das folgende funktioniert natürlich
nur als root
):
echo 1 > /proc/sys/net/ipv4/conf/eth0/route_localnet
Wir fälschen wieder die Quelladresse:
sudo python3.11 scapyclient.py --source-address=127.0.0.1
Wieder wird das Paket von beiden laufenden tcpdump
Prozessen
ausgegeben aber wieder nicht von unserem Server. Aber wenn wir die
Zieladresse fälschen:
sudo python3.11 scapyclient.py --address=127.0.0.1
Bekommen wir zusätzlich zur tcpdump
Ausgabe auch eine Antwort von
unserem Server:
Received 14 bytes from ('10.23.5.9', 22222)
Und bemerkenswert ist, dass das auch funktioniert wenn wir unserem Server sagen dass er nur auf der localhost Adresse hören soll:
python3 server.py --address=127.0.0.1
Der Server empfängt trotzdem das gefälschte Paket obwohl es nicht über die Loopback Schnittstelle empfangen wurde:
Received 14 bytes from ('10.23.5.9', 22222)
Sicherheit
Manche Anwendungen verwenden lokale Server die nur auf die Loopback Schnittstelle hören. Es wird erwartet dass diese nur Pakete über die Loopback Schnittstelle empfangen – gesendet von Prozessen die auf der selben Maschine laufen wie der Server.
Dies ist immer eine gefährliche Annahme, es sollten nie gefährliche
Dinge getan werden die sich auf diese (Nicht-) Authentifizierung
verlassen. Zumindest unter Linux gibt es eine gewisse Sicherheit solange
die Einstellung route_localnet
auf 0 gesetzt ist. Auf anderen
Betriebssystemen ist das Glück vielleicht nicht so hold. Und auch unter
Linux kann dieses Verhalten ausgeschaltet werden.
Warum wollen wir diese Einstellung vornehmen?
Manchmal sollen Verbindungen die auf der lokalen Maschine zu einem Port
auf der lokalen Maschine aufgebaut werden zu einer anderen Maschine
verbunden werden, wenn z.B. /etc/hosts
folgenden Eintrag hat
127.0.0.1 some.remote.example.com
und eine Anwendung zu dieser Maschine verbinden soll können wir Firewall Regeln konfigurieren die die Pakete so umschreiben dass das funktioniert:
iptables -t nat -A OUTPUT -d 127.0.0.1 -p tcp --dport 443 \ -j DNAT --to-destination XXXXXXX.5:1443 iptables -t nat -A POSTROUTING -d XXXXXXX.5 -p tcp --dport 1443 \ -j SNAT --to-source XXXXXXX.9
Die erste Regel schreibt die Zieladresse um (und den Zielport) während
die zweite Regel sicherstellt dass Antwortpakete ankommen indem die
Quelladresse umgeschrieben wird. Aber ohne die Einstellung
route_localnet
für die ausgehende Ethernet-Schnittstelle wird das
nicht funktionieren, die Pakete werden verworfen.
Aber Vorsicht: Weil diese Einstellung auch auf die empfangenen Pakete einen Einfluss hat ist zu beachten was im Abschnitt Sicherheit beschrieben wurde.