Setuid-Bit richtig verwenden

45

Ich habe einen Prozess, der Root-Rechte benötigt, wenn er von einem normalen Benutzer ausgeführt wird. Anscheinend kann ich das "setuid-Bit" verwenden, um dies zu erreichen. Wie geht das auf einem POSIX-System?

Wie kann ich dies auch mit einem Skript tun, das einen Interpreter verwendet (Bash, Perl, Python, PHP usw.)?

Goldlöckchen
quelle

Antworten:

53

Das setuid-Bit kann für eine ausführbare Datei festgelegt werden, sodass das Programm beim Ausführen die Berechtigungen des Eigentümers der Datei anstelle des tatsächlichen Benutzers hat, sofern diese unterschiedlich sind. Dies ist der Unterschied zwischen der effektiven UID (Benutzer-ID) und der realen UID.

Einige gängige Dienstprogramme, wie z. B. passwd, sind im Besitz von root und werden so konfiguriert, dass sie nicht passwdbenötigt werden ( Zugriff /etc/shadownur für root möglich).

Die beste Strategie dabei ist, sofort das zu tun, was Sie als Superuser tun müssen, und dann die Berechtigungen zu verringern, damit Fehler oder Missbrauch beim Ausführen von root weniger wahrscheinlich sind. Dazu setzen Sie die effektive Benutzer-ID des Prozesses auf die tatsächliche Benutzer-ID. In POSIX C:

#define _POSIX_C_SOURCE 200112L // Needed with glibc (e.g., linux).
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

void report (uid_t real) {
    printf (
        "Real UID: %d Effective UID: %d\n",
        real,
        geteuid()
    );
}

int main (void) {
    uid_t real = getuid();
    report(real);
    seteuid(real);
    report(real);
    return 0;
}

Die relevanten Funktionen, die in den meisten höheren Sprachen gleichwertig sein sollten, wenn sie auf POSIX-Systemen häufig verwendet werden:

  • getuid(): Holen Sie sich die echte UID.
  • geteuid(): Holen Sie sich die effektive UID.
  • seteuid(): Stellen Sie die effektive UID ein.

Sie können nichts mit dem letzten tun, das für die echte UID unangemessen ist, außer insofern, als das Setuid-Bit für die ausführbare Datei gesetzt wurde . Um dies zu versuchen, kompilieren Sie gcc test.c -o testuid. Sie müssen dann mit Privilegien:

chown root testuid
chmod u+s testuid

Der letzte setzt das setuid-Bit. Wenn Sie jetzt ./testuidals normaler Benutzer ausgeführt werden, wird der Prozess standardmäßig mit der effektiven UID 0 root ausgeführt.

Was ist mit Skripten?

Dies ist von Plattform zu Plattform unterschiedlich , aber unter Linux können Dinge, die einen Interpreter, einschließlich Bytecode, erfordern, das setuid-Bit nur verwenden, wenn es auf dem Interpreter gesetzt ist (was sehr, sehr dumm wäre). Hier ist ein einfaches Perl-Skript, das den obigen C-Code nachahmt:

#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);

print "Real UID: $< Effective UID: $>\n";
$> = $<; # Not an ASCII art greedy face, but genuine perl...
print "Real UID: $< Effective UID: $>\n"; 

Getreu seinen * nixy-Wurzeln hat Perl spezielle Variablen für effektive uid ( $>) und echte uid ( $<) eingebaut . Wenn Sie jedoch dasselbe versuchen chownund chmodes mit der kompilierten (aus C, vorheriges Beispiel) ausführbaren Datei verwenden, macht es keinen Unterschied. Das Skript kann keine Berechtigungen erhalten.

Die Antwort auf diese Frage ist, eine setuid-Binärdatei zu verwenden, um das Skript auszuführen:

#include <stdio.h>
#include <unistd.h> 

int main (int argc, char *argv[]) {
    if (argc < 2) {
        puts("Path to perl script required.");
        return 1;
    }
    const char *perl = "perl";
    argv[0] = (char*)perl;
    return execv("/usr/bin/perl", argv);
}

Kompilieren Sie dies gcc --std=c99 whatever.c -o perlsuiddann chown root perlsuid && chmod u+s perlsuid. Sie können jetzt jedes Perl-Skript mit einer effektiven UID von 0 ausführen , unabhängig davon, wem es gehört.

Eine ähnliche Strategie funktioniert mit PHP, Python usw. Aber ...

# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face

BITTE BITTE Lass sowas NICHT herumliegen . Höchstwahrscheinlich möchten Sie den Namen des Skripts tatsächlich als absoluten Pfad kompilieren , dh den gesamten Code in main()durch Folgendes ersetzen :

    const char *args[] = { "perl", "/opt/suid_scripts/whatever.pl" }
    return execv("/usr/bin/perl", (char * const*)args);

Sie stellen sicher, dass alle /opt/suid_scriptsInhalte für Benutzer ohne Rootberechtigung schreibgeschützt sind. Andernfalls könnte jemand etwas gegen etwas eintauschen whatever.pl.

Beachten Sie außerdem, dass in vielen Skriptsprachen Umgebungsvariablen die Art und Weise ändern können, in der sie ein Skript ausführen . Beispielsweise kann eine Umgebungsvariable dazu führen, dass eine vom Aufrufer bereitgestellte Bibliothek geladen wird, sodass der Aufrufer beliebigen Code als Root ausführen kann. Wenn Sie also nicht wissen, dass sowohl der Interpreter als auch das Skript selbst gegenüber allen möglichen Umgebungsvariablen robust sind, TUN Sie DIESES NICHT .

Also, was soll ich stattdessen tun?

Eine sicherere Möglichkeit, einem Benutzer ohne Rootberechtigung die Ausführung eines Skripts als Rootberechtigung zu ermöglichen, besteht darin, eine sudo- Regel hinzuzufügen und den Benutzer ausführen zu lassen sudo /path/to/script. Sudo entfernt die meisten Umgebungsvariablen und ermöglicht es dem Administrator, genau auszuwählen, wer den Befehl mit welchen Argumenten ausführen darf. Siehe So führen Sie ein bestimmtes Programm als root ohne Kennworteingabeaufforderung aus zum Beispiel.

Goldlöckchen
quelle
1
Viele Shells verhalten sich anders, wenn sie als -privilegedShell aufgerufen suid rootwerden. Wenn sie von einem verantwortlichen Skript aufgerufen werden, können sie auch sicher auf diese Weise aufgerufen werden. Ich befürchte jedoch, dass die Vorstellung eines verantwortungsvollen Skripts in der realen Welt ein bisschen wie ein Wunschtraum ist. Wie auch immer, es ist wahrscheinlich erwähnenswert, wie wichtig es ist, Schreibzugriffe jeglicher Art zu vermeiden, wenn dies bei erhöhten Berechtigungen möglich ist ...
mikeserv
@mikeserv Ich kenne die Konzepte von " -privilegedShell" und "Responsible Script" nicht. Möchten Sie eine weitere Antwort zu Muscheln geben? Ich kann dir das Häkchen natürlich nicht versprechen;) Es gibt nicht viele gute Gründe, dies zu tun - sudoist eine viel bessere Option, wenn möglich - ich habe es als generische Antwort geschrieben, die aus einer frühen Frage über die Authentifizierung von Systemkennwörtern in PHP stammt .
Goldlöckchen
1
Ich möchte das nicht wirklich tun - verantwortungsvolle Skripte sind wirklich schwierig - wahrscheinlich über mich hinaus. Aber ich muss sagen , ich bin mit Ihrer Analyse sudo- halte ich sudoals psychotisch , dass sie vorgibt , nicht zu sein root. Sie können sogar Muschelkugeln in Ihre Muschel legen sudoers. suist meiner meinung nach für scripting zwecke besser, sudoist aber gut für live anwendungen. Aber weder ist so explizit wie ein Skript mit einem #!/bin/ksh -pbangline oder was auch immer das suid kshin $PATHist.
mikeserv
Während diese Antwort die Frage beantwortet, sind setuid-Skripte ein Rezept, mit dem Sie sich einer lokalen Sicherheitsanfälligkeit bezüglich der Eskalation von Berechtigungen öffnen können. Es gibt einen Grund, warum Skripte nicht einfach zu setuid gemacht werden können. Die richtige Antwort lautet hier sudo.
mdadm
Ich habe mir erlaubt, Ihre Antwort zu bearbeiten, um zumindest die enorme Sicherheitslücke bei Umgebungsvariablen zu erwähnen. Ich mag dies nicht als kanonische Antwort, da es bei einer unsicheren Methode (dem Setuid-Wrapper für Skripte) zu vielen Abweichungen kommt. Am besten ist es, sudo zu empfehlen und die ausführliche Diskussion einem anderen Thread zu überlassen (ich habe es übrigens dort behandelt).
Gilles 'SO - hör auf böse zu sein'
11

Ja, das kannst du, aber es ist wahrscheinlich eine sehr schlechte Idee . Normalerweise setzen Sie das SUID-Bit nicht direkt in Ihrer ausführbaren Datei, sondern verwenden ein sudo (8) oder su (1) , um sie auszuführen (und begrenzen, wer sie ausführen kann).

Beachten Sie jedoch, dass es sehr viele Sicherheitsprobleme gibt, wenn reguläre Benutzer Programme (und insbesondere Skripte!) Als Root ausführen. Die meisten von ihnen müssen dies tun, es sei denn, das Programm wurde speziell und sehr sorgfältig dafür geschrieben, und böswillige Benutzer könnten es verwenden, um auf einfache Weise die Root-Shell zu erhalten.

Selbst wenn Sie dies tun würden, wäre es eine gute Idee, zuerst ALLE Eingaben für das Programm zu bereinigen, das Sie als Root ausführen möchten, einschließlich der Umgebung, der Befehlszeilenparameter, STDIN und aller von ihm verarbeiteten Dateien sowie Daten, die von Netzwerkverbindungen stammen es öffnet sich usw. usw. Es ist extrem schwer und noch schwerer, es richtig zu machen.

Beispielsweise können Sie Benutzern erlauben, dass nur einige Dateien mit dem Editor bearbeitet werden. Der Editor ermöglicht jedoch die Befehlsausführung externer Hilfsprogramme oder das Unterbrechen, sodass Benutzer die Root-Shell nach Belieben ausführen können. Oder Sie führen Shell-Skripte aus, die für Sie sehr sicherheitsrelevant sind, aber der Benutzer kann sie mit geändertem $ IFS oder anderen Umgebungsvariablen ausführen, um das Verhalten vollständig zu ändern. Oder es könnte ein PHP-Skript sein, das "ls" ausführen soll, aber der Benutzer führt es mit modifiziertem $ PATH aus und lässt es so eine Shell ausführen, die er "ls" nennt. Oder Dutzende anderer Sicherheitsprobleme.

Wenn das Programm wirklich einen Teil seiner Arbeit als root ausführen muss, ist es wahrscheinlich am besten, es so zu ändern, dass es als normaler Benutzer ausgeführt wird, aber nur den Teil auszuführen (nachdem so viel wie möglich bereinigt wurde), der unbedingt root durch das Hilfsprogramm suid benötigt.

Beachten Sie, dass es eine schlechte Idee ist, selbst wenn Sie Ihren lokalen Benutzern vollständig vertrauen (z. B. geben Sie ihnen ein Root-Passwort), da JEDER von ihnen eine unbeabsichtigte Sicherheitsüberprüfung durchführt (z. B. ein PHP-Skript online oder ein schwaches Passwort) (was auch immer) ermöglicht es einem entfernten Angreifer plötzlich, die gesamte Maschine vollständig zu gefährden.

Matija Nalis
quelle
1
abgeordnet. Siehe das Beispiel für sichere Aufrufe in der POSIX-Programmierserie für man sh- und selbst das tut ein nicht command -p env -i .... Es ist doch ziemlich schwer zu tun. Dennoch , wenn es richtig gemacht, für die explizite Zwecke kann es besser sein, suideinen kompetenten Dolmetscher als zu verwenden suoder sudo- wie das Verhalten der beiden wird man immer außerhalb der Kontrolle des Skripts sein (wenn auch suweniger wahrscheinlich Kurve Bälle zu werfen , da es dazu neigt , etwas weniger verrückt sein) . Das Bündeln des gesamten Pakets ist die einzig sichere Wette - es ist nur besser, wenn Sie sicher sind , dass es alles ist.
mikeserv