2014-02-02 05:48:05 +0000 2014-02-02 05:48:05 +0000
117
117

Erlauben Sie Nicht-Root-Prozessen, sich an Port 80 und 443 zu binden?

Ist es möglich, einen Kernel-Parameter einzustellen, um einem Userland-Programm zu erlauben, sich an Port 80 und 443 zu binden?

Der Grund, warum ich frage, ist, dass ich es für dumm halte, einem privilegierten Prozess zu erlauben, einen Socket zu öffnen und zu lauschen. Alles, was einen Socket öffnet und abhört, ist ein hohes Risiko, und Anwendungen mit hohem Risiko sollten nicht als root laufen.

Ich würde viel lieber versuchen, herauszufinden, welcher unprivilegierte Prozess an Port 80 lauscht, als zu versuchen, Malware zu entfernen, die sich mit Root-Rechten eingegraben hat.

Antworten (5)

176
176
176
2015-03-21 21:12:41 +0000

Ich bin mir nicht sicher, worauf sich die anderen Antworten und Kommentare hier beziehen. Dies ist recht einfach möglich. Es gibt zwei Möglichkeiten, die beide den Zugriff auf niedrig nummerierte Ports erlauben, ohne dass der Prozess zu root erhoben werden muss:

Option 1: Verwenden Sie CAP_NET_BIND_SERVICE , um einem Prozess Zugriff auf niedrig nummerierte Ports zu gewähren:

Damit können Sie einem bestimmten Binary permanenten Zugriff gewähren, um sich über den Befehl setcap an niedrig nummerierte Ports zu binden:

sudo setcap CAP_NET_BIND_SERVICE=+eip /path/to/binary

Für weitere Details zum e/i/p-Teil siehe cap_from_text .

Nachdem Sie dies getan haben, wird /path/to/binary in der Lage sein, sich an niedrig nummerierte Ports zu binden. Beachten Sie, dass Sie setcap auf der Binärdatei selbst verwenden müssen und nicht über einen Symlink.

Option 2: Verwenden Sie authbind, um einmaligen Zugriff zu gewähren, mit feinerer Benutzer/Gruppen/Port-Kontrolle:

Genau dafür gibt es das Werkzeug authbind man page ).

  1. Installieren Sie authbind mit Ihrem bevorzugten Paketmanager.

  2. Konfigurieren Sie es so, dass es den Zugriff auf die relevanten Ports erlaubt, z. B. 80 und 443 von allen Benutzern und Gruppen:

  3. Führen Sie nun Ihren Befehl über authbind aus (optional mit Angabe von --deep oder anderen Argumenten, siehe Manpage):


Es gibt Vor- und Nachteile für beide oben genannten Möglichkeiten. Option 1 gewährt dem binary Vertrauen, bietet aber keine Kontrolle über den Zugriff auf einzelne Ports. Option 2 gewährt dem Benutzer/Gruppe Vertrauen und bietet Kontrolle über den Zugriff pro Port, aber ältere Versionen unterstützten nur IPv4 (seit ich dies ursprünglich geschrieben habe, wurden neuere Versionen mit IPv6-Unterstützung veröffentlicht).

29
29
29
2014-02-02 16:21:39 +0000

Dale Hagglund hat es genau getroffen. Also werde ich einfach das Gleiche sagen, aber auf eine andere Art und Weise, mit einigen Besonderheiten und Beispielen. ☺

Das Richtige in der Unix- und Linux-Welt ist:

  • ein kleines, einfaches, leicht überprüfbares Programm zu haben, das als Superuser läuft und den lauschenden Socket bindet;
  • ein weiteres kleines, einfaches, leicht überprüfbares Programm zu haben, das Privilegien abgibt und vom ersten Programm gestartet wird;
  • den Kern des Dienstes in einem separaten dritten Programm zu haben, das unter einem Nicht-Superuser-Account läuft und vom zweiten Programm in Kettenschaltung geladen wird, in der Erwartung, einfach einen offenen Dateideskriptor für den Socket zu erben.

Sie haben eine falsche Vorstellung davon, wo das hohe Risiko liegt. Das hohe Risiko liegt im Lesen aus dem Netzwerk und im Handeln auf das Gelesene, nicht im einfachen Öffnen eines Sockets, dem Binden an einen Port und dem Aufruf von listen(). Es ist der Teil eines Dienstes, der die eigentliche Kommunikation durchführt, der das hohe Risiko darstellt. Die Teile, die öffnen, bind(), und sogar (bis zu einem gewissen Grad) der Teil, der listen() aufruft, sind nicht das hohe Risiko und können unter der Ägide des Superusers ausgeführt werden. Sie verwenden und handeln nicht mit (mit Ausnahme der Quell-IP-Adressen im Fall von accepts()) Daten, die unter der Kontrolle von nicht vertrauenswürdigen Fremden über das Netzwerk stehen.

Es gibt viele Möglichkeiten, dies zu tun.

accept()

Wie Dale Hagglund sagt, tut dies der alte “Netzwerk-Superserver” inetd. Das Konto, unter dem der Dienstprozess ausgeführt wird, ist eine der Spalten in inetd. Es trennt nicht den Teil des Abhörens und den Teil des Ablegens von Privilegien in zwei separate Programme, die klein und leicht überprüfbar sind, aber es trennt den Hauptdienstcode in ein separates Programm, inetd.confed in einem Dienstprozess, den es mit einem offenen Dateideskriptor für den Socket erzeugt.

Die Schwierigkeit des Auditierens ist nicht so sehr ein Problem, da man nur das eine Programm auditieren muss. Das Hauptproblem von exec() ist nicht so sehr das Auditing, sondern eher, dass es im Vergleich zu neueren Tools keine einfache, feinkörnige Kontrolle der Laufzeitdienste bietet.

UCSPI-TCP und daemontools

Die Pakete UCSPI-TCP und daemontools von Daniel J. Bernstein wurden entwickelt, um dies in Verbindung zu tun. Alternativ kann man auch Bruce Guenters weitgehend äquivalentes daemontoolss-encore Toolset verwenden.

Das Programm zum Öffnen des Socket-Dateideskriptors und zum Binden an den privilegierten lokalen Port ist inetd , von UCSPI-TCP. Es erledigt sowohl das tcpserver als auch das listen().

accept() erzeugt dann entweder ein Dienstprogramm, das selbst Root-Rechte abgibt (weil das Protokoll, das bedient wird, beinhaltet, als Superuser zu starten und sich dann “anzumelden”, wie es z. B. der Fall ist bei, einem FTP- oder SSH-Daemon) oder tcpserver , das ein eigenständiges kleines und leicht zu überprüfendes Programm ist, das nur die Rechte abgibt und dann zum eigentlichen Dienstprogramm weiterleitet (von dem also kein Teil mit Superuser-Rechten läuft, wie es z. B. bei setuidgid der Fall ist).

Ein Dienst qmail-smtpd-Skript wäre also zum Beispiel (dieses für dummyidentd zur Bereitstellung des Null-IDENT-Dienstes):

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

nosh

Mein nosh-Paket ist dafür gedacht. Es hat ein kleines run Dienstprogramm, genau wie die anderen. Ein kleiner Unterschied ist, dass es sowohl mit setuidgid-artigen “LISTEN_FDS”-Diensten als auch mit UCSPI-TCP-Diensten verwendet werden kann, so dass das traditionelle systemd-Programm durch zwei separate Programme ersetzt wird: tcpserver und tcp-socket-listen.

Wiederum spawnen Einzweck-Dienstprogramme und laden sich gegenseitig in Ketten. Eine interessante Eigenheit des Designs ist, dass man die Superuser-Rechte nach tcp-socket-accept, aber noch vor listen() abgeben kann. Hier ist ein Skript für accept(), das tatsächlich 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 Superusers laufen, sind die kleinen, dienstagnostischen Kettenladeprogramme run, qmail-smtpd, fdmove, clearenv, envdir und softlimit. Zu dem Zeitpunkt, zu dem tcp-socket-listen gestartet wird, ist der Socket geöffnet und an den Port setuidgid gebunden, und der Prozess hat keine Superuser-Rechte mehr.

s6, s6-networking und execline

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

smtp-Skripte wären weitgehend identisch, bis auf die Ersetzung von daemontools für run und s6-tcpserver für tcpserver. Man könnte aber auch gleichzeitig das Toolset execline von M. Bercot verwenden.

Hier ist ein Beispiel für einen FTP-Dienst, leicht modifiziert von Wayne Marshalls Original , der execline, s6, s6-networking und das FTP-Server-Programm aus 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 Pape’s ipsvd ist ein weiteres Toolset, das nach dem gleichen Prinzip wie ucspi-tcp und s6-networking funktioniert. Die Werkzeuge heißen dieses Mal s6-setuidgid und setuidgid, aber sie tun dasselbe, und der risikoreiche Code, der das Lesen, Verarbeiten und Schreiben von Dingen erledigt, die über das Netzwerk von nicht vertrauenswürdigen Clients ist immer noch in einem separaten Programm.

Hier ist das Beispiel von M. Pape für die Ausführung von chpst in einem tcpsvd-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

fnord

run , das neue Dienstüberwachungs- und Init-System, das in einigen Linux-Distributionen zu finden ist, ist dazu gedacht, das zu tun, was systemd tun kann. Allerdings verwendet es keine Suite von kleinen, in sich geschlossenen Programmen. Man muss systemd leider in seiner Gesamtheit prüfen.

Mit inetd erstellt man Konfigurationsdateien, um einen Socket zu definieren, an dem systemd lauscht, und einen Dienst, den systemd startet. Die “unit”-Datei des Dienstes enthält Einstellungen, die eine weitgehende Kontrolle über den Dienstprozess ermöglichen, einschließlich des Benutzers, unter dem er läuft.

Wenn dieser Benutzer auf einen Nicht-Superuser eingestellt ist, erledigt systemd die gesamte Arbeit des Öffnens des Sockets, des Bindens an einen Port und des Aufrufs von systemd (und, falls erforderlich, von systemd) im Prozess Nr. 1 als Superuser, und der von ihm gestartete Dienstprozess läuft ohne Superuser-Rechte.

17
17
17
2018-06-27 07:00:56 +0000

Ich habe einen etwas anderen Ansatz. Ich wollte Port 80 für einen node.js-Server verwenden. Das konnte ich nicht, da Node.js für einen Nicht-Sudo-Benutzer installiert wurde. Ich habe versucht, Symlinks zu verwenden, aber das hat bei mir nicht funktioniert.

Dann habe ich erfahren, dass ich Verbindungen von einem Port auf einen anderen Port weiterleiten kann. Also startete ich den Server auf Port 3000 und richtete eine Portweiterleitung von Port 80 auf Port 3000 ein. Dieser Link stellt die eigentlichen Befehle zur Verfügung, mit denen man dies tun kann. 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

external

sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000

Ich habe den zweiten Befehl verwendet und er hat bei mir funktioniert. Ich denke also, dass dies ein Mittelweg ist, um den Benutzerprozess nicht direkt auf die unteren Ports zugreifen zu lassen, sondern ihnen den Zugriff über Port-Weiterleitung zu ermöglichen.

4
4
4
2014-02-02 06:49:22 +0000

Ihr Instinkt ist völlig richtig: Es ist eine schlechte Idee, ein großes, komplexes Programm als Root laufen zu lassen, weil ihre Komplexität es schwer macht, ihnen zu vertrauen.

Aber es ist auch eine schlechte Idee, normalen Benutzern zu erlauben, sich an privilegierte Ports zu binden, weil solche Ports normalerweise wichtige Systemdienste darstellen.

Der Standardansatz zur Lösung dieses scheinbaren Widerspruchs ist die Privilegientrennung. Die Grundidee besteht darin, Ihr Programm in zwei (oder mehr) Teile aufzuteilen, von denen jeder einen genau definierten Teil der Gesamtanwendung ausführt und die über einfache, begrenzte Schnittstellen kommunizieren.

In dem Beispiel, das Sie geben, wollen Sie Ihr Programm in zwei Teile aufteilen. Eines, das als root läuft und den privilegierten Socket öffnet und bindet, und es dann irgendwie an den anderen Teil übergibt, der als normaler Benutzer läuft.

Dies sind die zwei wichtigsten Möglichkeiten, diese Trennung zu erreichen.

  1. Ein einzelnes Programm, das als root startet. Das allererste, was es tut, ist das Anlegen des notwendigen Sockets, und zwar so einfach und begrenzt wie möglich. Dann gibt es die Privilegien ab, d. h., es verwandelt sich in einen regulären Prozess im Benutzermodus und erledigt alle anderen Arbeiten. Das korrekte Ablegen von Privilegien ist knifflig, also nehmen Sie sich bitte die Zeit, den richtigen Weg zu studieren, um es zu tun.

  2. Ein Programmpaar, das über ein Socket-Paar kommuniziert, das von einem Elternprozess erstellt wurde. Ein nicht-privilegiertes Treiberprogramm empfängt anfängliche Argumente und führt vielleicht eine grundlegende Argumentüberprüfung durch. Es erstellt ein Paar verbundener Sockets über socketpair() und führt dann zwei andere Programme aus, die die eigentliche Arbeit erledigen und über das Socket-Paar kommunizieren. Eines davon ist privilegiert und erstellt den Server-Socket sowie alle anderen privilegierten Operationen, während das andere die komplexere und daher weniger vertrauenswürdige Ausführung der Anwendung übernimmt.

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

3
3
3
2019-09-13 07:38:46 +0000

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

Funktioniert unter 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)

Seien Sie jetzt vorsichtig mit der Sicherheit, da alle Benutzer alle Ports binden können!