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


104

Ist es möglich, einen Kernel-Parameter zu optimieren, damit ein Userland-Programm an die Ports 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 versuche lieber herauszufinden, welcher nicht privilegierte Prozess Port 80 überwacht, als Malware zu entfernen, die mit Root-Rechten versehen ist.



10
Die lange Antwort lautet "Ja". Die kurze Antwort lautet also "Ja".
BT

4
Die kurze Antwort lautet ja.
Jason C

Antworten:


163

Ich bin 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 diese Option, CAP_NET_BIND_SERVICEum einem Prozess Portzugriff mit niedriger Nummer zu gewähren:

Mit diesem setcapBefehl können Sie dauerhaften Zugriff auf eine bestimmte Binärdatei gewähren, um sie 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.

Anschließend /path/to/binarykönnen Sie Verbindungen zu Ports mit niedriger Nummer herstellen. Beachten Sie, dass Sie setcapdie Binärdatei selbst anstelle eines Symlinks verwenden müssen.

Option 2: Verwenden Sie diese Option authbind, um einen einmaligen Zugriff mit genauerer Benutzer- / Gruppen- / Portkontrolle zu gewähren:

Genau dafür gibt es das authbind( Manpage- ) Tool.

  1. Installieren Sie authbindmit Ihrem bevorzugten Paket-Manager.

  2. Konfigurieren Sie es, um Zugriff auf die relevanten Ports zu gewähren, z. B. um 80 und 443 von allen Benutzern und Gruppen zuzulassen:

    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 aus authbind(optional mit Angabe --deepoder 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 der Binärdatei Vertrauen, bietet jedoch keine Kontrolle über den Zugriff pro Port. Option 2 gewährt dem Benutzer / der Gruppe Vertrauen und bietet Kontrolle über den Zugriff pro Port, aber AFAIK unterstützt nur IPv4.


Braucht es wirklich eine rwxErlaubnis?
Matt

-pWürden Sie den Befehl zum Zurücksetzen des Vorgangs in Option 1 erneut mit intead of ausführen +eip?
Eugene1832

5
Beachten Sie, dass mit setcap beim Überschreiben der ausführbaren Datei, für die Sie Berechtigungen erteilen (z. B. Neuerstellen), der Status des privilegierten Ports verloren geht und Sie ihm erneut Berechtigungen erteilen müssen: |
Rogerdpack

1
Etwas, mit dem ich herumspielen musste; Ich habe versucht, einen Sysv-Dienst auszuführen, der eine Ruby-Programmdatei ausführt, die Ruby verwendet. Sie müssen die setcapErlaubnis für die versionsspezifische ausführbare Ruby-Datei geben , z. B./usr/bin/ruby1.9.1
Christian Rondeau,

3
Ich habe meine Zweifel, dass es die beste Idee ist, die Dateien chmodzu byportspeichern. Ich habe gesehen, wie Berechtigungen von 500bis erteilt wurden 744. Ich würde mich an die restriktivste halten, die für Sie funktioniert.
Pere

28

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 überprüfbares 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 ausgeführt werden kann, muss er unter einem Nicht-Superuser-Konto ausgeführt und vom zweiten Programm geladen werden, wobei erwartet wird, dass ein offener Dateideskriptor für den Socket geerbt wird.

Sie haben die falsche Vorstellung, wo das hohe Risiko liegt. Das hohe Risiko besteht darin , aus dem Netzwerk zu lesen und auf das zu reagieren, was gelesen wird, und nicht darin, einen Socket zu öffnen, ihn an einen Port zu binden und anzurufen listen(). Es ist der Teil eines Dienstes, der die eigentliche Kommunikation durchführt, der das hohe Risiko darstellt. Die Teile, die sich öffnen, bind()und listen()(bis zu einem gewissen Grad) der Teil , der sich öffnet, accepts()sind nicht das hohe Risiko und können unter der Ägide des Super-Benutzers ausgeführt werden. Sie verwenden und verarbeiten keine accept()Daten (mit Ausnahme der Quell-IP-Adressen in diesem Fall), die der Kontrolle nicht vertrauenswürdiger Fremder über das Netzwerk unterliegen.

Dafür gibt es viele Möglichkeiten.

inetd

Wie Dale Hagglund sagt, macht das der alte "Netzwerk-Superserver" inetd. Das Konto, unter dem der Serviceprozess 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 exec()in einem Dienstprozess erstellt wird, der mit einem offenen Dateideskriptor erstellt wird für die Steckdose.

Die Schwierigkeit der Prüfung ist nicht so problematisch, da nur das eine Programm geprüft werden muss. inetdDas Hauptproblem ist nicht die Überwachung, sondern, 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 dafür zusammen entwickelt. Alternativ kann Bruce Guenters weitgehend gleichwertiges daemontools-encore- Toolset verwendet werden.

Das Programm zum Öffnen des Socket-Dateideskriptors und zum Binden an den privilegierten lokalen Port wird tcpserverüber UCSPI-TCP ausgeführt. Es macht sowohl die listen()als auch die accept().

tcpserverAnschließend wird entweder ein Dienstprogramm erstellt, das die Root-Berechtigungen selbst löscht (da das bereitgestellte Protokoll darin besteht, als Superuser zu beginnen und sich dann "anzumelden", wie dies beispielsweise bei einem FTP- oder einem SSH-Dämon der Fall ist) oder setuidgida in sich geschlossenes kleines und leicht zu überprüfendes Programm, das nur Berechtigungen verwirft und dann das eigentliche Dienstprogramm lädt (von dem also kein Teil jemals mit Superuser-Berechtigungen ausgeführt wird, wie dies beispielsweise der Fall ist qmail-smtpd).

Ein Service- runSkript wäre also zum Beispiel (dieses für dummyidentd zur Bereitstellung eines Null-IDENT-Dienstes):

#!/bin/sh -e
exec 2>&1
exec \
tcpserver 0 113 \
setuidgid nobody \
dummyidentd.pl

Leckereien

Mein nosh-Paket wurde dafür entwickelt. Es hat einen kleinen setuidgidNutzen, genau wie die anderen. Ein systemdkleiner Unterschied besteht darin, dass es sowohl mit "LISTEN_FDS" -Diensten als auch mit UCSPI-TCP-Diensten verwendet werden kann. Daher wird das herkömmliche tcpserverProgramm durch zwei separate Programme ersetzt: tcp-socket-listenund tcp-socket-accept.

Auch hier spawnen und laden sich die Mehrzweck-Dienstprogramme gegenseitig. Eine interessante Besonderheit des Designs ist, dass man Superuser-Privilegien nach, listen()aber schon vor, fallen lassen kann accept(). Hier ist ein runSkript 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 Schirmherrschaft der Superuser sind die kleinen Service-agnostisch Kettenlade Tools fdmove, clearenv, envdir, softlimit, tcp-socket-listen, und setuidgid. Ab dem Startpunkt shist der Socket geöffnet und an den smtpPort gebunden , und der Prozess verfügt nicht mehr über Superuser-Berechtigungen.

s6, s6-networking und execline

Die s6- und s6-Netzwerkpakete von Laurent Bercot wurden dazu in Verbindung entwickelt. Die Befehle sind strukturell denen von daemontoolsund UCSPI-TCP sehr ähnlich .

runSkripte wären ähnlich, mit Ausnahme der Substitution von s6-tcpserverfor tcpserverund s6-setuidgidfor setuidgid. Man kann sich jedoch auch dafür entscheiden, gleichzeitig das Execline- Toolset von M. Bercot zu verwenden .

Hier ist ein Beispiel eines FTP-Dienstes, der geringfügig von Wayne Marshalls Original geändert 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 tcpsvddieses Mal, aber sie tun 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 das Beispiel von M. Pape, wie man fnordein runSkript ausführt:

#!/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

systemdDas neue Dienstüberwachungs- und Init-System, das in einigen Linux-Distributionen zu finden ist, soll das tun, was es inetdkann . Es werden jedoch keine kleinen eigenständigen Programme verwendet. Man muss systemdleider in vollem Umfang prüfen .

Mit systemderstellt man Konfigurationsdateien, um einen Socket zu definieren, der systemdlauscht, und einen Dienst, der systemdstartet. Die Service "Unit" -Datei enthält Einstellungen, mit denen Sie den Serviceprozess 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, systemderledigt er die gesamte Arbeit, indem er den Socket öffnet, an einen Port bindet listen()und accept()in Prozess 1 den Superuser und den zugehörigen Dienstprozess aufruft (und, falls erforderlich, aufruft ) Spawns werden ohne Superuser-Rechte ausgeführt.


2
Danke für das Kompliment. Dies ist eine großartige Sammlung konkreter Ratschläge. +1.
Dale Hagglund

11

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 enthält die tatsächlichen Befehle, 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 Benutzerprozess nicht zu erlauben, direkt auf die unteren Ports zuzugreifen, sondern ihnen den Zugriff über die Portweiterleitung zu ermöglichen.


6
+1 für unkonventionelles Denken
Richard Wiseman

4

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 die Privilegientrennung . Die Grundidee besteht darin, Ihr Programm in zwei (oder mehr) Teile zu unterteilen, von denen jeder einen genau definierten Teil der Gesamtanwendung darstellt und die ü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 daher 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 Argumentvalidierung 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


Was Sie vorschlagen, wird nicht als Best Practice angesehen. Sie könnten sich inetd ansehen, das einen privilegierten Socket abhören und diesen Socket dann an ein nichtprivilegiertes Programm übergeben kann.
Dale Hagglund

3

Einfachste Lösung: Entfernen Sie alle privilegierten Ports unter Linux

Funktioniert auf Ubuntu / Debian:

#save configuration permanently
echo 'net.ipv4.ip_unprivileged_port_start=0' > /etc/sysctl.d/50-unprivileged-ports.conf
#apply conf
sysctl --system

(Funktioniert gut für VirtualBox mit Nicht-Root-Account)

Achten Sie jetzt auf die Sicherheit, da alle Benutzer alle Ports binden können!


Das ist schlau. Eine kleine Kleinigkeit: Die Konfiguration öffnet 80 und 443, aber es öffnet auch alle anderen Ports. Entspannende Berechtigungen für die anderen Ports sind möglicherweise nicht erwünscht.
13.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.