Linux를 공부하면서 capabilities와 setuid에 대하여 보았습니다.
setuid(Set User ID)와 setgid(Set Group ID)는 Unix/Linux에서 파일 실행 시 권한을 변경하는 특별한 권한 비트입니다.
[user1@localhost ~]$ ls -l $(which sleep)
-rwxr-xr-x. 1 root root 69088 11월 7 2024 /usr/bin/sleep
[user1@localhost ~]$ cp /usr/bin/sleep ./mysleep
[user1@localhost ~]$ ls -l mysleep
-rwxr-xr-x. 1 user1 user1 69088 5월 24 17:32 mysleep
[user1@localhost ~]$ chmod +s mysleep
[user1@localhost ~]$ ls -l mysleep
-rwsr-sr-x. 1 user1 user1 69088 5월 24 17:32 mysleep
[user1@localhost ~]$ ./mysleep 100
user1 2291 1722 0 18:04 pts/0 00:00:00 ./mysleep 100
-> user1으로 실행되었다.
[user1@localhost ~]$ sudo chown root:root ./mysleep
[user1@localhost ~]$ sudo chmod +s mysleep
[user1@localhost ~]$ ./mysleep 100
[user1@localhost ~]$ ps -ef
root 2273 1722 0 18:01 pts/0 00:00:00 ./mysleep 100
-> root로 실행되었다.
권한 표시에서 주목할 점:
s: setuid 비트 (소유자 실행 권한 위치)s: setgid 비트 (그룹 실행 권한 위치)# passwd 명령어 - 일반 사용자가 비밀번호 변경 시 root 권한 필요
[user1@localhost ~]$ ls -l /usr/bin/passwd
-rwsr-xr-x. 1 root root 33600 4월 15 2024 /usr/bin/passwd
# sudo - 권한 상승 도구
[user1@localhost ~]$ ls -l /usr/bin/sudo
-rwsr-xr-x. 1 root root 143304 3월 20 2024 /usr/bin/sudo
위 setid의 경우는 root/non-root로 구분해서 사용하기 위한 방식으로 오래전 설계된 방식이였고 한계가 존재합니다.
이러한 root/non-root 이분법의 한계를 극복하기 위해 Linux는 capabilities 시스템을 도입했습니다.
Capabilities는 root 권한을 40개 이상의 세분화된 권한으로 나눈 시스템입니다:
[user1@localhost ~]$ sudo bash
[root@localhost user1]# ps
PID TTY TIME CMD
2161 pts/1 00:00:00 sudo
2163 pts/1 00:00:00 bash
2182 pts/1 00:00:00 ps
[root@localhost user1]# getpcaps 2161
2161: =ep
[root@localhost user1]# getpcaps 2163
2163: =ep
=ep의 의미:
=: 모든 capabilitiese: effective - 현재 활성화된 권한p: permitted - 허용된 권한i: inheritable - 상속 가능한 권한 (여기서는 없음)즉, =ep는 모든 capabilities가 effective와 permitted 상태로, 사실상 완전한 root 권한을 의미합니다.
과거 ping은 ICMP 패킷을 보내기 위해 원시 소켓(raw socket)이 필요했고, 이는 root 권한을 요구했습니다:
# 전통적 방식
[user1@localhost ~]$ ls -l /bin/ping
-rwsr-xr-x 1 root root 44168 May 7 23:51 /bin/ping
setuid 대신 CAP_NET_RAW capability만 부여하는 방식:
# setuid 제거 후 capability 설정
[user1@localhost ~]$ sudo chmod u-s /bin/ping
[user1@localhost ~]$ sudo setcap cap_net_raw+p /bin/ping
[user1@localhost ~]$ getcap /bin/ping
/bin/ping = cap_net_raw+p
이 방식의 장점:
Linux 3.0부터 도입된 혁신적 기능으로, 권한 없이도 ping이 가능합니다.
2011년에 추가된 SOCK_DGRAM + IPPROTO_ICMP 조합을 통해 일반 사용자도 ICMP Echo 패킷을 보낼 수 있게 되었습니다.
# 현대 ping의 소켓 생성 순서 (strace로 확인)
socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP) = 3 # 비특권 방식 시도
# 실패 시에만
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP) = 3 # 특권 방식 fallback
[user1@localhost ~]$ cp /usr/bin/ping ./myping
[user1@localhost ~]$ ls -l myping
-rwxr-xr-x. 1 user1 user1 70072 5월 24 17:36 myping
# setuid나 capability 없이도 작동!
[user1@localhost ~]$ ./myping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=58 time=10.2 ms
비특권 ICMP 소켓 사용 가능 여부는 sysctl 설정으로 제어됩니다:
[user1@localhost ~]$ sysctl net.ipv4.ping_group_range
net.ipv4.ping_group_range = 0 2147483647 # 모든 그룹 허용
0부터 2147483647의 그룹은 모두 사용할 수 있다(그냥 전체 허용)
# 또는
net.ipv4.ping_group_range = 1 0 # 비활성화 상태
[user1@localhost ~]$ sudo -l
user1 사용자는 localhost에서 다음 명령을 실행해야 합니다:
(ALL) NOPASSWD: ALL
이 설정의 의미:
매우 강력한 권한이지만 개발/테스트 환경에서는 편리합니다.
사용자 → setuid 바이너리 → 전체 root 권한 획득
사용자 → capability 설정된 바이너리 → 필요한 권한만 획득
ping의 사례에서 볼 수 있듯이, 같은 기능을 구현하는 방법이 시대에 따라 진화하고 있으며 이에 맞는 적절한 방법을 선택해야 합니다.
최소한의 권한으로 실행하도록 하는게 가장 좋을 것 같습니다.