Bene, puoi sempre fare:
#! /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)"
(ʘ‿ʘ)
Ciò è iniziato inizialmente come uno scherzo poiché implementa ciò che è richiesto, sebbene probabilmente non esattamente come previsto, e non è praticamente utile. Ma mentre si è evoluto in qualcosa che funziona in una certa misura e coinvolge alcuni bei hack, ecco una piccola spiegazione:
Come ha detto Miroslav , se lasciamo da parte le funzionalità in stile Linux (che non aiuterebbero comunque qui), l'unico modo per un processo senza privilegi di cambiare uid è eseguendo un eseguibile setuid.
Tuttavia, una volta ottenuto il privilegio di superutente (eseguendo un eseguibile setuid il cui proprietario è root, ad esempio), è possibile cambiare l'ID utente effettivo avanti e indietro tra l'ID utente originale, 0 e qualsiasi altro id a meno che non si rinunci all'ID utente set salvato ( come cose come sudoo in sugenere fanno).
Per esempio:
$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env
Ora ho un envcomando che mi permette di eseguire qualsiasi comando con un ID utente efficace e un ID utente impostato salvato pari a 0 (il mio ID utente reale è ancora 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
perlha wrapper per setuid/ seteuid(quelli $>e $<variabili).
Anche zsh:
$ sudo zsh -c 'EUID=1; id -u; EUID=0; EUID=2; id -u'
1
2
Sebbene sopra quei idcomandi vengano chiamati con un vero ID utente e salvato un set userid di 0 (anche se se avessi usato il mio ./envinvece di sudoquello sarebbe stato solo il set salvato userid, mentre il vero ID utente sarebbe rimasto 1000), il che significa che se fossero comandi non attendibili, potrebbero comunque arrecare danno, quindi ti consigliamo di scriverlo come:
$ sudo zsh -c 'UID=1 id -u; UID=2 id -u'
(che è impostato su tutti gli uid (set effettivo, reale e salvato) solo per l'esecuzione di quei comandi.
bashnon ha alcun modo per modificare gli ID utente. Quindi, anche se avessi un eseguibile setuid con cui chiamare il tuo bashscript, questo non sarebbe d'aiuto.
Con bash, ti resta con l'esecuzione di un eseguibile setuid ogni volta che vuoi cambiare uid.
L'idea nello script sopra è su una chiamata a SWITCH_TO_USER, per eseguire una nuova istanza bash per eseguire il resto dello script.
SWITCH_TO_USER someuserè più o meno una funzione che esegue nuovamente lo script come un altro utente (usando sudo) ma salta l'inizio dello script fino a quando SWITCH_TO_USER someuser.
Dove diventa difficile è che vogliamo mantenere lo stato della bash corrente dopo aver avviato la nuova bash come utente diverso.
Analizziamolo:
{ shopt -s expand_aliases;
Avremo bisogno di alias. Uno dei trucchi di questo script è saltare la parte dello script fino a quando SWITCH_TO_USER someuser, con qualcosa del tipo:
:||: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Tale modulo è simile a quello #if 0utilizzato in C, ovvero un modo per commentare completamente un codice.
:è una no-op che ritorna vera. Quindi : || :, il secondo :non viene mai eseguito. Tuttavia, viene analizzato. E << 'xxx'c'è una forma di documento qui dove (perché xxxè citato), non viene fatta alcuna espansione o interpretazione.
Avremmo potuto fare:
: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Ma ciò avrebbe significato che il documento qui avrebbe dovuto essere scritto e trasmesso come standard :. :||:evita quello.
Ora, dove diventa confuso è che usiamo il fatto che bashespande gli alias molto presto nel suo processo di analisi. Essere skipalias della :||: << 'SWITCH_TO_USER someuther'parte del costrutto per commentare .
Andiamo avanti:
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";}
Ecco la definizione della funzione SWITCH_TO_USER . Vedremo di seguito che SWITCH_TO_USER alla fine sarà un alias racchiuso in quella funzione.
Tale funzione svolge la maggior parte della riesecuzione dello script. Alla fine vediamo che si ri-esegue (nello stesso processo a causa di exec) bashcon la _xvariabile nel suo ambiente (usiamo envqui perché di sudosolito igienizza il suo ambiente e non consente il passaggio di ambiti arbitrari attraverso l'altro). Ciò bashvaluta il contenuto di quella $_xvariabile come codice bash e genera lo script stesso.
_x è definito in precedenza come:
_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'
Tutti i declare, alias, shopt -p set +ouscita costituiscono un dump dello stato interno del guscio. Cioè, scaricano la definizione di tutte le variabili, funzioni, alias e opzioni come codice shell pronto per essere valutato. Inoltre, aggiungiamo l'impostazione dei parametri posizionali ( $1, $2...) in base al valore $_adell'array (vedi sotto) e alcuni clean up in modo che l'enorme $_xvariabile non rimanga nell'ambiente per il resto della sceneggiatura.
Noterai che la prima parte fino a set +xè racchiusa in un gruppo di comandi il cui stderr viene reindirizzato a /dev/null( {...} 2> /dev/null). Questo perché, se ad un certo punto dello script set -x(o set -o xtrace) viene eseguito, non vogliamo che quel preambolo generi tracce perché vogliamo renderlo il meno invasivo possibile. Quindi eseguiamo un set +x(dopo esserci assicurati di scaricare xtracepreventivamente le opzioni (incluse )) in cui le tracce vengono inviate a / dev / null.
Lo eval "$_X"stderr viene anche reindirizzato su / dev / null per ragioni simili, ma anche per evitare errori di scrittura nel tentativo di variabili speciali di sola lettura.
Andiamo avanti con la sceneggiatura:
alias skip=":||:<<'SWITCH_TO_USER $_u'"
Questo è il nostro trucco descritto sopra. L'invocazione iniziale dello script verrà annullata (vedi sotto).
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
Ora il wrapper alias attorno a SWITCH_TO_USER. Il motivo principale è riuscire a passare i parametri posizionali ( $1, $2...) al nuovo bashche interpreterà il resto dello script. Non abbiamo potuto farlo nella SWITCH_TO_USER funzione perché all'interno della funzione, "$@"sono gli argomenti alle funzioni, non quelli degli script. Il reindirizzamento stderr su / dev / null è di nuovo per nascondere xtraces e evaldeve aggirare un bug bash. Quindi chiamiamo la SWITCH_TO_USER funzione .
${_u+:} alias skip=:
Quella parte annulla l' skipalias (lo sostituisce con il :comando no-op) a meno che non $_usia impostata la variabile.
skip
Questo è il nostro skipalias. Alla prima invocazione, sarà solo :(la no-op). Su subsequence re-invocazioni, sarà qualcosa di simile a: :||: << 'SWITCH_TO_USER root'.
echo test
a=foo
set a b
SWITCH_TO_USER root
Quindi qui, ad esempio, a quel punto, riprendiamo lo script come rootutente e lo script ripristinerà lo stato salvato, salterà su quella SWITCH_TO_USER rootlinea e proseguirà.
Ciò significa che deve essere scritto esattamente come stat, con SWITCH_TO_USERall'inizio della riga e con esattamente uno spazio tra gli argomenti.
La maggior parte dello stato, stdin, stdout e stderr saranno conservati, ma non gli altri descrittori di file perché in sudogenere li chiudono a meno che non siano esplicitamente configurati per non. Quindi per esempio:
exec 3> some-file
SWITCH_TO_USER bob
echo test >&3
in genere non funzionerà.
Nota anche che se lo fai:
SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root
Funziona solo se hai il diritto di sudoas alicee aliceha il diritto di sudoas bobe bobas root.
Quindi, in pratica, questo non è davvero utile. Usare suinvece di sudo(o una sudoconfigurazione in cui sudoautentica l'utente target anziché il chiamante) potrebbe avere un po 'più senso, ma ciò significherebbe comunque che dovresti conoscere le password di tutti quei tipi.