it-swarm.com.de

Nicht-Root-Prozess an Port 80 und 443 binden lassen?

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.

89
jww

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 ).

  1. Installieren Sie authbind mit Ihrem bevorzugten Paketmanager.

  2. 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
    
  3. 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.

138
Jason C

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:

  • ein kleines, einfaches, leicht zu überprüfendes Programm zu haben, das als Superuser ausgeführt wird und den Listening-Socket bindet;
  • ein weiteres kleines, einfaches, leicht zu überprüfendes Programm zu haben, das Privilegien fallen lässt, die vom ersten Programm erzeugt wurden;
  • damit der Service in einem separaten dritten Programm verfügbar ist, muss er unter einem Nicht-Superuser-Konto und einer Kette ausgeführt werden, die vom zweiten Programm geladen werden, und es muss lediglich ein offener Dateideskriptor für den Socket geerbt werden.

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.

inetdname__

Wie Dale Hagglund sagt, tut dies der alte "Netzwerk-Superserver" inetdname__. 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 inetdname __ besteht nicht in der Überwachung, sondern darin, dass es im Vergleich zu neueren Tools keine einfache, fein abgestimmte Laufzeitdienststeuerung bietet.

UCSPI-TCP und Daemontools

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 tcpserverNAME_ von UCSPI-TCP. Es führt sowohl die listen() als auch die accept() aus.

tcpservererzeugt 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 setuidgidNAME_ 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 runname__-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

nein

Mein Nosh-Paket soll dies tun. Es hat ein kleines Hilfsprogramm setuidgidname__, genau wie die anderen. Ein kleiner Unterschied besteht darin, dass es mit systemdname__-artigen "LISTEN_FDS" -Diensten sowie mit UCSPI-TCP-Diensten verwendet werden kann, sodass das traditionelle tcpservername__-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 runname__-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 fdmovename__, clearenvname__, envdirname__, softlimitname__, tcp-socket-listen und setuidgidname__. Ab dem Zeitpunkt, an dem shgestartet wird, ist der Socket geöffnet und an den Port smtpgebunden, und der Prozess verfügt nicht mehr über Superuser-Berechtigungen.

s6, s6-networking und execline

Die s6 und s6-networking - Pakete von Laurent Bercot wurden entwickelt, um dies in Verbindung zu tun. Die Befehle sind strukturell denen von daemontoolsund UCSPI-TCP sehr ähnlich.

runname__-Skripte wären ähnlich, mit Ausnahme der Ersetzung von s6-tcpserver für tcpserverund s6-setuidgid für setuidgidname__. 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}

ipsvd

Gerrit Papes ipsvd ist ein weiteres Toolset, das mit ucspi-tcp und s6-networking vergleichbar ist. Die Tools sind chpstund tcpsvdname__, 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 fnordNAME_ in einem runname__-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

systemdname__

systemdNAME_ , das neue Dienstüberwachungs- und Init-System, das in einigen Linux-Distributionen enthalten ist, soll das tun, was inetdkann . Es werden jedoch keine kleinen eigenständigen Programme verwendet. Leider muss man systemdin seiner Gesamtheit prüfen.

Mit systemdwerden Konfigurationsdateien erstellt, um einen Socket zu definieren, den systemdabhört, und einen Dienst, den systemdstartet. 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 systemddie 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.

25
JdeBP

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.

7
noob

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.

  1. 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.

  2. 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.

[1] http://en.m.wikipedia.org/wiki/Privilege_separation

4
Dale Hagglund