Warum sollte ich aus Sicherheitsgründen für Sandboxing chrooten, wenn meine Anwendung von Anfang an auf einer niedrigeren Ebene ausgeführt werden kann?

14

Ich schreibe einen HTTP-Server-Daemon in C (es gibt Gründe dafür) und verwalte ihn mit systemd unit file.

Ich schreibe eine Anwendung um, die vor 20 Jahren, um 1995, entworfen wurde. Und das System, das sie verwenden, ist, dass sie chroot und dann setuid und das Standardverfahren.

In meiner vorherigen Arbeit war die übliche Richtlinie, dass Sie niemals einen Prozess als root ausführen. Sie erstellen dafür einen Benutzer / eine Gruppe und führen sie von dort aus. Natürlich hat das System einige Dinge als root ausgeführt, aber wir konnten die gesamte Geschäftslogikverarbeitung erreichen, ohne root zu sein.

Jetzt kann ich den HTTP-Daemon ohne root ausführen, wenn ich nicht in der Anwendung chroot bin. Ist es nicht sicherer, dass die Anwendung niemals als Root ausgeführt wird?

Ist es nicht sicherer, es von Anfang an als mydaemon-Benutzer auszuführen? Anstatt es mit root, chrooting und setuid zu mydaemon-user zu starten?

mur
quelle
3
Sie müssen als root ausgeführt werden, um Port 80 oder 443 zu verwenden. Andernfalls können Sie das tun, was Tomcat und andere Webapp- / Webserver-Software tun, und auf einem höheren Port (z. B. 8080, 9090 usw.) ausgeführt werden. Verwenden Sie dann entweder apache / nginx, um die Verbindung zu Ihrer Webserver-Software zu proxen, oder verwenden Sie die Firewall des Systems, um den Datenverkehr von Port 80 an Ihren Webserver weiterzuleiten. Wenn Sie Port 80 oder 443 nicht benötigen oder die Verbindung proxen oder weiterleiten können, Dann müssen Sie nicht als root, in einer Chroot oder auf andere Weise ausgeführt werden.
SnakeDoc
3
@SnakeDoc unter Linux stimmt nicht mehr. Danke an capabilities(7).
0xC0000022L
@SnakeDoc können Sie verwenden authbind auch
Abdul Ahad

Antworten:

27

Es sieht so aus, als hätten andere Ihren Standpunkt verfehlt, was nicht der Grund dafür war, geänderte Wurzeln zu verwenden, die Sie natürlich bereits genau kennen, oder was Sie sonst noch tun können, um den Dæmons Grenzen zu setzen, wenn Sie auch genau wissen, wie man unter den Ägide von läuft nicht privilegierte Benutzerkonten; aber warum diese Dinge zu tun in der Anwendung . Es gibt tatsächlich ein gutes Beispiel dafür, warum.

Betrachten Sie das Design des httpddæmon-Programms in Daniel J. Bernsteins Publicfile-Paket. Als Erstes wird root in das Stammverzeichnis geändert, das mit einem Befehlsargument verwendet werden soll, und anschließend werden Berechtigungen für die nicht privilegierte Benutzer-ID und Gruppen-ID gelöscht, die in zwei Umgebungsvariablen übergeben werden.

Die Management-Toolsets von Dæmon verfügen über spezielle Tools zum Ändern des Stammverzeichnisses und zum Löschen von nicht privilegierten Benutzer- und Gruppen-IDs. Gerrit Papes Runit hat chpst. Mein nosh Toolset hat chrootund setuidgid-fromenv. Laurent Bercots s6 hat s6-chrootund s6-setuidgid. Wayne Marshalls Täter hat runtoolund runuid. Und so weiter. Tatsächlich haben sie alle M. Bernsteins eigenes daemontools-Toolset setuidgidals Vorläufer.

Man könnte denken, dass man die Funktionalität aus httpdsolchen dedizierten Werkzeugen extrahieren und verwenden könnte. Dann wird, wie Sie sich vorstellen, kein Teil des Serverprogramms jemals mit Superuser-Rechten ausgeführt.

Das Problem ist, dass man als direkte Folge erheblich mehr Arbeit aufwenden muss, um die geänderte Wurzel aufzubauen, was neue Probleme aufwirft.

Bei Bernstein httpdsind die einzigen Dateien und Verzeichnisse im Stammverzeichnisbaum diejenigen, die für die Welt veröffentlicht werden sollen. Es gibt überhaupt nichts anderes im Baum. Darüber hinaus gibt es keinen Grund dafür, dass in diesem Baum eine ausführbare Programm-Image-Datei vorhanden ist.

Aber bewegen ändert die Root - Verzeichnis in ein Kette-Ladeprogramm (oder systemd), und plötzlich die Programm Bilddatei für httpdbeliebigen Shared Libraries , dass es geladen wird , und spezielle Dateien in /etc, /runund /devdass der Programmlader oder C - Laufzeitbibliothek Zugangs während Programminitialisierung (die Sie finden können ziemlich überraschend , wenn Sie truss/ straceein C oder C ++ Programm), auch in der veränderten Wurzel sein. Andernfalls httpdkann nicht angekettet werden und wird nicht geladen / ausgeführt.

Denken Sie daran, dass dies ein HTTP (S) -Inhaltsserver ist. Es kann möglicherweise jede (weltweit lesbare) Datei im geänderten Stammverzeichnis bereitstellen. Dies umfasst jetzt Dinge wie Ihre gemeinsam genutzten Bibliotheken, Ihren Programmlader und Kopien verschiedener Loader- / CRTL-Konfigurationsdateien für Ihr Betriebssystem. Und wenn durch eine (zufällige) bedeutet , dass die Content - Server - Zugriff hat Schreib Zeug, ein kompromittierte Server möglicherweise Schreibzugriff auf das Programmbild für gewinnen kann httpdselbst oder sogar Programmlader Ihres Systems. (Denken Sie daran , dass Sie haben jetzt zwei parallele Sätze von /usr, /lib, /etc, /run, und /devVerzeichnisse sicher zu halten.)

Dies ist jedoch nicht der Fall, wenn httpdRoot-Rechte geändert und Berechtigungen selbst gelöscht werden.

Sie haben also mit einer kleinen Menge privilegierten Codes gehandelt, der ziemlich einfach zu überwachen ist und der direkt zu Beginn des httpdProgramms mit Superuser-Rechten ausgeführt wird. für eine stark erweiterte Angriffsfläche von Dateien und Verzeichnissen innerhalb des geänderten Stamms.

Deshalb ist es nicht so einfach, alles außerhalb des Serviceprogramms zu erledigen.

Beachten Sie, dass dies dennoch ein Minimum an Funktionalität in httpdsich ist. Der gesamte Code, mit dem beispielsweise in der Kontodatenbank des Betriebssystems nach der Benutzer-ID und der Gruppen-ID gesucht wird, um diese Umgebungsvariablen überhaupt erst zu erstellen, befindet sich außerhalb des httpdProgramms und wird in einfachen, eigenständigen, überprüfbaren Befehlen wie z envuidgid. (Und natürlich ist es ein ucspi Werkzeug, so dass es enthält keine der Code auf der entsprechenden TCP - Port zu hören (n) oder Verbindungen zu akzeptieren, diejenigen sind die Domäne der Befehle wie tcpserver, tcp-socket-listen, tcp-socket-accept, s6-tcpserver4-socketbinder, s6-tcpserver4d, und so weiter.)

Weitere Lektüre

JdeBP
quelle
+1, als angeklagt schuldig. Ich fand den Titel und den letzten Absatz mehrdeutig, und wenn Sie Recht haben, habe ich den Punkt verpasst. Diese Antwort gibt eine sehr praktische Interpretation. Persönlich würde ich ausdrücklich darauf hinweisen, dass das Erstellen der Chroot-Umgebung eine zusätzliche Anstrengung ist, die die meisten Menschen vermeiden möchten. Aber die 2 Sicherheitspunkte hier sind schon gut gemacht.
Sourcejedi
Ein weiterer zu beachtender Punkt ist, dass der privilegierte Code keinen Remote-Exploits ausgesetzt wird, wenn der Server Privilegien verwirft, bevor er Netzwerkverkehr verarbeitet.
Kasperd
5

Ich denke, dass viele Details Ihrer Frage gleichermaßen zutreffen könnten avahi-daemon, die ich kürzlich angeschaut habe. (Ich könnte ein anderes Detail übersehen haben, das sich jedoch unterscheidet). Das Ausführen von avahi-daemon in einer Chroot hat viele Vorteile, falls avahi-daemon gefährdet ist. Diese beinhalten:

  1. Es kann kein Basisverzeichnis eines Benutzers lesen und keine privaten Informationen herausfiltern.
  2. Es kann keine Bugs in anderen Programmen ausnutzen, indem es nach / tmp schreibt. Es gibt mindestens eine ganze Kategorie solcher Fehler. ZB https://www.google.de/search?q=tmp+race+security+bug
  3. Es kann keine Unix-Socket-Datei außerhalb der Chroot öffnen, auf der andere Daemons möglicherweise Nachrichten abhören und lesen.

Punkt 3 könnte besonders nützlich sein, wenn Sie keinen Dbus oder ähnliches verwenden ... Ich denke, Avahi-Daemon verwendet Dbus, sodass sichergestellt ist, dass der Zugriff auf den System-Dbus auch von innerhalb der Chroot aus möglich ist. Wenn Sie nicht in der Lage sein müssen, Nachrichten auf dem System-D-Bus zu senden, ist es möglicherweise eine nette Sicherheitsfunktion, diese Fähigkeit zu verweigern.

Verwaltung mit systemd Unit-Datei

Beachten Sie, dass avahi-daemon, wenn es neu geschrieben wurde, sich möglicherweise aus Sicherheitsgründen auf systemd verlassen und z ProtectHome. Ich habe eine Änderung an Avahi-Daemon vorgeschlagen, um diese Schutzfunktionen als zusätzliche Ebene hinzuzufügen, zusammen mit einigen zusätzlichen Schutzfunktionen, die von Chroot nicht garantiert werden. Die vollständige Liste der von mir vorgeschlagenen Optionen finden Sie hier:

https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a

Es sieht so aus, als gäbe es weitere Einschränkungen, die ich hätte anwenden können, wenn avahi-daemon nicht chroot selbst verwendet hätte, von denen einige in der Festschreibungsnachricht erwähnt sind. Ich bin mir nicht sicher, wie viel dies gilt.

Beachten Sie, dass die von mir verwendeten Schutzfunktionen den Daemon nicht daran gehindert hätten, Unix-Socket-Dateien zu öffnen (siehe Punkt 3 oben).

Ein anderer Ansatz wäre die Verwendung von SELinux. Allerdings würden Sie Ihre Anwendung an diese Untergruppe von Linux-Distributionen binden. Der Grund, warum ich SELinux hier positiv fand, ist, dass SELinux den Zugriff, den Prozesse auf dbus haben, auf feinkörnige Weise einschränkt. Ich denke zum Beispiel, dass Sie oft erwarten können, dass systemddies nicht in der Liste der Busnamen enthalten ist, an die Sie Nachrichten senden müssen :-).

"Ich habe mich gefragt, ob die Verwendung von systemd-Sandboxen sicherer ist als chroot / setuid / umask / ..."

Zusammenfassung: Warum nicht beides? Lass uns das obenstehende ein wenig dekodieren :-).

Wenn Sie an Punkt 3 denken, bietet die Verwendung von Chroot mehr Einschränkungen. ProtectHome = und seine Freunde versuchen nicht einmal, so restriktiv wie chroot zu sein. (Zum Beispiel keine der genannten Systemd-Options-Blacklists /run, in denen wir Unix-Socket-Dateien ablegen).

chroot zeigt, dass das Einschränken des Dateisystemzugriffs sehr mächtig sein kann, aber nicht alles unter Linux ist eine Datei :-). Es gibt systemd-Optionen, die andere Dinge einschränken können, die keine Dateien sind. Dies ist nützlich, wenn das Programm kompromittiert ist. Sie können die verfügbaren Kernelfunktionen reduzieren, um eine Sicherheitsanfälligkeit auszunutzen. Zum Beispiel benötigt avahi-daemon keine Bluetooth-Sockets und ich denke, Ihr Webserver auch nicht :-). Geben Sie ihm also keinen Zugriff auf die AF_BLUETOOTH-Adressfamilie. Verwenden Sie einfach die Whitelist AF_INET, AF_INET6 und möglicherweise AF_UNIX RestrictAddressFamilies=.

Bitte lesen Sie die Dokumentation für jede Option, die Sie verwenden. Einige Optionen sind in Kombination mit anderen Optionen effektiver und einige sind nicht auf allen CPU-Architekturen verfügbar. (Nicht weil die CPU schlecht ist, sondern weil der Linux-Port für diese CPU nicht so gut ausgelegt ist. Ich denke).

(Hier gibt es ein allgemeines Prinzip. Es ist sicherer, wenn Sie Listen schreiben können, was Sie zulassen möchten, und nicht, was Sie ablehnen möchten. Wenn Sie beispielsweise eine Chroot definieren, erhalten Sie eine Liste der Dateien, auf die Sie zugreifen dürfen, und dies ist robuster als zu sagen, dass Sie blockieren möchten /home).

Grundsätzlich können Sie alle Einschränkungen vor setuid () selbst anwenden. Es ist alles nur Code, den Sie von systemd kopieren können. Die Optionen für Systemeinheiten sollten jedoch wesentlich einfacher zu schreiben sein, und da sie in einem Standardformat vorliegen, sollten sie leichter zu lesen und zu überprüfen sein.

Daher kann ich nur empfehlen, den Sandbox-Abschnitt man systemd.execauf Ihrer Zielplattform zu lesen . Aber wenn Sie die sicherste Ausführung möglich wollen, würde ich keine Angst zu versuchen chroot(und dann fallen rootPrivilegien) in Ihrem Programm auch . Hier gibt es einen Kompromiss. Durch chrootdie Verwendung werden dem Gesamtdesign einige Einschränkungen auferlegt. Wenn Sie bereits ein Design haben, das Chroot verwendet und das zu tun scheint, was Sie brauchen, klingt das ziemlich gut.

sourcejedi
quelle
+1 speziell für die Systemvorschläge.
Mattdm
Ich habe einiges von deiner Antwort gelernt, wenn Stapelüberlauf mehrere Antworten erlauben würde, würde ich deine auch akzeptieren. Ich habe mich gefragt, ob systemd Verwendung sicherer als Sandbox chroot / setuid / umask / ...
mur
@ bin froh, dass es dir gefallen hat :). Das ist eine sehr natürliche Antwort auf meine Antwort. Deshalb habe ich es erneut aktualisiert, um Ihre Frage zu beantworten.
Sourcejedi
1

Wenn Sie sich auf systemd verlassen können, ist es in der Tat sicherer (und einfacher!), Das Sandboxing systemd zu überlassen. (Natürlich kann die Anwendung auch erkennen, ob sie von systemd sandboxed gestartet wurde oder nicht, und die Sandbox selbst, wenn sie noch root ist.) Das Äquivalent des von Ihnen beschriebenen Dienstes wäre:

[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...

Aber wir müssen hier nicht aufhören. systemd kann auch viele andere Sandbox-Aufgaben für Sie erledigen - hier einige Beispiele:

[Service]
# allocate separate /tmp and /var/tmp for the service
PrivateTmp=yes
# mount / (except for some subdirectories) read-only
ProtectSystem=strict
# empty /home, /root
ProtectHome=yes
# disable setuid and other privilege escalation mechanisms
NoNewPrivileges=yes
# separate network namespace with only loopback device
PrivateNetwork=yes
# only unix domain sockets (no inet, inet6, netlink, …)
RestrictAddressFamilies=AF_UNIX

Siehe man 5 systemd.execfür viel mehr Richtlinien und detailliertere Beschreibungen. Wenn Sie Ihren Daemon-Socket aktivierbar machen ( man 5 systemd.socket), können Sie sogar die netzwerkbezogenen Optionen verwenden: Der einzige Link des Dienstes zur Außenwelt ist der von systemd empfangene Netzwerk-Socket, er kann keine Verbindung zu etwas anderem herstellen. Wenn es sich um einen einfachen Server handelt, der nur einige Ports überwacht und keine Verbindung zu anderen Servern herstellen muss, kann dies hilfreich sein. (Die mit dem Dateisystem verbundenen Optionen können RootDirectorymeiner Meinung nach auch veraltet sein, sodass Sie möglicherweise nicht mehr die Mühe haben, ein neues Stammverzeichnis mit allen erforderlichen Binärdateien und Bibliotheken einzurichten.)

Neuere systemd-Versionen (seit v232) unterstützen ebenfalls DynamicUser=yes, wobei systemd den Dienstbenutzer nur für die Laufzeit des Dienstes automatisch für Sie zuweist . Das heißt , Sie müssen nicht einen permanenten Benutzer für den Dienst registrieren lassen , und funktionieren gut , solange der Dienst schreiben nicht an irgendwelche Dateisystem an anderen Stellen als seine StateDirectory, LogsDirectoryund CacheDirectory(die man auch in der Unit - Datei deklarieren - sehen Sie man 5 systemd.execnoch einmal - und welches Systemd sich dann darum kümmert, sie dem dynamischen Benutzer richtig zuzuweisen).

Lucas Werkmeister
quelle