Post

[Linux] namespace

개요

Linux Kernel Exploit을 할 때 switch_task_namespaces(bpf_get_current_task(), &init_nsproxy) 함수를 통해서 네임스페이스를 변경하는 경우가 있어서 해당 포스터를 통해 개념을 정리하고자 합니다.

namespace

우선, namespace라는 개념은 프로세스 별 격리된 공간을 제공해주는 기능입니다. 이를 이용해서 Docker나 sandbox에서 사용하는 등 많은 곳에서 사용되게 됩니다. 따라서 아래와 같이 프로세스의 namespace를 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hogbal@ubuntu22:~$ sudo ls -al /proc/1/ns
total 0
dr-x--x--x 2 root root 0 Mar 29 06:05 .
dr-xr-xr-x 9 root root 0 Mar 29 06:04 ..
lrwxrwxrwx 1 root root 0 Mar 29 06:05 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Mar 29 06:05 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 net -> 'net:[4026531840]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 uts -> 'uts:[4026531838]'

위에서 출력되는 값처럼 namespace에는 많은 종류가 있습니다. 그 중 중요한 namespace는 아래와 같습니다.

  • PID: 프로세스의 ID를 격리하는 namespace

💡 PID namespace
새로운 namespace를 생성하게 되면 해당 프로세스는 1번부터 다시 시작하게 됩니다.

  • net: 프로세스의 네트워크 환경을 분리할 수 있는 namespace

💡 network namespace
network namepsace를 격리하는 순간 새로운 network에 있다고 생각하면 됩니다. 즉, IP도 달라지게 됩니다.

  • uts: 호스트 네임과 NIS 도메인 이름을 격리하는 namespace
  • user: 시큐리티와 관련된 식별자 및 속성을 격리하는 namespace

unshare

linux에서 unshare 명령어를 통해서 새로운 namespace를 생성할 수 있습니다. 해당 명령어의 옵션은 다음과 같습니다.

1
2
3
4
5
6
7
8
9
Options:
 -m, --mount[=<file>]      unshare mounts namespace
 -u, --uts[=<file>]        unshare UTS namespace (hostname etc)
 -i, --ipc[=<file>]        unshare System V IPC namespace
 -n, --net[=<file>]        unshare network namespace
 -p, --pid[=<file>]        unshare pid namespace
 -U, --user[=<file>]       unshare user namespace
 -C, --cgroup[=<file>]     unshare cgroup namespace
 -T, --time[=<file>]       unshare time namespace

해당 옵션들을 토대로 unshare -Urn 명령어를 실행한다면 아래와 같이 동작하게 됩니다.

1
2
3
4
5
6
7
hogbal@ubuntu22:~$ id
uid=1000(hogbal) gid=1000(hogbal) groups=1000(hogbal),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd)
hogbal@ubuntu22:~$ unshare -Urn
root@ubuntu22:~# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
root@ubuntu22:~# ifconfig
root@ubuntu22:~#

새로운 user, network namespace를 생성했기 때문에 바로 실행되는 bash 쉘은 pid 1번으로 root가 되고 ifconfig로 network를 확인해도 아무 출력값이 없는 것을 확인할 수 있습니다.

하지만, 아래와 같이 기존의 root와 namespace가 다른 것을 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
hogbal@ubuntu22:~$ sudo ls -al /proc/1/ns
total 0
dr-x--x--x 2 root root 0 Mar 29 06:05 .
dr-xr-xr-x 9 root root 0 Mar 29 06:04 ..
lrwxrwxrwx 1 root root 0 Mar 29 06:05 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Mar 29 06:05 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 net -> 'net:[4026531840]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Mar 29 06:07 uts -> 'uts:[4026531838]'
hogbal@ubuntu22:~$ unshare -Urn
root@ubuntu22:~# ls -al /proc/$$/ns
total 0
dr-x--x--x 2 root root 0 Mar 29 06:23 .
dr-xr-xr-x 9 root root 0 Mar 29 06:23 ..
lrwxrwxrwx 1 root root 0 Mar 29 06:23 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Mar 29 06:23 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Mar 29 06:23 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 Mar 29 06:23 net -> 'net:[4026532685]'
lrwxrwxrwx 1 root root 0 Mar 29 06:23 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Mar 29 06:23 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Mar 29 06:23 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Mar 29 06:23 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Mar 29 06:23 user -> 'user:[4026532684]'
lrwxrwxrwx 1 root root 0 Mar 29 06:23 uts -> 'uts:[4026531838]'

Linux Kernel Exploit

우선 Linux에서는 root 권한 하나로 모든 권한을 통제하기에 보안적인 문제가 발생할 수 있기 때문에 capabilities라는 개념을 통해서 권한을 여러개로 나뉘어 관리하게 됩니다. 아래는 대표적인 예시입니다.

  • CAP_NET_ADMIN: 네트워크 관리 기능을 수행할 수 있는 권한을 가집니다. 이 권한은 네트워크 인터페이스의 설정 변경, 라우팅 테이블 조작 등을 포함합니다.
  • CAP_SYS_ADMIN: 시스템 관리 기능을 수행할 수 있는 권한을 가집니다. 이 권한은 시스템 리소스 관리, 장치 관리, 파일 시스템 마운트 및 마운트 해제 등에 사용됩니다.
  • CAP_DAC_OVERRIDE: 파일과 디렉터리에 대한 DAC (Discretionary Access Control) 권한을 무시하고 액세스할 수 있는 권한을 가집니다. 이는 파일과 디렉터리에 대한 접근 제어를 우회할 수 있는 권한을 제공합니다.
  • CAP_SYS_PTRACE: 프로세스 추적 및 디버깅 기능을 사용할 수 있는 권한을 가집니다. 이는 다른 프로세스를 추적하고 해당 메모리를 검사할 수 있는 능력을 제공합니다.
  • CAP_SYS_RAWIO: 시스템의 I/O 포트 및 메모리에 직접 액세스할 수 있는 권한을 가집니다. 이는 하드웨어와의 직접적인 상호 작용이 필요한 경우에 사용됩니다.
  • CAP_SYS_CHROOT: chroot 시스템 호출을 사용하여 프로세스의 루트 디렉터리를 변경할 수 있는 권한을 가집니다. 이는 시스템 보안을 강화하기 위해 프로세스의 파일 시스템 루트를 제한하는 데 사용됩니다.

따라서, 이러한 권한을 사용하기 위해 namespace를 활용하게 됩니다. 만약, CAP_NET_ADMIN 권한을 요구하게 된다면 net namespace를 격리하게 되면 해당 프로세스는 CAP_NET_ADMIN 권한이 생기게 되므로 이를 이용하는 것입니다.

💡 이렇게 namespace를 격리하게 된다면 commit_creds 함수를 통해 실행해도 진짜 root 계정을 탈취하는 것이 아니기 때문에 switch_task_namespaces(bpf_get_current_task(), &init_nsproxy) 함수를 이용하여 격리 전의 namespace로 돌아가는 루틴을 실행하게 됩니다.

This post is licensed under CC BY 4.0 by the author.