Nun, Sie könnten immer tun:
#! /bin/bash -
{ shopt -s expand_aliases;SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";};alias skip=":||:<<'SWITCH_TO_USER $_u'"
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
${_u+:} alias skip=:;} 2>/dev/null
skip
echo test
a=foo
set a b
SWITCH_TO_USER root
echo "$a and $1 as $(id -un)"
set -x
foo() { echo "bar as $(id -un)"; }
SWITCH_TO_USER rag
foo
set +x
SWITCH_TO_USER root again
echo "hi again from $(id -un)"
(ʘ‿ʘ)
Das begann zunächst als Witz, da es implementiert, was angefordert wird, obwohl es wahrscheinlich nicht genau wie erwartet ist, und praktisch nicht nützlich ist. Aber da es sich zu etwas entwickelt hat, das bis zu einem gewissen Grad funktioniert und ein paar nette Hacks beinhaltet, hier eine kleine Erklärung:
Wie Miroslav sagte , besteht die einzige Möglichkeit für einen nicht privilegierten Prozess, die UID zu ändern, darin, eine ausführbare Setuid-Datei auszuführen , wenn wir die Linux- Funktionen außer Acht lassen (was auch hier sowieso nicht wirklich hilfreich wäre).
Sobald Sie jedoch das Superuser-Privileg erhalten haben (indem Sie beispielsweise eine ausführbare Setuid-Datei ausführen, deren Eigentümer root ist), können Sie die effektive Benutzer-ID zwischen Ihrer ursprünglichen Benutzer-ID 0 und einer anderen ID hin und her wechseln, es sei denn, Sie geben Ihre gespeicherte Set-Benutzer-ID auf ( wie Dinge wie sudo
oder in der su
Regel tun).
Zum Beispiel:
$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env
Jetzt habe ich einen env
Befehl, mit dem ich jeden Befehl mit einer effektiven Benutzer-ID und einer gespeicherten Benutzer-ID von 0 ausführen kann (meine tatsächliche Benutzer-ID ist immer noch 1000):
$ ./env id -u
0
$ ./env id -ru
1000
$ ./env -u PATH =perl -e '$>=1; system("id -u"); $>=0;$>=2; system("id -u");
$>=0; $>=$<=3; system("id -ru; id -u"); $>=0;$<=$>=4; system("id -ru; id -u")'
1
2
3
3
4
4
perl
hat Wrapper für setuid
/ seteuid
(diese $>
und $<
Variablen).
Zsh auch:
$ sudo zsh -c 'EUID=1; id -u; EUID=0; EUID=2; id -u'
1
2
Obwohl oben diese id
Befehle mit einer echten Benutzer-ID und einer gespeicherten Set-Benutzer-ID von 0 aufgerufen werden (obwohl, wenn ich meine ./env
stattdessen verwendet sudo
hätte, nur die gespeicherte Set-Benutzer-ID gewesen wäre, während die echte Benutzer-ID 1000 geblieben wäre), was bedeutet, dass Wenn es sich um nicht vertrauenswürdige Befehle handelt, können sie dennoch Schaden anrichten. Schreiben Sie sie stattdessen wie folgt:
$ sudo zsh -c 'UID=1 id -u; UID=2 id -u'
(das heißt, alle UIDs (effektive, reale und gespeicherte Menge) nur für die Ausführung dieser Befehle.
bash
hat keine Möglichkeit, die Benutzer-IDs zu ändern. Selbst wenn Sie eine ausführbare setuid-Datei hätten, mit der Sie Ihr bash
Skript aufrufen könnten , würde dies nicht helfen.
Mit bash
bleibt Ihnen die Möglichkeit, jedes Mal, wenn Sie die UID ändern möchten, eine ausführbare Setuid-Datei auszuführen.
Die Idee im obigen Skript besteht darin, SWITCH_TO_USER aufzurufen, um eine neue Bash-Instanz auszuführen und den Rest des Skripts auszuführen.
SWITCH_TO_USER someuser
ist mehr oder weniger eine Funktion, die das Skript erneut als anderer Benutzer ausführt (mit sudo
), aber den Start des Skripts bis überspringt SWITCH_TO_USER someuser
.
Schwierig wird es, dass wir den Status der aktuellen Bash beibehalten möchten, nachdem wir die neue Bash als anderer Benutzer gestartet haben.
Lassen Sie es uns zusammenfassen:
{ shopt -s expand_aliases;
Wir brauchen Aliase. Einer der Tricks in diesem Skript besteht darin, den Teil des Skripts bis zum zu überspringen SWITCH_TO_USER someuser
, mit etwas wie:
:||: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Dieses Formular ähnelt dem #if 0
in C verwendeten, mit dem Sie Code vollständig auskommentieren können.
:
ist ein No-Op, das true zurückgibt. So in : || :
dem zweiten :
wird nie ausgeführt. Es wird jedoch analysiert. Und das << 'xxx'
ist eine Form des Here-Dokuments, bei dem (weil xxx
zitiert wird) keine Erweiterung oder Interpretation vorgenommen wird.
Wir hätten tun können:
: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Aber das hätte bedeutet, dass das Here-Dokument geschrieben und als Standard übergeben werden müsste :
. :||:
vermeidet das.
Nun, wo es hacky wird, ist, dass wir die Tatsache verwenden, dass bash
Aliase sehr früh in seinem Parsing-Prozess erweitert werden. Zu haben , skip
ist ein Alias für den :||: << 'SWITCH_TO_USER someuther'
Teil des Kommentierung-out - Konstrukts.
Lass uns weitermachen:
SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";}
Hier ist die Definition der Funktion SWITCH_TO_USER . Wir werden unten sehen, dass SWITCH_TO_USER irgendwann ein Alias sein wird, der um diese Funktion gewickelt ist.
Diese Funktion führt den Großteil der erneuten Ausführung des Skripts aus. Am Ende sehen wir, dass es (im selben Prozess wegen exec
) bash
mit der _x
Variablen in seiner Umgebung erneut ausgeführt wird (wir verwenden es env
hier, weil es sudo
normalerweise seine Umgebung bereinigt und es nicht erlaubt, beliebige env vars über zu übergeben). Das bash
wertet den Inhalt dieses $_x
Variable als Bash - Code und Quellen Skript selbst.
_x
ist früher definiert als:
_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'
Alle die declare
, alias
, shopt -p
set +o
ein Abbild des inneren Zustands der Schale bilden ausgegeben. Das heißt, sie speichern die Definition aller Variablen, Funktionen, Aliase und Optionen als Shell-Code, der ausgewertet werden kann. Darüber hinaus fügen wir die Einstellung der Positionsparameter ( $1
, $2
...) basierend auf dem Wert des $_a
Arrays hinzu (siehe unten) und einige Bereinigungen, damit die große $_x
Variable für die verbleibenden nicht in der Umgebung bleibt des Skripts.
Sie werden feststellen, dass der erste Teil bis zu set +x
in eine Befehlsgruppe eingeschlossen ist, deren stderr zu /dev/null
( {...} 2> /dev/null
) umgeleitet wird . Das liegt daran, dass wir , wenn das Skript set -x
(oder set -o xtrace
) irgendwann ausgeführt wird, nicht möchten, dass diese Präambel Spuren generiert, da wir sie so wenig aufdringlich wie möglich gestalten möchten. Wir führen also eine aus set +x
(nachdem wir zuvor sichergestellt haben, dass die Optionen (einschließlich xtrace
) Einstellungen gespeichert werden), in denen die Traces an / dev / null gesendet werden.
Der eval "$_X"
stderr wird aus ähnlichen Gründen auch nach / dev / null umgeleitet, aber auch, um die Fehler beim Schreiben in spezielle schreibgeschützte Variablen zu vermeiden.
Fahren wir mit dem Skript fort:
alias skip=":||:<<'SWITCH_TO_USER $_u'"
Das ist unser oben beschriebener Trick. Beim ersten Skriptaufruf wird er abgebrochen (siehe unten).
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
Nun der Alias-Wrapper um SWITCH_TO_USER. Der Hauptgrund besteht darin, die Positionsparameter ( $1
, $2
...) an die neuen übergeben zu können bash
, die den Rest des Skripts interpretieren. Wir konnten es in der SWITCH_TO_USER
Funktion nicht tun, da sich innerhalb der Funktion "$@"
die Argumente für die Funktionen befinden, nicht die der Skripte. Die stderr-Umleitung nach / dev / null dient erneut zum Ausblenden von xtraces und zum Umgehen eval
eines Fehlers bash
. Dann rufen wir die SWITCH_TO_USER
Funktion auf .
${_u+:} alias skip=:
Dieser Teil bricht den skip
Alias ab (ersetzt ihn durch den :
Befehl no-op), sofern die $_u
Variable nicht festgelegt ist.
skip
Das ist unser skip
Pseudonym. Beim ersten Aufruf wird es nur sein :
(das No-Op). Bei erneuten Aufrufen von Subsequenzen wird es so etwas wie : :||: << 'SWITCH_TO_USER root'
.
echo test
a=foo
set a b
SWITCH_TO_USER root
Als Beispiel rufen wir an diesem Punkt das Skript als root
Benutzer erneut auf, und das Skript stellt den gespeicherten Status wieder her, springt zu dieser SWITCH_TO_USER root
Zeile und fährt fort.
Das bedeutet, dass es genau wie stat geschrieben werden muss, mit SWITCH_TO_USER
am Anfang der Zeile und mit genau einem Leerzeichen zwischen den Argumenten.
Der größte Teil von state, stdin, stdout und stderr bleibt erhalten, nicht jedoch die anderen Dateideskriptoren, da sie sudo
normalerweise geschlossen werden, sofern dies nicht ausdrücklich so konfiguriert ist. Also zum Beispiel:
exec 3> some-file
SWITCH_TO_USER bob
echo test >&3
wird normalerweise nicht funktionieren.
Beachten Sie außerdem Folgendes:
SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root
Das funktioniert nur, wenn Sie das Recht auf sudo
as alice
und alice
das Recht auf sudo
as bob
und bob
as haben root
.
In der Praxis ist das also nicht wirklich nützlich. Die Verwendung von su
anstelle von sudo
(oder einer sudo
Konfiguration, bei sudo
der der Zielbenutzer anstelle des Anrufers authentifiziert wird) ist möglicherweise etwas sinnvoller, aber das würde immer noch bedeuten, dass Sie die Kennwörter all dieser Typen kennen müssen.