Je možné spustit nový příkaz bez přístupu k síti jako uživatel bez root pomocí unshare -r -n
, například:
$ unshare -r -n ls
a.txt b.txt
Příkaz, který vyžaduje přístup k síti, předvídatelně selže.
$ unshare -r -n curl unix.stackexchange.com
curl: (6) Could not resolve host: unix.stackexchange.com
Zajímalo by mě, zda je možné odebrat přístup k síti pro aktuální proces, případně zápisem do magického souboru v /sys
nebo něco podobného.
Chtěl bych umět něco jako
$ /bin/sh -c 'echo 1 > /sys/unsharethis; curl unix.stackexchange.com'
Výňatek z strace
-ing unshare -r -n ls
zobrazuje unshare
systémové volání
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
Což mi naznačuje, že zrušení sdílení přístupu k síti z aktuálního procesu je ve skutečnosti jediný způsob, jak dosáhnout zrušení sdílení (tj. nemůže být předán jako argument pro spawn
nebo nějaký jeho ekvivalent). Také to naznačuje, že zrušení sdílení ze skriptu shellu by nefungovalo, pokud by shell nebyl speciálně rozšířen tak, aby odkryl obal kolem unshare
.
Přijatá odpověď:
To lze provést pomocí gdb
debugger a zda lze k běžícímu procesu připojit (nelze se připojit k programům, které mění svůj stav výpisu nebo mají setgid atd., pokud nejde o root).
Některé volitelné soubory mohou pomoci použít gdb jako ladicí symboly pro libc6 a několik začleněných souborů souvisejících s Linuxem pro pozdější získání skutečných hodnot několika symbolů (např. na Debianu:(možná) libc6-dbg
, libc6-dev
a linux-libc-dev
balíčky), ale jakmile je „recept“ vyroben, pravděpodobně již nejsou potřeba.
Nejprve co víc než unshare()
unshare -r
dělá? Bez toho nový uživatel zůstane u nobody
a nemůže ani psát jako jeho první uživatel:
$ 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
To bude použito později.
$ 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
$
Na jiném terminálu:
$ 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)
Nyní zavoláme první funkci:
(gdb) call unshare(CLONE_NEWNET|CLONE_NEWUSER)
No symbol "CLONE_NEWNET" in current context.
Dobře, možná existuje způsob, jak to gdb poznat, ale nejsem 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
U změněného procesu lze ověřit eth0
rozhraní zmizelo:
$ 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
Není cesty zpět:nový uživatelský jmenný prostor se nemůže změnit zpět na svůj původní jmenný prostor. Pokud proces běží s dostatečnými oprávněními (např. root bez ztracených schopností nebo SELinux), pak by to bylo možné (pouze pomocí unshare(CLONE_NEWNET)
/ setns(savedopenedfd)
).
Samozřejmě je možné jej naskriptovat do souboru a změnit jakýkoli povolený běžící proces nebo nechat shell sám změnit z podprocesu gdb. Obsah removenetwork.gdb
, zde platí pouze pro změnu procesu pomocí pid:gid
==1000:1000
:
AKTUALIZACE:přidán (přibližný) návratový typ pro systémová volání níže, to by mělo zabránit tomu, aby si některé verze gdb stěžovaly v prostředích bez vývojáře:
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
Příklad:
$ 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
AKTUALIZACE :pokud root vůbec není potřeba, jak se zdá u této otázky, pak není vůbec potřeba mapovat na root. Jednoduše nahraďte výskyty write($XX, "0 1000 1", 8)
pomocí write($XX, "1000 1000 1", 11)
(pro uid:gid
==1000:1000
případ). Doplňkové skupiny jsou stále nevyhnutelně ztraceny, ale uid/gid se nemění (je namapováno na sebe).