Ist es möglich, einen Kernel-Parameter so zu optimieren, dass ein Userland-Programm an Port 80 und 443 gebunden werden kann?
Der Grund, den ich frage, ist, dass ich es für dumm halte, einem privilegierten Prozess zu erlauben, einen Socket zu öffnen und zuzuhören. Alles, was einen Socket öffnet und lauscht, ist mit einem hohen Risiko verbunden, und Anwendungen mit hohem Risiko sollten nicht als Root ausgeführt werden.
Ich würde viel lieber versuchen herauszufinden, welcher nicht privilegierte Prozess Port 80 überwacht, als zu versuchen, Malware zu entfernen, die mit Root-Rechten eingedrungen ist.
Ich bin mir nicht sicher, worauf sich die anderen Antworten und Kommentare hier beziehen. Dies ist ziemlich einfach möglich. Es gibt zwei Optionen, die den Zugriff auf Ports mit niedriger Nummer ermöglichen, ohne dass der Prozess auf root angehoben werden muss:
Option 1: Verwenden Sie CAP_NET_BIND_SERVICE
, um einem Prozess Portzugriff mit niedriger Nummer zu gewähren:
Mit diesem Befehl können Sie einen permanenten Zugriff auf eine bestimmte Binärdatei gewähren, um über den Befehl setcap
an Ports mit niedriger Nummer zu binden:
Sudo setcap CAP_NET_BIND_SERVICE=+eip /path/to/binary
Weitere Einzelheiten zum e/i/p-Teil finden Sie unter cap_from_text
.
Danach kann /path/to/binary
eine Bindung an Ports mit niedriger Nummer herstellen. Beachten Sie, dass Sie setcap
für die Binärdatei selbst anstelle eines Symlinks verwenden müssen.
Option 2: Verwenden Sie authbind
, um einen einmaligen Zugriff mit genauerer Benutzer-/Gruppen-/Port-Kontrolle zu gewähren:
Genau dafür gibt es das Tool authbind
( man page ).
Installieren Sie authbind
mit Ihrem bevorzugten Paketmanager.
Konfigurieren Sie es so, dass Zugriff auf die relevanten Ports gewährt wird, z. 80 und 443 von allen Benutzern und Gruppen zulassen:
Sudo touch /etc/authbind/byport/80
Sudo touch /etc/authbind/byport/443
Sudo chmod 777 /etc/authbind/byport/80
Sudo chmod 777 /etc/authbind/byport/443
Führen Sie nun Ihren Befehl über authbind
aus (optional mit Angabe von --deep
oder anderen Argumenten, siehe Manpage):
authbind --deep /path/to/binary command line args
Z.B.
authbind --deep Java -jar SomeServer.jar
Beides hat Vor- und Nachteile. Option 1 gewährt dem binär Vertrauen, bietet jedoch keine Kontrolle über den Zugriff pro Port. Option 2 gewährt dem Benutzer/Gruppe Vertrauen und bietet Kontrolle über den Zugriff pro Port, AFAIK unterstützt jedoch nur IPv4.
Dale Hagglund ist genau richtig. Also werde ich nur dasselbe sagen, aber auf andere Weise, mit einigen Einzelheiten und Beispielen. ☺
Das Richtige in der Unix- und Linux-Welt ist:
Sie haben die falsche Vorstellung, wo das hohe Risiko liegt. Das hohe Risiko besteht in aus dem Netzwerk lesen und auf das Gelesene reagieren nicht in der einfachen Handlung, einen Socket zu öffnen, ihn an einen Port zu binden und listen()
aufzurufen. Es ist der Teil eines Dienstes, der die eigentliche Kommunikation durchführt, der das hohe Risiko darstellt. Die zu öffnenden Teile, bind()
und listen()
und sogar (in gewissem Umfang) der Teil, der accepts()
ist, stellen kein hohes Risiko dar und können unter der Ägide des Superbenutzers ausgeführt werden. Sie verwenden keine Daten (mit Ausnahme der Quell-IP-Adressen im Fall accept()
), die von nicht vertrauenswürdigen Fremden über das Netzwerk kontrolliert werden.
Dafür gibt es viele Möglichkeiten.
inetd
name__Wie Dale Hagglund sagt, tut dies der alte "Netzwerk-Superserver" inetd
name__. Das Konto, unter dem der Dienstprozess ausgeführt wird, ist eine der Spalten in inetd.conf
. Es trennt den Listening-Teil und den Drop-Privileg-Teil nicht in zwei separate Programme, die klein und leicht zu überwachen sind, sondern trennt den Hauptdienstcode in ein separates Programm, das in einem Dienstprozess, der mit einem offenen Dateideskriptor erzeugt wird, exec()
für die Steckdose.
Die Schwierigkeit der Prüfung ist nicht so problematisch, da nur das eine Programm geprüft werden muss. Das Hauptproblem von inetd
name __ besteht nicht in der Überwachung, sondern darin, dass es im Vergleich zu neueren Tools keine einfache, fein abgestimmte Laufzeitdienststeuerung bietet.
Daniel J. Bernsteins UCSPI-TCP und daemontools -Pakete wurden entwickelt, um dies in Verbindung zu tun. Alternativ kann man Bruce Guenters weitgehend gleichwertiges Toolset daemontools-encore verwenden.
Das Programm zum Öffnen des Socket-Dateideskriptors und zum Binden an den privilegierten lokalen Port ist tcpserver
NAME_ von UCSPI-TCP. Es führt sowohl die listen()
als auch die accept()
aus.
tcpserver
erzeugt dann entweder ein Dienstprogramm, das die Root-Rechte selbst aufhebt (da das Protokoll als Superuser gestartet und dann "angemeldet" wird, wie dies beispielsweise bei einem FTP- oder SSH-Daemon der Fall ist) oder setuidgid
NAME_ ist ein in sich geschlossenes kleines und leicht zu überprüfendes Programm, das nur Berechtigungen verwirft und dann das eigentliche Dienstprogramm kettet (von dem also kein Teil jemals mit Superuser-Berechtigungen ausgeführt wird, wie dies beispielsweise bei qmail-smtpd
der Fall ist). ).
Ein Dienst run
name__-Skript wäre also zum Beispiel (dieses für dummyidentd zum Bereitstellen eines Null-IDENT-Dienstes):
#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl
Mein Nosh-Paket soll dies tun. Es hat ein kleines Hilfsprogramm setuidgid
name__, genau wie die anderen. Ein kleiner Unterschied besteht darin, dass es mit systemd
name__-artigen "LISTEN_FDS" -Diensten sowie mit UCSPI-TCP-Diensten verwendet werden kann, sodass das traditionelle tcpserver
name__-Programm durch zwei separate Programme ersetzt wird: tcp-socket-listen
und tcp-socket-accept
.
Auch hier spawnen und laden sich Mehrzweck-Dienstprogramme gegenseitig. Eine interessante Besonderheit des Designs ist, dass man Superuser-Berechtigungen nach listen()
aber noch vor accept()
löschen kann. Hier ist ein run
name__-Skript für qmail-smtpd
, das genau das tut:
#!/bin/nosh
fdmove -c 2 1
clearenv --keep-path --keep-locale
envdir env/
softlimit -m 70000000
tcp-socket-listen --combine4and6 --backlog 2 ::0 smtp
setuidgid qmaild
sh -c 'exec \
tcp-socket-accept -v -l "${LOCAL:-0}" -c "${MAXSMTPD:-1}" \
ucspi-socket-rules-check \
qmail-smtpd \
'
Die Programme, die unter der Ägide des Super-Benutzers ausgeführt werden, sind die kleinen service-agnostischen Kettenladetools fdmove
name__, clearenv
name__, envdir
name__, softlimit
name__, tcp-socket-listen
und setuidgid
name__. Ab dem Zeitpunkt, an dem sh
gestartet wird, ist der Socket geöffnet und an den Port smtp
gebunden, und der Prozess verfügt nicht mehr über Superuser-Berechtigungen.
Die s6 und s6-networking - Pakete von Laurent Bercot wurden entwickelt, um dies in Verbindung zu tun. Die Befehle sind strukturell denen von daemontools
und UCSPI-TCP sehr ähnlich.
run
name__-Skripte wären ähnlich, mit Ausnahme der Ersetzung von s6-tcpserver
für tcpserver
und s6-setuidgid
für setuidgid
name__. Man kann sich jedoch auch dafür entscheiden, gleichzeitig das Toolset execline von M. Bercot zu verwenden.
Hier ist ein Beispiel eines FTP-Dienstes, der leicht von Wayne Marshalls Original modifiziert wurde und execline, s6, s6-networking und das FTP-Server-Programm von publicfile verwendet:
#!/command/execlineb -PW
multisubstitute {
define CONLIMIT 41
define FTP_ARCHIVE "/var/public/ftp"
}
fdmove -c 2 1
s6-envuidgid pubftp
s6-softlimit -o25 -d250000
s6-tcpserver -vDRH -l0 -b50 -c ${CONLIMIT} -B '220 Features: a p .' 0 21
ftpd ${FTP_ARCHIVE}
Gerrit Papes ipsvd ist ein weiteres Toolset, das mit ucspi-tcp und s6-networking vergleichbar ist. Die Tools sind chpst
und tcpsvd
name__, aber diesmal tun sie dasselbe, und der Code mit hohem Risiko, der das Lesen, Verarbeiten und Schreiben von Dingen übernimmt, die von nicht vertrauenswürdigen Clients über das Netzwerk gesendet werden, befindet sich immer noch in einem separaten Programm.
Hier ist M. Papes Beispiel für das Ausführen von fnord
NAME_ in einem run
name__-Skript:
#!/bin/sh
exec 2>&1
cd /public/10.0.5.4
exec \
chpst -m300000 -Uwwwuser \
tcpsvd -v 10.0.5.4 443 sslio -v -unobody -//etc/fnord/jail -C./cert.pem \
fnord
systemd
name__systemd
NAME_ , das neue Dienstüberwachungs- und Init-System, das in einigen Linux-Distributionen enthalten ist, soll das tun, was inetd
kann . Es werden jedoch keine kleinen eigenständigen Programme verwendet. Leider muss man systemd
in seiner Gesamtheit prüfen.
Mit systemd
werden Konfigurationsdateien erstellt, um einen Socket zu definieren, den systemd
abhört, und einen Dienst, den systemd
startet. Die "Einheit" -Datei des Dienstes enthält Einstellungen, mit denen Sie den Dienstprozess in hohem Maße steuern können, einschließlich des Benutzers, unter dem er ausgeführt wird.
Wenn dieser Benutzer als Nicht-Superuser festgelegt ist, erledigt systemd
die gesamte Arbeit, indem er den Socket öffnet, ihn an einen Port bindet und listen()
(und gegebenenfalls accept()
) in Prozess 1 als Superuser und Service aufruft Der Prozess, den es erzeugt, wird ohne Superuser-Berechtigungen ausgeführt.
Ich habe einen etwas anderen Ansatz. Ich wollte Port 80 für einen node.js-Server verwenden. Ich konnte es nicht tun, da Node.js für einen Nicht-Sudo-Benutzer installiert wurde. Ich habe versucht, Symlinks zu verwenden, aber es hat bei mir nicht funktioniert.
Dann habe ich erfahren, dass ich Verbindungen von einem Port zu einem anderen Port weiterleiten kann. Also habe ich den Server auf Port 3000 gestartet und eine Portweiterleitung von Port 80 auf Port 3000 eingerichtet.
Dieser Link stellt die tatsächlichen Befehle bereit, die dazu verwendet werden können. Hier sind die Befehle -
localhost/loopback
Sudo iptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 80 -j REDIRECT --to-ports 3000
extern
Sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000
Ich habe den zweiten Befehl verwendet und es hat bei mir funktioniert. Ich denke, dies ist ein Mittelweg, um es dem Benutzer nicht zu erlauben, direkt auf die unteren Ports zuzugreifen, sondern ihnen den Zugriff über die Portweiterleitung zu ermöglichen.
Ihre Instinkte sind völlig richtig: Es ist eine schlechte Idee, ein großes komplexes Programm als Root ausführen zu lassen, da ihre Komplexität es schwierig macht, ihnen zu vertrauen.
Es ist jedoch auch eine schlechte Idee, regulären Benutzern das Binden an privilegierte Ports zu ermöglichen, da solche Ports normalerweise wichtige Systemdienste darstellen.
Der Standardansatz zur Lösung dieses offensichtlichen Widerspruchs ist Privilegientrennung . Die Grundidee besteht darin, Ihr Programm in zwei (oder mehr) Teile zu unterteilen, die jeweils einen genau definierten Teil der Gesamtanwendung darstellen und über einfache, begrenzte Schnittstellen kommunizieren.
In dem von Ihnen angegebenen Beispiel möchten Sie Ihr Programm in zwei Teile unterteilen. Einer, der als root ausgeführt wird und den privilegierten Socket öffnet und bindet und ihn dann irgendwie an den anderen Teil weitergibt, der als regulärer Benutzer ausgeführt wird.
Diese zwei Hauptwege, um diese Trennung zu erreichen.
Ein einzelnes Programm, das als root gestartet wird. Das allererste, was es tut, ist, die notwendige Steckdose so einfach und begrenzt wie möglich zu gestalten. Anschließend werden Berechtigungen gelöscht, das heißt, es wird in einen regulären Benutzermodus-Prozess konvertiert und alle anderen Aufgaben werden ausgeführt. Das korrekte Löschen von Berechtigungen ist schwierig. Nehmen Sie sich also bitte die Zeit, um den richtigen Weg zu finden.
Ein Programmpaar, das über ein Socket-Paar kommuniziert, das von einem übergeordneten Prozess erstellt wurde. Ein nicht privilegiertes Treiberprogramm empfängt anfängliche Argumente und führt möglicherweise eine grundlegende Argumentüberprüfung durch. Es erstellt über socketpair () ein Paar verbundener Sockets und führt dann zwei andere Programme aus, die die eigentliche Arbeit erledigen und über das Socket-Paar kommunizieren. Einer dieser Vorgänge ist privilegiert und erstellt den Server-Socket sowie alle anderen privilegierten Vorgänge. Der andere Vorgang erledigt die komplexere und daher weniger vertrauenswürdige Anwendungsausführung.