È possibile eseguire un nuovo comando senza accesso alla rete come non root utilizzando unshare -r -n
, ad esempio:
$ unshare -r -n ls
a.txt b.txt
Un comando che richiede l'accesso alla rete fallirà in modo prevedibile.
$ unshare -r -n curl unix.stackexchange.com
curl: (6) Could not resolve host: unix.stackexchange.com
Mi chiedo se sia possibile rimuovere l'accesso alla rete per il processo corrente, potenzialmente scrivendo in un file magico in /sys
o qualcosa di simile.
Mi piacerebbe essere in grado di fare qualcosa come
$ /bin/sh -c 'echo 1 > /sys/unsharethis; curl unix.stackexchange.com'
Un estratto da strace
-ing unshare -r -n ls
mostra il unshare
chiamata di sistema
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=4759040, ...}) = 0
mmap(NULL, 4759040, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7ec6968000
close(3) = 0
unshare(CLONE_NEWUSER|CLONE_NEWNET) = 0
open("/proc/self/setgroups", O_WRONLY) = 3
write(3, "deny", 4) = 4
Il che mi suggerisce che annullare la condivisione dell'accesso alla rete dal processo corrente è in effetti l'unico modo per ottenere l'annullamento della condivisione (cioè non può essere passato come argomento per spawn
o un suo equivalente). Suggerisce inoltre che l'annullamento della condivisione da uno script di shell non funzionerebbe a meno che la shell non sia stata specificamente estesa per esporre un wrapper attorno a unshare
.
Risposta accettata:
Questo può essere fatto, più o meno, con il gdb
debugger e se è possibile allegare il processo in esecuzione (i programmi che alterano il loro stato di dumping, o sono setgid ecc. non possono essere collegati, a meno che non siano root).
Alcuni file opzionali possono aiutare a usare gdb come i simboli di debug per libc6, e alcuni file include relativi a Linux per ottenere i valori effettivi di alcuni simboli in un secondo momento (ad esempio su Debian:(possibilmente) libc6-dbg
, libc6-dev
e linux-libc-dev
pacchetti), ma in realtà una volta preparata la “ricetta”, probabilmente non servono più.
Innanzitutto cosa più di unshare()
unshare -r
sta facendo? Senza questo, il nuovo utente rimane su nobody
e non può nemmeno scrivere come utente iniziale:
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ strace unshare -r -n /bin/sleep 1 2>&1 |sed -n '/^unshare/,/^execve/p'
unshare(CLONE_NEWNET|CLONE_NEWUSER) = 0
open("/proc/self/setgroups", O_WRONLY) = 3
write(3, "deny", 4) = 4
close(3) = 0
open("/proc/self/uid_map", O_WRONLY) = 3
write(3, "0 1000 1", 8) = 8
close(3) = 0
open("/proc/self/gid_map", O_WRONLY) = 3
write(3, "0 1000 1", 8) = 8
close(3) = 0
execve("/bin/sleep", ["/bin/sleep", "1"], [/* 18 vars */]) = 0
Verrà utilizzato in seguito.
$ ip -4 -br a
lo UNKNOWN 127.0.0.1/8
[email protected] UP 10.0.3.66/24
$ ping -c1 10.0.3.1
PING 10.0.3.1 (10.0.3.1) 56(84) bytes of data.
64 bytes from 10.0.3.1: icmp_seq=1 ttl=64 time=0.167 ms
--- 10.0.3.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.167/0.167/0.167/0.000 ms
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ echo $$
338
$
Su un altro terminale:
$ gdb --pid=338
Reading symbols from /bin/bash...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libtinfo.so.5...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libdl.so.2...Reading symbols from /usr/lib/debug/.build-id/b8/95f0831f623c5f23603401d4069f9f94c24761.debug...done.
done.
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug/.build-id/aa/889e26a70f98fa8d230d088f7cc5bf43573163.debug...done.
done.
[…]
(gdb)
Ora chiamiamo la prima funzione:
(gdb) call unshare(CLONE_NEWNET|CLONE_NEWUSER)
No symbol "CLONE_NEWNET" in current context.
Ok, potrebbe esserci un metodo per farlo conoscere a gdb, ma non sono un guru:
(gdb) !
$ grep CLONE_NEW /usr/include/linux/sched.h # man 2 unshare
#define CLONE_NEWNS 0x00020000 /* New mount namespace group */
#define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */
#define CLONE_NEWUTS 0x04000000 /* New utsname namespace */
#define CLONE_NEWIPC 0x08000000 /* New ipc namespace */
#define CLONE_NEWUSER 0x10000000 /* New user namespace */
#define CLONE_NEWPID 0x20000000 /* New pid namespace */
#define CLONE_NEWNET 0x40000000 /* New network namespace */
$ find /usr/include/ -name fcntl.h |xargs grep O_WRONLY # man 2 open
/usr/include/asm-generic/fcntl.h:#define O_WRONLY 00000001
$ exit
exit
(gdb) call unshare(0x50000000)
$1 = 0
(gdb) call open("/proc/self/setgroups", 1)
$2 = 3
(gdb) call write($2,"deny",4)
$3 = 4
(gdb) call close($2)
$4 = 0
(gdb) call open("/proc/self/uid_map", 1)
$5 = 3
(gdb) call write($5, "0 1000 1", 8)
$6 = 8
(gdb) call close($5)
$7 = 0
(gdb) call open("/proc/self/gid_map", 1)
$8 = 3
(gdb) call write($8, "0 1000 1", 8)
$9 = 8
(gdb) call close($8)
$10 = 0
(gdb) quit
A debugging session is active.
Inferior 1 [process 338] will be detached.
Quit anyway? (y or n) y
Detaching from program: /bin/bash, process 338
Sul processo modificato, è possibile verificare eth0
interfaccia scomparsa:
$ ip -br a
lo DOWN 127.0.0.1/8
$ echo $$
338
$ id
uid=0(root) gid=0(root) groupes=0(root)
$ touch /
touch: setting times of '/': Permission denied
$ touch ~/test1
$ ls ~/test1
/home/user/test1
$ ping 10.0.3.1
connect: Network is unreachable
Non si torna indietro:il nuovo spazio dei nomi utente non può tornare al suo spazio dei nomi iniziale. Se il processo è in esecuzione con privilegi sufficienti (ad es. root senza capacità perse né SELinux), allora sarebbe possibile (usando solo unshare(CLONE_NEWNET)
/ setns(savedopenedfd)
).
Ovviamente è possibile scriverlo in un file e modificare qualsiasi processo in esecuzione consentito, oppure fare in modo che la shell si modifichi da un sottoprocesso gdb. Contenuto di removenetwork.gdb
, qui valido solo per modificare un processo con pid:gid
==1000:1000
:
AGGIORNAMENTO:aggiunto il tipo restituito (approssimativo) per le syscall di seguito, questo dovrebbe evitare che alcune versioni di gdb si lamentino in ambienti non di sviluppo:
call (int)unshare(0x50000000)
call (int)open("/proc/self/setgroups", 1)
call (long)write($2,"deny",4)
call (int)close($2)
call (int)open("/proc/self/uid_map", 1)
call (long)write($5, "0 1000 1", 8)
call (int)close($5)
call (int)open("/proc/self/gid_map", 1)
call (long)write($8, "0 1000 1", 8)
call (int)close($8)
quit
Esempio:
$ sh -c 'id; gdb --pid=$$ < removenetwork.gdb >/dev/null 2>&1; id; curl unix.stackexchange.com'
uid=1000(user) gid=1000(user) groups=1000(user)
uid=0(root) gid=0(root) groups=0(root)
curl: (6) Could not resolve host: unix.stackexchange.com
AGGIORNAMENTO :se root non è affatto necessario, come appare per questa domanda, non è affatto necessario eseguire il mapping a root. Sostituisci semplicemente le occorrenze di write($XX, "0 1000 1", 8)
con write($XX, "1000 1000 1", 11)
(per il uid:gid
==1000:1000
Astuccio). I gruppi supplementari sono ancora inevitabilmente persi, ma l'uid/gid non cambia (è mappato su se stesso).