Setuid für Shell-Skripte zulassen

184

Das setuidBerechtigungsbit weist Linux an, ein Programm mit der effektiven Benutzer-ID des Eigentümers anstelle des Executors auszuführen:

> cat setuid-test.c

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

int main(int argc, char** argv) {
    printf("%d", geteuid());
    return 0;
}

> gcc -o setuid-test setuid-test.c
> ./setuid-test

1000

> sudo chown nobody ./setuid-test; sudo chmod +s ./setuid-test
> ./setuid-test

65534

Dies gilt jedoch nur für ausführbare Dateien. Shell-Skripte ignorieren das setuid-Bit:

> cat setuid-test2

#!/bin/bash
id -u

> ./setuid-test2

1000

> sudo chown nobody ./setuid-test2; sudo chmod +s ./setuid-test2
> ./setuid-test2

1000

Wikipedia sagt :

Aufgrund der erhöhten Wahrscheinlichkeit von Sicherheitslücken ignorieren viele Betriebssysteme das Attribut setuid, wenn es auf ausführbare Shell-Skripten angewendet wird.

Wenn ich bereit bin, diese Risiken in Kauf zu nehmen, kann Linux dann angewiesen werden, das setuid-Bit in Shell-Skripten genauso zu behandeln wie in ausführbaren Dateien?

Wenn nicht, gibt es eine gemeinsame Problemumgehung für dieses Problem? Meine derzeitige Lösung besteht darin, einen sudoersEintrag hinzuzufügen , mit ALLdem ein bestimmtes Skript wie der Benutzer ausgeführt werden kann, unter NOPASSWDdem es ausgeführt werden soll, um die Kennworteingabeaufforderung zu vermeiden. Der Hauptnachteil davon ist die Notwendigkeit eines sudoersEintrags jedes Mal, wenn ich dies tun möchte, und die Notwendigkeit für den Anrufer, sudo some-scriptanstatt nursome-script

Michael Mrozek
quelle

Antworten:

202

Linux ignoriert das setuid¹-Bit in allen interpretierten ausführbaren Dateien (dh ausführbaren Dateien, die mit einer #!Zeile beginnen). In den häufig gestellten Fragen zu comp.unix.questions werden die Sicherheitsprobleme mit setuid-Shell-Skripten erläutert. Es gibt zwei Arten von Problemen: shebang-bezogene und shell-bezogene; Ich gehe unten auf weitere Details ein.

Wenn Ihnen die Sicherheit egal ist und Sie setuid-Skripte zulassen möchten, müssen Sie unter Linux den Kernel patchen. Ab dem 3.x-Kernel muss install_exec_credsder load_scriptFunktion vor dem Aufruf von ein Aufruf hinzugefügt werden open_exec, aber ich habe ihn nicht getestet.


Setuid Shebang

Die #!Implementierung von shebang ( ) unterliegt einer Racebedingung:

  1. Der Kernel öffnet die ausführbare Datei und stellt fest, dass sie mit beginnt #!.
  2. Der Kernel schließt die ausführbare Datei und öffnet stattdessen den Interpreter.
  3. Der Kernel fügt den Pfad zum Skript in die Argumentliste ein (as argv[1]) und führt den Interpreter aus.

Wenn mit dieser Implementierung Setuid-Skripte zulässig sind, kann ein Angreifer ein beliebiges Skript aufrufen, indem er einen symbolischen Link zu einem vorhandenen Setuid-Skript erstellt, ausführt und anordnet, den Link zu ändern, nachdem der Kernel Schritt 1 ausgeführt hat und bevor der Interpreter zu ausgeführt hat Eröffnung seines ersten Arguments. Aus diesem Grund ignorieren die meisten Unices das Setuid-Bit, wenn sie einen Shebang erkennen.

Eine Möglichkeit, diese Implementierung zu sichern, besteht darin, dass der Kernel die Skriptdatei sperrt, bis der Interpreter sie geöffnet hat (beachten Sie, dass dies nicht nur das Aufheben oder Überschreiben der Verknüpfung der Datei, sondern auch das Umbenennen eines Verzeichnisses im Pfad verhindern muss). Unix-Systeme scheuen jedoch in der Regel obligatorische Sperren, und symbolische Verknüpfungen würden eine korrekte Sperrfunktion besonders schwierig und invasiv machen. Ich glaube nicht, dass es jemand so macht.

Einige Unix-Systeme (hauptsächlich OpenBSD, NetBSD und Mac OS X, für die alle eine Kernel-Einstellung erforderlich ist) implementieren Secure Setuid Shebang mit einer zusätzlichen Funktion: Der Pfad bezieht sich auf die Datei, die bereits im Dateideskriptor N geöffnet ist (also wird geöffnet) ungefähr äquivalent zu ). Viele Unix-Systeme (einschließlich Linux) haben aber keine setuid-Skripte./dev/fd/N/dev/fd/Ndup(N)/dev/fd

  1. Der Kernel öffnet die ausführbare Datei und stellt fest, dass sie mit beginnt #!. Angenommen, der Dateideskriptor für die ausführbare Datei lautet 3.
  2. Der Kernel öffnet den Interpreter.
  3. Der Kernel fügt /dev/fd/3die Argumentliste (as argv[1]) ein und führt den Interpreter aus.

Die shebang-Seite von Sven Mascheck enthält viele Informationen zu shebang across unices, einschließlich der Unterstützung von setuid .


Setuid-Dolmetscher

Nehmen wir an, Sie haben es geschafft, Ihr Programm als Root auszuführen, entweder weil Ihr Betriebssystem setuid shebang unterstützt oder weil Sie einen nativen Binär-Wrapper (wie sudo) verwendet haben. Haben Sie eine Sicherheitslücke geöffnet? Vielleicht . Hier geht es nicht um interpretierte oder kompilierte Programme. Das Problem ist, ob sich Ihr Laufzeitsystem sicher verhält, wenn es mit Berechtigungen ausgeführt wird.

  • Jede dynamisch verknüpfte native ausführbare Binärdatei wird auf eine Weise vom Dynamic Loader (z. B. /lib/ld.so) interpretiert , der die vom Programm benötigten dynamischen Bibliotheken lädt. Auf vielen Unices können Sie den Suchpfad für dynamische Bibliotheken über die Umgebung konfigurieren ( LD_LIBRARY_PATHist ein gebräuchlicher Name für die Umgebungsvariable) und sogar zusätzliche Bibliotheken in alle ausgeführten Binärdateien laden ( LD_PRELOAD). Die Aufrufer des Programms kann in diesem Programm Kontext beliebigen Code auszuführen , indem er eine speziell gestaltete Platzierung libc.soin $LD_LIBRARY_PATH(unter anderem Taktik). Alle vernünftigen Systeme ignorieren die LD_*Variablen in den ausführbaren Dateien von setuid.

  • In Shells wie sh, csh und Derivaten werden Umgebungsvariablen automatisch zu Shell-Parametern. Durch Parameter wie PATH, IFSund vieles mehr, hat der Aufrufer des Skripts viele Möglichkeiten , beliebigen Code in den Shell - Skripten Kontext auszuführen. Einige Shells setzen diese Variablen auf vernünftige Standardeinstellungen, wenn sie feststellen, dass das Skript mit Berechtigungen aufgerufen wurde, aber ich weiß nicht, dass es eine bestimmte Implementierung gibt, der ich vertrauen würde.

  • Die meisten Laufzeitumgebungen (ob native, Bytecode oder interpretiert) haben ähnliche Funktionen. Nur wenige Benutzer treffen spezielle Vorsichtsmaßnahmen in setuid-ausführbaren Dateien, obwohl diejenigen, die systemeigenen Code ausführen, häufig nichts Schöneres tun als dynamisches Verknüpfen (was Vorsichtsmaßnahmen erfordert).

  • Perl ist eine bemerkenswerte Ausnahme. Es unterstützt explizit setuid-Skripte auf sichere Weise. Tatsächlich kann Ihr Skript setuid ausführen, selbst wenn Ihr Betriebssystem das setuid-Bit in Skripten ignoriert. Dies liegt daran, dass Perl mit einem setuid-Root-Helfer geliefert wird, der die erforderlichen Überprüfungen durchführt und den Interpreter für die gewünschten Skripte mit den gewünschten Berechtigungen erneut aufruft. Dies wird im perlsec- Handbuch erklärt. Früher brauchte man #!/usr/bin/suidperl -wTstattdessen setuid-Perl-Skripte #!/usr/bin/perl -wT, aber auf den meisten modernen Systemen #!/usr/bin/perl -wTist dies ausreichend.

Beachten Sie, dass die Verwendung eines systemeigenen Binär- Wrappers nichts an sich bewirkt, um diese Probleme zu vermeiden . Tatsächlich kann dies die Situation verschlimmern , da Ihre Laufzeitumgebung möglicherweise nicht erkennt, dass sie mit Berechtigungen aufgerufen wird, und die Laufzeitkonfigurierbarkeit umgeht.

Ein nativer binärer Wrapper kann ein Shell-Skript sicher machen, wenn der Wrapper die Umgebung bereinigt . Das Skript muss darauf achten, nicht zu viele Annahmen zu machen (zB über das aktuelle Verzeichnis), aber das geht. Sie können sudo dafür verwenden, sofern es so eingerichtet ist, dass die Umgebung bereinigt wird. Das Blacklisting von Variablen ist fehleranfällig, daher immer eine Whitelist. Stellen Sie mit sudo sicher, dass die env_resetOption aktiviert und deaktiviert setenvist env_fileund env_keepnur harmlose Variablen enthält.


TL, DR:

  • Setuid shebang ist unsicher, wird aber normalerweise ignoriert.
  • Wenn Sie ein Programm mit Berechtigungen ausführen (entweder über sudo oder setuid), schreiben Sie systemeigenen Code oder Perl oder starten Sie das Programm mit einem Wrapper, der die Umgebung bereinigt (z. B. sudo mit der env_resetOption).

¹ Diese Diskussion gilt auch, wenn Sie "setuid" durch "setgid" ersetzen. Beide werden vom Linux-Kernel in Skripten ignoriert

Gilles
quelle
2
@Josh: Sichere setuid-Shell-Skripte sind nur möglich, wenn sowohl der Shell-Implementierer als auch der Skriptschreiber sehr vorsichtig sind. Anstelle von nativem Code empfehle ich Perl, wo die Implementierer darauf geachtet haben, dass setuid-Skripte mit geringem Aufwand für den Skriptschreiber sicher sind.
Gilles
3
anscheinend ist das suidperl
zeug seit jahren
7
Eigentlich suidperlperl5110delta:> "suidperl" wurde entfernt wie von Perl 5.11 (5.12 stable) entfernt worden ist . Früher wurde ein Mechanismus zum Emulieren von setuid-Berechtigungsbits auf Systemen bereitgestellt, die dies nicht ordnungsgemäß unterstützen. perl5120delta:> "suidperl" ist nicht mehr Teil von Perl. Früher wurde ein Mechanismus zum Emulieren von setuid-Berechtigungsbits auf Systemen bereitgestellt, die dies nicht ordnungsgemäß unterstützen.
Randy Stauner
5
Beachten Sie auch diese Zeile aus Perl 5.6.1 (vor fast einem Jahrzehnt) ... perl561delta:> Beachten Sie, dass suidperl in keiner neueren Version von Perl standardmäßig erstellt oder installiert wird. Von der Verwendung von Suidperl wird dringend abgeraten . Wenn Sie glauben, dass Sie es brauchen, probieren Sie zuerst Alternativen wie sudo aus. Siehe courtesan.com/sudo .
Randy Stauner
3
Ich verstehe nicht: Dies scheint eine Erklärung der Gründe für die Probleme zu sein, aber gibt es hier eine tatsächliche Antwort auf die Frage des OP? Kann ich meinem Betriebssystem mitteilen, dass setuid-Shell-Skripte ausgeführt werden sollen?
Tom
55

Eine Möglichkeit, dieses Problem zu lösen, besteht darin, das Shell-Skript von einem Programm aus aufzurufen, das das setuid-Bit verwenden kann.
Es ist so etwas wie Sudo. So würden Sie dies beispielsweise in einem C-Programm ausführen:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    setuid( 0 );   // you can set it at run time also
    system( "/home/pubuntu/setuid-test2.sh" );
    return 0;
 }

Speichern Sie es als setuid-test2.c. Jetzt
kompiliere
die setuid in diesem Programm binär:

su - nobody   
[enter password]  
chown nobody:nobody a.out  
chmod 4755 a.out  

Jetzt sollten Sie es ausführen können und sehen, dass Ihr Skript ohne Berechtigungen ausgeführt wird.
Aber auch hier müssen Sie entweder den Skriptpfad fest codieren oder ihn als Befehlszeilenargument an die obige Exe übergeben.

Hemant
quelle
25
Ich würde von dem Vorschlag abraten, die Übergabe des Skripts als Befehlszeilenargument zuzulassen, da dies im Wesentlichen jedem ermöglicht, der das Programm ausführen kann, ein beliebiges Skript als der definierte Benutzer auszuführen.
dsp
39
Beachten Sie, dass dies unsicher ist, auch wenn der vollständige Pfad zum Skript fest codiert ist. Die Shell erbt Variablen von der Umgebung und viele von ihnen ermöglichen dem Aufrufer, beliebigen Code einzufügen. PATHund LD_LIBRARY_PATHsind offensichtliche Vektoren. Einige Shells ausführen $ENVoder $BASHENVoder , ~/.zshenvnoch bevor sie das Skript richtigen beginnen Ausführung, so dass Sie nicht von dieser überhaupt aus dem Skript schützen. Die einzige sichere Möglichkeit, ein Shell-Skript mit Berechtigungen aufzurufen, besteht darin , die Umgebung zu bereinigen . Sudo weiß, wie man es sicher macht. Schreiben Sie also keinen eigenen Wrapper, sondern verwenden Sie sudo .
Gilles
28
Es tut mir leid, dass er plötzlich dafür abgelehnt wird - ich habe ausdrücklich gesagt, dass ich auch unsichere Versionen hören möchte, und ich stellte mir eine ausführbare Datei vor, die ein Shell-Skript-Argument hat, als ich es sagte. Natürlich ist es massiv unsicher, aber ich wollte wissen, welche Möglichkeiten es gibt
Michael Mrozek
8
@ Gilles: Zu Ihrer Information, Linux deaktiviert LD_LIBRARY_PATHunter anderem, wenn es auf das setuid-Bit stößt.
Grawity
3
Anstatt zu verwenden system, ist es möglicherweise einfacher (und effizienter), eine der execFamilien zu verwenden - am wahrscheinlichsten execve. Auf diese Weise erstellen Sie keinen neuen Prozess und starten keine Shell. Sie können Argumente weiterleiten (vorausgesetzt, Ihr privilegiertes Skript kann sicher mit Argumenten umgehen).
Toby Speight
23

Ich stelle ein paar Skripte voran, die sich in diesem Boot befinden:

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

Beachten Sie, dass dies nicht verwendet, setuidsondern einfach die aktuelle Datei mit ausführt sudo.

rcrowley
quelle
4
Hier wird setuid nicht verwendet, sondern es wird lediglich eine sudoEingabeaufforderung ausgegeben. (Für mich ist es der springende Punkt bei setuid, Dinge als root laufen zu lassen, ohne sudo zu benötigen.)
Luc
12

Wenn Sie einen Anruf vermeiden möchten, sudo some_scriptkönnen Sie Folgendes tun:

  #!/ust/bin/env sh

  sudo /usr/local/scripts/your_script

SETUID-Programme müssen mit äußerster Sorgfalt entworfen werden, da sie mit Root-Rechten ausgeführt werden und die Benutzer eine große Kontrolle über sie haben. Sie müssen alles auf geistige Gesundheit überprüfen. Sie können es nicht mit Skripten machen, weil:

  • Shells sind große Software-Teile, die stark mit dem Benutzer interagieren. Es ist fast unmöglich, alles zu überprüfen - zumal der größte Teil des Codes nicht für die Ausführung in einem solchen Modus vorgesehen ist.
  • Skripte sind meist eine schnelle Lösung und werden normalerweise nicht so sorgfältig erstellt, dass sie setuid zulassen. Sie haben viele potenziell gefährliche Eigenschaften.
  • Sie hängen stark von anderen Programmen ab. Es ist nicht ausreichend, dass die Shell überprüft wurde. sed, awkusw. müssten ebenfalls überprüft werden

Bitte beachten Sie, dass sudoeinige Sicherheitsüberprüfungen vorhanden sind, diese jedoch nicht ausreichen. Überprüfen Sie jede Zeile in Ihrem eigenen Code.

Als letzte Anmerkung: Erwägen Sie die Verwendung von Funktionen. Mit ihnen können Sie einem Prozess, der als Benutzer ausgeführt wird, spezielle Berechtigungen zuweisen, für die normalerweise Root-Berechtigungen erforderlich sind. Beispielsweise pingmuss das Netzwerk zwar manipuliert werden, es muss jedoch keinen Zugriff auf Dateien haben. Ich bin mir jedoch nicht sicher, ob sie vererbt werden.

Maciej Piechotka
quelle
2
Ich nehme an, /ust/bin/envsollte sein /usr/bin/env.
Bob
4

Sie können einen Alias ​​für sudo + den Namen des Skripts erstellen. Das ist natürlich noch aufwändiger, da Sie dann auch einen Alias ​​einrichten müssen, aber Sie müssen nicht sudo eingeben.

Wenn Sie jedoch nichts gegen schreckliche Sicherheitsrisiken haben, verwenden Sie eine setuid-Shell als Interpreter für das Shell-Skript. Ich weiß nicht, ob das für dich funktionieren wird, aber ich denke, es könnte sein.

Lassen Sie mich jedoch sagen, dass ich davon abraten würde. Ich erwähne es nur zu Bildungszwecken ;-)

wzzrd
quelle
5
Es wird klappen. Schrecklich, wie du gesagt hast. Das SETUID-Bit ermöglicht die Ausführung mit dem Eigentumsrecht. Die Setuid-Shell (sofern sie nicht für die Verwendung mit setuid entwickelt wurde) wird für jeden Benutzer als Root ausgeführt. Das heißt, jeder kann ausführen rm -rf /(und andere Befehle aus der Serie DON'T DO IT AT HOME ).
Maciej Piechotka
9
@MaciejPiechotka von DON'T DO IT AT HOME meinst du das gerne bei der Arbeit? :)
Peterph
4

super [-r reqpath] command [args]

Mit Super können bestimmte Benutzer Skripte (oder andere Befehle) so ausführen, als wären sie root. oder es kann die uid-, gid- und / oder ergänzenden Gruppen auf Befehlsbasis festlegen, bevor der Befehl ausgeführt wird. Es soll eine sichere Alternative zu Skripten sein, die setuid root verwenden. Mit Super können normale Benutzer auch Befehle zur Ausführung durch andere Benutzer bereitstellen. Diese werden mit der UID, der GID und den Gruppen des Benutzers ausgeführt, der den Befehl anbietet.

Super durchsucht eine "super.tab" -Datei, um festzustellen, ob der Benutzer den angeforderten Befehl ausführen darf. Wenn die Berechtigung erteilt wurde, führt super pgm [args] aus, wobei pgm das Programm ist, das diesem Befehl zugeordnet ist. (Die Ausführung von Root ist standardmäßig zulässig, kann jedoch weiterhin verweigert werden, wenn eine Regel Root ausschließt. Normalen Benutzern ist die Ausführung standardmäßig nicht gestattet.)

Wenn der Befehl eine symbolische Verknüpfung (oder auch eine feste Verknüpfung) zum Superprogramm ist, entspricht die Eingabe von% command args der Eingabe von% super command args (Der Befehl darf nicht super sein, oder super erkennt nicht, dass er über a aufgerufen wird Verknüpfung.)

http://www.ucolick.org/~will/RUE/super/README

http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html

Nizam Mohamed
quelle
Hallo und willkommen auf der Seite! Wir erwarten hier detailliertere Antworten. Könnten Sie Ihre Antwort bearbeiten und erklären, was dieses Programm ist und wie es zur Lösung des OP-Problems beitragen kann?
Terdon
Vielen Dank, Nizam, für die stundenlangen Versuche, so etwas wie Super zu finden, damit Accounts von einem anderen Benutzer als Benutzer der Datei ausgeführt werden können.
Tschad,
2

Wenn aus irgendeinem Grund sudonicht verfügbar ist, können Sie ein Thin-Wrapper-Skript in C schreiben:

#include <unistd.h>
int main() {
    setuid(0);
    execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}

Und sobald Sie es kompiliert haben, setzen Sie es wie setuidmit chmod 4511 wrapper_script.

Dies ähnelt einer anderen veröffentlichten Antwort, führt das Skript jedoch in einer sauberen Umgebung aus und verwendet explizit /bin/bashanstelle der von aufgerufenen Shell system(), wodurch einige potenzielle Sicherheitslücken geschlossen werden.

Beachten Sie, dass dies die Umgebung vollständig verwirft. Wenn Sie einige Umgebungsvariablen verwenden möchten, ohne Sicherheitslücken zu öffnen, müssen Sie wirklich nur verwenden sudo.

Natürlich möchten Sie sicherstellen, dass das Skript selbst nur von root geschrieben werden kann.

Chris
quelle
-3

Ich habe diese Frage zum ersten Mal gefunden, ohne von allen Antworten überzeugt zu sein. Hier ist eine viel bessere, mit der Sie auch Ihr Bash-Skript vollständig verschleiern können, wenn Sie dazu geneigt sind!

Es sollte selbsterklärend sein.

#include <string>
#include <unistd.h>

template <typename T, typename U>
T &replace (
          T &str, 
    const U &from, 
    const U &to)
{
    size_t pos;
    size_t offset = 0;
    const size_t increment = to.size();

    while ((pos = str.find(from, offset)) != T::npos)
    {
        str.replace(pos, from.size(), to);
        offset = pos + increment;
    }

    return str;
}

int main(int argc, char* argv[])
{
    // Set UUID to root
    setuid(0);

    std::string script = 
R"(#!/bin/bash
whoami
echo $1
)";

    // Escape single quotes.
    replace(script, std::string("'"), std::string("'\"'\"'"));

    std::string command;
    command = command + "bash -c '" + script + "'"; 

    // Append the command line arguments.
    for (int a = 0; a < argc; ++a)
    {
        command = command + " " + argv[a];
    }

    return system(command.c_str());
}

Dann rennst du

g++ embedded.cpp -o embedded
sudo chown root embedded
sudo chmod u+s embedded
Theodore R. Smith
quelle
Warum die downvotes ???
Theodore R. Smith
2
Wahrscheinlich, weil es sich um eine übermäßig komplizierte Lösung handelt, die Zeichenfolgen manipuliert und eingebetteten Shell-Code enthält. Entweder Sie erstellen einen einfachen Launcher oder Sie verwenden sudo. Dies ist die schlechteste aller Optionen.
Siride