Ver3.0 ๐ก๏ธ ๋ฆฌ๋ ์ค ๋ฅ๋ ฅ(Capabilities) ๊ด๋ฆฌ

๐ก๏ธ ๋ฆฌ๋ ์ค ๋ฅ๋ ฅ(Capabilities) ๊ด๋ฆฌ
์ด์์ฒด์ ๋ณด์์ ํต์ฌ, ์ธ๋ฐํ ๊ถํ ์ ์ด์ ์ธ๊ณ๋ก!
๐ฏ ๋ฆฌ๋ ์ค Capabilities, ์ ์์์ผ ํ ๊น?
์ฌ๋ฌ๋ถ, ๋ฆฌ๋
์ค ์์คํ
์์ ํ๋ก๊ทธ๋จ์ ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์ด๋ฐ ๊ณ ๋ฏผ ํ ๋ฒ์ฏค ํด๋ณด์
จ์ ๊ฑฐ์์. "์ด ํ๋ก๊ทธ๋จ์ด 80๋ฒ ํฌํธ๋ฅผ ์ด์ด์ผ ํ๋๋ฐ, root ๊ถํ์ ์ค์ผ ํ๋?" ๐ค
์ ํต์ ์ผ๋ก ๋ฆฌ๋
์ค๋ ๊ถํ์ ๋ฑ ๋ ๊ฐ์ง๋ก ๋๋ด์ด์. ์ผ๋ฐ ์ฌ์ฉ์(unprivileged)์ ์ํผ์ ์ (root). ๋ง์น "ํ๋ฏผ ์๋๋ฉด ์" ๊ฐ์ ๊ทน๋จ์ ์ธ ๊ตฌ์กฐ์์ฃ . ๊ทผ๋ฐ ์๊ฐํด๋ณด์ธ์. ๋จ์ง ๋คํธ์ํฌ ํฌํธ ํ๋ ์ด๋ ค๊ณ ํ๋ก๊ทธ๋จ์ root ๊ถํ์ ์ฃผ๋ ๊ฑด ๋ง์น ํธ์์ ๊ฐ๋๋ฐ Ferrari๋ฅผ ๋ชฐ๊ณ ๊ฐ๋ ๊ฒ๊ณผ ๊ฐ์์. ์์ ์ค๋ฒ์ฃ ! ๐
๋ฐ๋ก ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ฑ์ฅํ ๊ฒ Capabilities์
๋๋ค. root์ ๋ง๊ฐํ ๊ถํ์ ์๊ฒ ์ชผ๊ฐ์, ํ์ํ ๊ถํ๋ง ๋ฑ ์ค ์ ์๊ฒ ๋ง๋ ๊ฑฐ์์. ๋ณด์ ์ธก๋ฉด์์ ์์ฒญ๋ ํ์ ์ด์์ฃ !
๐ Capabilities์ ์ญ์ฌ์ ๋ฐฐ๊ฒฝ
Capabilities๋ POSIX 1003.1e ์ด์์์ ์ฒ์ ์ ์๋์์ด์. ๋น๋ก ์ด ํ์ค์ ๊ณต์์ ์ผ๋ก ์ฑํ๋์ง ์์์ง๋ง, ๋ฆฌ๋
์ค ์ปค๋ 2.2 ๋ฒ์ ๋ถํฐ ๋ถ๋ถ์ ์ผ๋ก ๊ตฌํ๋๊ธฐ ์์ํ์ฃ . ๊ทธ๋ฆฌ๊ณ ์ปค๋ 2.6.24 ๋ฒ์ ์์ ํ์ผ ๊ธฐ๋ฐ capabilities๊ฐ ์ถ๊ฐ๋๋ฉด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ค์ฉํ๋์์ด์! ๐
์ ์ด๋ฐ ์์คํ
์ด ํ์ํ์๊น์? ์ ํต์ ์ธ Unix ์์คํ
์์๋ ํน์ ์์
(privileged operations)์ ์ํํ๋ ค๋ฉด ๋ฌด์กฐ๊ฑด root ๊ถํ์ด ํ์ํ์ด์. ์๋ฅผ ๋ค์ด:
โข 1024๋ฒ ์ดํ์ ํฌํธ ๋ฐ์ธ๋ฉ ๐
โข ์์คํ
์๊ฐ ๋ณ๊ฒฝ โฐ
โข ๋ค๋ฅธ ์ฌ์ฉ์์ ํ๋ก์ธ์ค ์ข
๋ฃ โก
โข ํ์ผ ์์ ๊ถ ๋ณ๊ฒฝ ๐
โข ๋คํธ์ํฌ ์ธํฐํ์ด์ค ์ค์ ๐
์ด๋ฐ ์์
๋ค์ ์ํด ํ๋ก๊ทธ๋จ์ setuid root ๋นํธ๋ฅผ ์ค์ ํ๋ฉด, ๊ทธ ํ๋ก๊ทธ๋จ์ ์คํ๋๋ ๋์ ์์ ํ root ๊ถํ์ ๊ฐ๊ฒ ๋ผ์. ๋ณด์ ๊ด์ ์์ ๋ณด๋ฉด ์ ๋ง ์ํํ ์ผ์ด์ฃ ! ๋ง์ฝ ๊ทธ ํ๋ก๊ทธ๋จ์ ๋ฒ๊ทธ๋ ์ทจ์ฝ์ ์ด ์๋ค๋ฉด? ๊ณต๊ฒฉ์๊ฐ ์์คํ
์ ์ฒด๋ฅผ ์ฅ์
ํ ์ ์๋ ๊ฑฐ์์. ๐ฑ
๐ Capabilities์ ๊ตฌ์กฐ์ ์ข ๋ฅ
๋ฆฌ๋ ์ค capabilities๋ root ๊ถํ์ ์ฝ 40์ฌ ๊ฐ์ ๋ ๋ฆฝ์ ์ธ ๊ถํ์ผ๋ก ๋๋ ์. ๊ฐ capability๋ CAP_๋ก ์์ํ๋ ์ด๋ฆ์ ๊ฐ์ง๊ณ ์์ฃ . ์ฃผ์ capabilities๋ฅผ ์ดํด๋ณผ๊น์?
๐ ๋คํธ์ํฌ ๊ด๋ จ Capabilities
1024๋ฒ ์ดํ์ ํน๊ถ ํฌํธ(privileged port)์ ๋ฐ์ธ๋ฉํ ์ ์๋ ๊ถํ์ด์์. ์น ์๋ฒ๋ ๋ฉ์ผ ์๋ฒ์ฒ๋ผ 80๋ฒ, 443๋ฒ ํฌํธ๋ฅผ ์ฌ์ฉํ๋ ํ๋ก๊ทธ๋จ์ ํ์์ ์ด์ฃ !
CAP_NET_ADMIN
๋คํธ์ํฌ ์ธํฐํ์ด์ค ์ค์ , ๋ผ์ฐํ ํ ์ด๋ธ ์์ , ๋ฐฉํ๋ฒฝ ๊ท์น ์ค์ ๋ฑ ๋คํธ์ํฌ ๊ด๋ฆฌ ์์ ์ ์ํํ ์ ์์ด์. ๋คํธ์ํฌ ๊ด๋ฆฌ ๋๊ตฌ์ ํ์ํ ๊ถํ์ด์์.
CAP_NET_RAW
RAW ์์ผ๊ณผ PACKET ์์ผ์ ์ฌ์ฉํ ์ ์๋ ๊ถํ์ด์์. ping ๋ช ๋ น์ด๋ ๋คํธ์ํฌ ์ค๋ํผ ๊ฐ์ ๋๊ตฌ์ ํ์ํ์ฃ . ๐ง
๐พ ํ์ผ ์์คํ ๊ด๋ จ Capabilities
ํ์ผ์ ์ฝ๊ธฐ, ์ฐ๊ธฐ, ์คํ ๊ถํ ๊ฒ์ฌ๋ฅผ ๋ฌด์ํ ์ ์์ด์. DAC๋ Discretionary Access Control์ ์ฝ์์์. ๋ฐฑ์ ํ๋ก๊ทธ๋จ ๊ฐ์ ๊ณณ์์ ์ ์ฉํ๊ฒ ์ฐ์ด์ฃ .
CAP_DAC_READ_SEARCH
ํ์ผ ์ฝ๊ธฐ์ ๋๋ ํ ๋ฆฌ ๊ฒ์ ๊ถํ ๊ฒ์ฌ๋ฅผ ๋ฌด์ํ ์ ์์ด์. CAP_DAC_OVERRIDE๋ณด๋ค๋ ์ ํ์ ์ธ ๊ถํ์ด์์.
CAP_CHOWN
ํ์ผ์ ์์ ์(UID)์ ๊ทธ๋ฃน(GID)์ ์์๋ก ๋ณ๊ฒฝํ ์ ์๋ ๊ถํ์ด์์. ํ์ผ ๊ด๋ฆฌ ๋๊ตฌ์ ํ์ํ์ฃ .
CAP_FOWNER
ํ์ผ ์์ ์๊ฐ ์๋์ด๋ ํ์ผ์ ๊ถํ, ํ์์คํฌํ ๋ฑ์ ์์ ํ ์ ์์ด์. ๐
โ๏ธ ์์คํ ๊ด๋ฆฌ ๊ด๋ จ Capabilities
๋ค์ํ ์์คํ ๊ด๋ฆฌ ์์ ์ ์ํํ ์ ์๋ ๊ถํ์ด์์. ๋ง์ดํธ, ์ค์ ์ค์ , ํธ์คํธ๋ช ๋ณ๊ฒฝ ๋ฑ ์ ๋ง ๋ง์ ์์ ์ด ํฌํจ๋์ด ์์ด์ "๋ง๋ฅ capability"๋ผ๊ณ ๋ถ๋ฆฌ๊ธฐ๋ ํด์. ํ์ง๋ง ๊ทธ๋งํผ ์กฐ์ฌํด์ ์ฌ์ฉํด์ผ ํ์ฃ ! โก
CAP_SYS_TIME
์์คํ ์๊ฐ๊ณผ ํ๋์จ์ด ํด๋ญ์ ์ค์ ํ ์ ์์ด์. NTP ๋ฐ๋ชฌ ๊ฐ์ ์๊ฐ ๋๊ธฐํ ํ๋ก๊ทธ๋จ์ ํ์ํด์.
CAP_SYS_BOOT
์์คํ ์ ์ฌ๋ถํ ํ ์ ์๋ ๊ถํ์ด์์. reboot() ์์คํ ์ฝ์ ์ฌ์ฉํ ์ ์์ฃ .
CAP_SYS_MODULE
์ปค๋ ๋ชจ๋์ ๋ก๋ํ๊ณ ์ธ๋ก๋ํ ์ ์์ด์. ์์คํ ๋ณด์์ ๋งค์ฐ ์ค์ํ ๊ถํ์ด๋ผ ์ ์คํ๊ฒ ๋ค๋ค์ผ ํด์! ๐
๐ค ํ๋ก์ธ์ค ๊ด๋ จ Capabilities
๋ค๋ฅธ ์ฌ์ฉ์์ ํ๋ก์ธ์ค์ ์๊ทธ๋์ ๋ณด๋ผ ์ ์์ด์. ํ๋ก์ธ์ค ๊ด๋ฆฌ ๋๊ตฌ์ ํ์ํ ๊ถํ์ด์ฃ .
CAP_SETUID / CAP_SETGID
ํ๋ก์ธ์ค์ UID์ GID๋ฅผ ์์๋ก ๋ณ๊ฒฝํ ์ ์์ด์. ์ฌ์ฉ์ ์ ํ์ด ํ์ํ ๋ฐ๋ชฌ ํ๋ก๊ทธ๋จ์์ ์ฌ์ฉ๋ผ์.
CAP_SYS_PTRACE
๋ค๋ฅธ ํ๋ก์ธ์ค๋ฅผ ptrace()๋ก ์ถ์ ํ๊ณ ๋๋ฒ๊น ํ ์ ์์ด์. ๋๋ฒ๊ฑฐ๋ ํ๋กํ์ผ๋ง ๋๊ตฌ์ ํ์ํ์ฃ . ๐
CAP_SYS_NICE
ํ๋ก์ธ์ค์ ์ฐ์ ์์(nice value)๋ฅผ ๋ณ๊ฒฝํ๊ณ , ์ค์๊ฐ ์ค์ผ์ค๋ง ์ ์ฑ ์ ์ค์ ํ ์ ์์ด์.
๐ ๋ณด์ ๊ด๋ จ Capabilities
๋ค๋ฅธ ํ๋ก์ธ์ค์ capabilities๋ฅผ ๋ณ๊ฒฝํ ์ ์์ด์. ๋งค์ฐ ๊ฐ๋ ฅํ ๊ถํ์ด๋ผ ์กฐ์ฌํด์ผ ํด์!
CAP_SETFCAP
ํ์ผ์ capabilities๋ฅผ ์ค์ ํ ์ ์๋ ๊ถํ์ด์์. ์์คํ ๊ด๋ฆฌ์์๊ฒ ํ์ํ ๊ถํ์ด์ฃ .
CAP_SYS_CHROOT
chroot() ์์คํ ์ฝ์ ์ฌ์ฉํด์ ๋ฃจํธ ๋๋ ํ ๋ฆฌ๋ฅผ ๋ณ๊ฒฝํ ์ ์์ด์. ์๋๋ฐ์ค ํ๊ฒฝ ๊ตฌ์ถ์ ์ ์ฉํด์. ๐ฆ
๐ญ Capabilities์ ์ธ ๊ฐ์ง ์ธํธ
๊ฐ ํ๋ก์ธ์ค์ ํ์ผ์ capabilities๋ฅผ ์ธ ๊ฐ์ง ์ธํธ๋ก ๊ด๋ฆฌํด์. ์ด๊ฒ ์ข ๋ณต์กํด ๋ณด์ผ ์ ์๋๋ฐ, ์ฐจ๊ทผ์ฐจ๊ทผ ์ค๋ช ํด๋๋ฆด๊ฒ์! ๐
๐ ํ๋ก์ธ์ค Capability ์ธํธ
ํ๋ก์ธ์ค๊ฐ ์ฌ์ฉํ ์ ์๋ capabilities์ ์ํ์ ์ด์์. ์ด ์ธํธ์ ์๋ capability๋ง effective๋ inheritable ์ธํธ๋ก ์ฎ๊ธธ ์ ์์ฃ . ๋ง์น "ํ์ฉ๋ ์ต๋ ๊ถํ"์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ผ์.
2. Effective (E)
ํ์ฌ ์ค์ ๋ก ์ฌ์ฉ ์ค์ธ capabilities์์. ์ปค๋์ ๊ถํ ๊ฒ์ฌ๋ฅผ ํ ๋ ์ด ์ธํธ๋ฅผ ํ์ธํด์. ํ๋ก๊ทธ๋จ์ด ํน์ ์์ ์ ํ๋ ค๋ฉด ํด๋น capability๊ฐ effective ์ธํธ์ ์์ด์ผ ํ์ฃ ! โ
3. Inheritable (I)
execve() ์์คํ ์ฝ๋ก ์ ํ๋ก๊ทธ๋จ์ ์คํํ ๋ ์์๋ ์ ์๋ capabilities์์. ํ์ง๋ง ์๋์ผ๋ก ์์๋๋ ๊ฑด ์๋๊ณ , ์คํ๋๋ ํ์ผ์ inheritable ์ธํธ์ AND ์ฐ์ฐ์ ๊ฑฐ์ณ์.
4. Bounding (B)
ํ๋ก์ธ์ค๊ฐ ํ๋ํ ์ ์๋ capabilities์ ์ ํ ์ธํธ์์. ์ด ์ธํธ์ ์๋ capability๋ ์ ๋ ์ป์ ์ ์์ด์. ๋ณด์ ๊ฐํ๋ฅผ ์ํ ์ถ๊ฐ ์ ์ฝ์ด์ฃ ! ๐ก๏ธ
5. Ambient (A)
์ปค๋ 4.3๋ถํฐ ์ถ๊ฐ๋ ์ธํธ์์. ๋นํน๊ถ ํ๋ก๊ทธ๋จ์ ์คํํ ๋๋ capabilities๋ฅผ ์ ์งํ ์ ์๊ฒ ํด์ค์. ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌํ์ง๋ง ๋ณด์์ ์ฃผ์ํด์ผ ํด์!
๐ ํ์ผ Capability ์ธํธ
ํ์ผ์ด ์คํ๋ ๋ ํ๋ก์ธ์ค์ permitted ์ธํธ์ ์๋์ผ๋ก ์ถ๊ฐ๋๋ capabilities์์.
2. Inheritable (ํ์ผ)
ํ๋ก์ธ์ค์ inheritable ์ธํธ์ AND ์ฐ์ฐ๋์ด ์ ํ๋ก์ธ์ค์ permitted ์ธํธ์ ์ถ๊ฐ๋ผ์.
3. Effective (ํ์ผ)
ํ์ผ์ ๊ฒฝ์ฐ ์ด๊ฑด ๋จ์ํ ๋นํธ ํ๋๊ทธ์์. ์ค์ ๋์ด ์์ผ๋ฉด ์คํ ์ permitted ์ธํธ์ ๋ชจ๋ capability๊ฐ effective ์ธํธ๋ก ์๋ ํ์ฑํ๋ผ์! ๐
๐ ๏ธ Capabilities ์ค์ ํ์ฉ๋ฒ
์ด์ ์ค์ ๋ก capabilities๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์์๋ณผ๊น์? ๋ฆฌ๋ ์ค์์๋ ์ฌ๋ฌ ๋๊ตฌ๋ฅผ ์ ๊ณตํ๊ณ ์์ด์!
๐ง getcap๊ณผ setcap ๋ช ๋ น์ด
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋๊ตฌ๋ getcap๊ณผ setcap์ด์์. ํ์ผ์ capabilities๋ฅผ ํ์ธํ๊ณ ์ค์ ํ๋ ๋ช ๋ น์ด์ฃ .
$ getcap /usr/bin/ping
/usr/bin/ping = cap_net_raw+ep
์ด ์ถ๋ ฅ์ ping ํ๋ก๊ทธ๋จ์ด CAP_NET_RAW capability๋ฅผ ๊ฐ์ง๊ณ ์๊ณ , effective์ permitted ์ธํธ์ ์ค์ ๋์ด ์๋ค๋ ๋ป์ด์์. +ep๊ฐ ๋ฐ๋ก ๊ทธ ์๋ฏธ์ฃ !ํ์ผ์ capabilities ์ค์ ํ๊ธฐ:
$ sudo setcap 'cap_net_bind_service=+ep' /path/to/myserver
$ getcap /path/to/myserver
/path/to/myserver = cap_net_bind_service+ep
์ด์ myserver ํ๋ก๊ทธ๋จ์ root ๊ถํ ์์ด๋ 80๋ฒ ํฌํธ๋ฅผ ์ฌ์ฉํ ์ ์์ด์! ๐capabilities ์ ๊ฑฐํ๊ธฐ:
$ sudo setcap -r /path/to/myserver
๐ capsh - Capability Shell
capsh๋ capabilities๋ฅผ ํ ์คํธํ๊ณ ๋๋ฒ๊น ํ๋ ๋ฐ ์ ์ฉํ ๋๊ตฌ์์.
$ capsh --print
Current: =
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,...
Ambient set =
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=1000(user)
gid=1000(user)
groups=1000(user),...
ํน์ capability๋ก ํ๋ก๊ทธ๋จ ์คํ:$ sudo capsh --caps='cap_net_raw+eip cap_setuid,cap_setgid+ep' \
--keep=1 --user=nobody --addamb=cap_net_raw -- -c /path/to/program
์ด ๋ช
๋ น์ nobody ์ฌ์ฉ์๋ก ํ๋ก๊ทธ๋จ์ ์คํํ๋ฉด์ CAP_NET_RAW capability๋ฅผ ์ ์งํด์. ํ
์คํธ ํ๊ฒฝ ๊ตฌ์ถ์ ์ ๋ง ์ ์ฉํ์ฃ ! ๐งช
๐ป ํ๋ก๊ทธ๋๋ฐ์ผ๋ก Capabilities ๋ค๋ฃจ๊ธฐ
C ํ๋ก๊ทธ๋จ์์ ์ง์ capabilities๋ฅผ ์ ์ดํ ์๋ ์์ด์. libcap ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ผ์!
#include <sys/capability.h>
#include <stdio.h>
int main() {
cap_t caps;
char *caps_text;
// ํ์ฌ ํ๋ก์ธ์ค์ capabilities ๊ฐ์ ธ์ค๊ธฐ
caps = cap_get_proc();
if (caps == NULL) {
perror("cap_get_proc");
return 1;
}
// ํ
์คํธ ํ์์ผ๋ก ๋ณํ
caps_text = cap_to_text(caps, NULL);
printf("Current capabilities: %s\n", caps_text);
// ๋ฉ๋ชจ๋ฆฌ ํด์
cap_free(caps_text);
cap_free(caps);
return 0;
}
์ปดํ์ผ:$ gcc -o check_caps check_caps.c -lcap
#include <sys/capability.h>
#include <sys/prctl.h>
#include <stdio.h>
#include <stdlib.h>
int drop_all_caps_except(cap_value_t keep_cap) {
cap_t caps;
cap_value_t cap_list[1];
// ๋น capability ์ธํธ ์์ฑ
caps = cap_init();
if (caps == NULL) {
perror("cap_init");
return -1;
}
// ์ ์งํ capability ์ค์
cap_list[0] = keep_cap;
if (cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_SET) == -1 ||
cap_set_flag(caps, CAP_PERMITTED, 1, cap_list, CAP_SET) == -1) {
perror("cap_set_flag");
cap_free(caps);
return -1;
}
// ํ๋ก์ธ์ค์ ์ ์ฉ
if (cap_set_proc(caps) == -1) {
perror("cap_set_proc");
cap_free(caps);
return -1;
}
cap_free(caps);
return 0;
}
int main() {
// CAP_NET_BIND_SERVICE๋ง ์ ์งํ๊ณ ๋๋จธ์ง ์ ๊ฑฐ
if (drop_all_caps_except(CAP_NET_BIND_SERVICE) == -1) {
fprintf(stderr, "Failed to drop capabilities\n");
return 1;
}
printf("Successfully dropped all capabilities except CAP_NET_BIND_SERVICE\n");
// ์ฌ๊ธฐ์ 80๋ฒ ํฌํธ ๋ฐ์ธ๋ฉ ๋ฑ์ ์์
์ํ
// ...
return 0;
}
์ด ์ฝ๋๋ ํ๋ก๊ทธ๋จ์ด ์์ํ ๋ ๋ถํ์ํ ๊ถํ์ ๋ชจ๋ ๋ฒ๋ฆฌ๊ณ ํ์ํ ๊ฒ๋ง ์ ์งํ๋ ํจํด์ด์์. ๋ณด์ ๊ฐํ์ ํต์ฌ ๊ธฐ๋ฒ์ด์ฃ ! ๐
๐ ์ค์ ์๋๋ฆฌ์ค์ ์์
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ์ํฉ์์ ์ด๋ป๊ฒ ํ์ฉํ๋์ง ์ดํด๋ณผ๊น์? ์ค๋ฌด์์ ์์ฃผ ๋ง์ฃผ์น๋ ์๋๋ฆฌ์ค๋ค์ด์์! ๐ผ
๐ ์๋๋ฆฌ์ค 1: ์น ์๋ฒ๋ฅผ ์์ ํ๊ฒ ์คํํ๊ธฐ
์น ์๋ฒ๋ 80๋ฒ ํฌํธ๋ฅผ ์ฌ์ฉํด์ผ ํ๋๋ฐ, ์ ํต์ ์ผ๋ก๋ root๋ก ์คํํด์ผ ํ์ด์. ํ์ง๋ง capabilities๋ฅผ ์ฌ์ฉํ๋ฉด ํจ์ฌ ์์ ํ๊ฒ ํ ์ ์์ฃ !
Node.js๋ก ๋ง๋ ์น ์๋ฒ๋ฅผ 80๋ฒ ํฌํธ์์ ์คํํ๊ณ ์ถ์๋ฐ, root ๊ถํ์ ์ฃผ๊ณ ์ถ์ง ์์์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# 1. Node.js ๋ฐ์ด๋๋ฆฌ์ capability ๋ถ์ฌ
$ sudo setcap 'cap_net_bind_service=+ep' /usr/bin/node
# 2. ์ผ๋ฐ ์ฌ์ฉ์๋ก ์๋ฒ ์คํ
$ node server.js # 80๋ฒ ํฌํธ ์ฌ์ฉ ๊ฐ๋ฅ!
# 3. ํ์ธ
$ getcap /usr/bin/node
/usr/bin/node = cap_net_bind_service+ep
Node.js ์๋ฒ ์ฝ๋ ์์ :const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello from port 80 without root!\n');
});
// 80๋ฒ ํฌํธ์์ ๋ฆฌ์ค๋ (root ๊ถํ ๋ถํ์!)
server.listen(80, () => {
console.log('Server running on port 80');
// ๋ณด์ ๊ฐํ: ์๋ฒ ์์ ํ capability ์ ๊ฑฐ
try {
process.setuid('nobody');
console.log('Dropped privileges to nobody');
} catch (err) {
console.error('Failed to drop privileges:', err);
process.exit(1);
}
});
๐ ์๋๋ฆฌ์ค 2: ๋คํธ์ํฌ ๋ชจ๋ํฐ๋ง ๋๊ตฌ
ํจํท์ ์บก์ฒํ๋ ๋๊ตฌ๋ฅผ ๋ง๋ค ๋๋ capabilities๊ฐ ์ ์ฉํด์!
๋คํธ์ํฌ ํจํท์ ์บก์ฒํ๋ Python ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ค์๋๋ฐ, root ๊ถํ์ด ํ์ํด์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# Python ์ธํฐํ๋ฆฌํฐ์ capability ๋ถ์ฌ
$ sudo setcap 'cap_net_raw,cap_net_admin=+ep' /usr/bin/python3.9
# ๋๋ ํน์ ์คํฌ๋ฆฝํธ๋ฅผ ๋ฐ์ด๋๋ฆฌ๋ก ๋ง๋ค์ด์ ์ค์
$ pip install pyinstaller
$ pyinstaller --onefile packet_sniffer.py
$ sudo setcap 'cap_net_raw=+ep' dist/packet_sniffer
Python ํจํท ์ค๋ํผ ์์ :#!/usr/bin/env python3
import socket
import struct
import sys
def packet_sniffer():
try:
# RAW ์์ผ ์์ฑ (CAP_NET_RAW ํ์)
sock = socket.socket(socket.AF_PACKET,
socket.SOCK_RAW,
socket.ntohs(3))
except PermissionError:
print("Error: CAP_NET_RAW capability required!")
print("Run: sudo setcap 'cap_net_raw=+ep' /path/to/this/script")
sys.exit(1)
print("Packet sniffer started (Ctrl+C to stop)...")
try:
while True:
raw_data, addr = sock.recvfrom(65535)
# Ethernet ํค๋ ํ์ฑ
dest_mac, src_mac, eth_proto = struct.unpack('! 6s 6s H', raw_data[:14])
print(f"Packet from {format_mac(src_mac)} to {format_mac(dest_mac)}")
except KeyboardInterrupt:
print("\nStopping sniffer...")
sock.close()
def format_mac(mac_bytes):
return ':'.join(f'{b:02x}' for b in mac_bytes)
if __name__ == '__main__':
packet_sniffer()
โฐ ์๋๋ฆฌ์ค 3: ์๊ฐ ๋๊ธฐํ ๋ฐ๋ชฌ
์์คํ ์๊ฐ์ ์กฐ์ ํ๋ ํ๋ก๊ทธ๋จ์ ๋ง๋ค์ด๋ณผ๊น์?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/capability.h>
#include <unistd.h>
int set_system_time(time_t new_time) {
struct timeval tv;
tv.tv_sec = new_time;
tv.tv_usec = 0;
// CAP_SYS_TIME์ด ํ์ํ ์์
if (settimeofday(&tv, NULL) == -1) {
perror("settimeofday");
return -1;
}
return 0;
}
int drop_capabilities() {
cap_t caps = cap_init();
if (caps == NULL) {
perror("cap_init");
return -1;
}
if (cap_set_proc(caps) == -1) {
perror("cap_set_proc");
cap_free(caps);
return -1;
}
cap_free(caps);
return 0;
}
int main() {
printf("Time sync daemon starting...\n");
// ์๊ฐ ์ค์ (CAP_SYS_TIME ์ฌ์ฉ)
time_t current_time = time(NULL);
printf("Current time: %s", ctime(¤t_time));
// ์์ : 1์ด ์ถ๊ฐ (์ค์ ๋ก๋ NTP ์๋ฒ์์ ๊ฐ์ ธ์ด)
if (set_system_time(current_time + 1) == 0) {
printf("Time adjusted successfully\n");
}
// ์์
์๋ฃ ํ ๋ชจ๋ capabilities ์ ๊ฑฐ
printf("Dropping all capabilities...\n");
if (drop_capabilities() == 0) {
printf("Capabilities dropped successfully\n");
}
// ์ด์ ์ผ๋ฐ ์ฌ์ฉ์ ๊ถํ์ผ๋ก ๊ณ์ ์คํ
printf("Running with minimal privileges\n");
return 0;
}
์ปดํ์ผ ๋ฐ ์ค์ :$ gcc -o time_sync time_sync.c -lcap
$ sudo setcap 'cap_sys_time=+ep' ./time_sync
$ ./time_sync # root ๊ถํ ์์ด ์คํ!
๐ณ ์๋๋ฆฌ์ค 4: ์ปจํ ์ด๋ ํ๊ฒฝ์์์ Capabilities
Docker ๊ฐ์ ์ปจํ ์ด๋ ํ๊ฒฝ์์๋ capabilities๋ฅผ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์์ด์!
# ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ capability ์ ๊ฑฐํ๊ณ ํ์ํ ๊ฒ๋ง ์ถ๊ฐ
$ docker run --rm -it \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--cap-add=NET_RAW \
ubuntu:latest /bin/bash
# ์ปจํ
์ด๋ ๋ด๋ถ์์ ํ์ธ
root@container:/# capsh --print
Dockerfile์์ ์ค์ :FROM ubuntu:20.04
RUN apt-get update && apt-get install -y \
libcap2-bin \
python3 \
&& rm -rf /var/lib/apt/lists/*
COPY myapp.py /app/myapp.py
RUN chmod +x /app/myapp.py
# Python์ capability ๋ถ์ฌ
RUN setcap 'cap_net_bind_service=+ep' /usr/bin/python3.8
USER nobody
WORKDIR /app
CMD ["python3", "myapp.py"]
docker-compose.yml ์์ :version: '3.8'
services:
webserver:
image: mywebserver:latest
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
ports:
- "80:80"
user: "1000:1000" # ๋นroot ์ฌ์ฉ์๋ก ์คํ
security_opt:
- no-new-privileges:true
๐ ๋ณด์ ๊ณ ๋ ค์ฌํญ๊ณผ ํจ์
Capabilities๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ง๋ง, ์๋ชป ์ฌ์ฉํ๋ฉด ์คํ๋ ค ๋ณด์ ์ํ์ด ๋ ์ ์์ด์. ์ฃผ์ํด์ผ ํ ์ ๋ค์ ์์๋ณผ๊น์? ๐ฐ
โ ๏ธ ์ผ๋ฐ์ ์ธ ์ค์๋ค
CAP_SYS_ADMIN์ "๋ง๋ฅ capability"๋ผ๊ณ ๋ถ๋ฆด ์ ๋๋ก ๋ง์ ๊ถํ์ ํฌํจํด์. ์ด๊ฑธ ๋ถ์ฌํ๋ ๊ฑด ๊ฑฐ์ root ๊ถํ์ ์ฃผ๋ ๊ฒ๊ณผ ๋น์ทํด์! ๐ฑ
๋์ ์:
$ sudo setcap 'cap_sys_admin=+ep' /usr/bin/myapp
์ข์ ์:์ ํํ ์ด๋ค ์์ ์ด ํ์ํ์ง ํ์ ํ๊ณ , ๋ ๊ตฌ์ฒด์ ์ธ capability๋ฅผ ์ฐพ์๋ณด์ธ์. ์๋ฅผ ๋ค์ด ๋ง์ดํธ ์์ ์ด ํ์ํ๋ค๋ฉด CAP_SYS_ADMIN ๋์ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
์ผ๋ฐ์ ์ธ cp ๋ช ๋ น์ด๋ extended attributes๋ฅผ ๋ณต์ฌํ์ง ์์์!
๋ฌธ์ ๊ฐ ๋๋ ๊ฒฝ์ฐ:
$ cp /usr/bin/ping /tmp/ping
$ getcap /tmp/ping
# ์๋ฌด๊ฒ๋ ์ถ๋ ฅ๋์ง ์์ - capabilities๊ฐ ์ฌ๋ผ์ง!
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ:$ cp -a /usr/bin/ping /tmp/ping # ๋๋
$ cp --preserve=all /usr/bin/ping /tmp/ping
Ambient capabilities๋ ํธ๋ฆฌํ์ง๋ง, ์์ ํ๋ก์ธ์ค์ ์๋์ผ๋ก ์ ํ๋๊ธฐ ๋๋ฌธ์ ์ฃผ์ํด์ผ ํด์!
// ์ํํ ์ฝ๋
prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_RAW, 0, 0);
system("/bin/bash"); // bash๋ CAP_NET_RAW๋ฅผ ๊ฐ์ง๊ฒ ๋จ!
์์ ํ๋ก์ธ์ค๋ฅผ ์์ฑํ๊ธฐ ์ ์ ambient capabilities๋ฅผ ์ ๊ฑฐํ๋ ๊ฒ ์์ ํด์.
๐ก๏ธ ๋ณด์ ๊ฐํ ๊ธฐ๋ฒ
Securebits๋ capabilities์ ๋์์ ์ ์ดํ๋ ํ๋๊ทธ์์. ๋ณด์์ ๋์ฑ ๊ฐํํ ์ ์์ฃ !
#include <sys/prctl.h>
// UID ๋ณ๊ฒฝ ์ capabilities ์ ์ง ๋ฐฉ์ง
prctl(PR_SET_SECUREBITS,
SECBIT_KEEP_CAPS_LOCKED |
SECBIT_NO_SETUID_FIXUP |
SECBIT_NO_SETUID_FIXUP_LOCKED);
// ์ด์ setuid(0)์ ํด๋ capabilities๋ฅผ ์ป์ ์ ์์
์ฃผ์ securebits:โข SECBIT_KEEP_CAPS: UID ๋ณ๊ฒฝ ์ capabilities ์ ์ง
โข SECBIT_NO_SETUID_FIXUP: setuid ์ capabilities ์๋ ์กฐ์ ๋ฐฉ์ง
โข SECBIT_NOROOT: root UID์ ํน๋ณ ์ทจ๊ธ ๋นํ์ฑํ
โข *_LOCKED ๋ณํ: ํด๋น ์ค์ ์ ์ ๊ฐ์ ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ๊ฒ ๋ง๋ฆ
์ด ํ๋๊ทธ๋ฅผ ์ค์ ํ๋ฉด ํ๋ก์ธ์ค์ ๊ทธ ์์๋ค์ด ๋ ๋ง์ ๊ถํ์ ์ป์ ์ ์์ด์!
#include <sys/prctl.h>
// ๋ ์ด์ ๊ถํ ์์น ๋ถ๊ฐ
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
// ์ด์ setuid ๋ฐ์ด๋๋ฆฌ๋ฅผ ์คํํด๋ ๊ถํ์ด ์์น๋์ง ์์
execve("/usr/bin/sudo", ...); // ์คํจ!
Docker์์๋ ์ฌ์ฉ ๊ฐ๋ฅํด์:$ docker run --security-opt=no-new-privileges:true myimage
Bounding set์ ์ ํํ๋ฉด ํ๋ก์ธ์ค๊ฐ ํ๋ํ ์ ์๋ capabilities์ ์ํ์ ์ ์ค์ ํ ์ ์์ด์.
#include <sys/prctl.h>
#include <sys/capability.h>
void restrict_capabilities() {
// CAP_SYS_ADMIN์ bounding set์์ ์ ๊ฑฐ
if (prctl(PR_CAPBSET_DROP, CAP_SYS_ADMIN, 0, 0, 0) == -1) {
perror("prctl(PR_CAPBSET_DROP)");
exit(1);
}
// ์ด์ ์ด ํ๋ก์ธ์ค์ ์์๋ค์ ์ ๋ CAP_SYS_ADMIN์ ์ป์ ์ ์์
}
๐ ๊ฐ์ฌ์ ๋ชจ๋ํฐ๋ง
์์คํ ์์ capabilities๊ฐ ์ด๋ป๊ฒ ์ฌ์ฉ๋๊ณ ์๋์ง ๋ชจ๋ํฐ๋งํ๋ ๊ฒ๋ ์ค์ํด์!
$ sudo find / -type f -exec getcap {} \; 2>/dev/null
/usr/bin/ping = cap_net_raw+ep
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep
...
์คํ ์ค์ธ ํ๋ก์ธ์ค์ capabilities ํ์ธ:$ grep Cap /proc/self/status
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
์ด 16์ง์ ๊ฐ์ ํด์ํ๋ ค๋ฉด capsh๋ฅผ ์ฌ์ฉํ์ธ์:$ capsh --decode=000001ffffffffff
0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,...
# /etc/audit/rules.d/capabilities.rules
-a always,exit -F arch=b64 -S capset -k capabilities
-a always,exit -F arch=b32 -S capset -k capabilities
๐ง Systemd์ Capabilities ํตํฉ
ํ๋ ๋ฆฌ๋ ์ค ์์คํ ์์๋ systemd๊ฐ ์๋น์ค๋ฅผ ๊ด๋ฆฌํ๋๋ฐ, systemd๋ capabilities๋ฅผ ์์ฃผ ์ ์ง์ํด์! ์๋น์ค ๋จ์๋ก ์ธ๋ฐํ๊ฒ ๊ถํ์ ์ ์ดํ ์ ์์ฃ . ๐ฏ
๐ Systemd Service Unit ์ค์
# /etc/systemd/system/mywebserver.service
[Unit]
Description=My Web Server
After=network.target
[Service]
Type=simple
User=webuser
Group=webgroup
# ์คํ ํ์ผ
ExecStart=/usr/local/bin/mywebserver
# Capabilities ์ค์
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# ๋ณด์ ๊ฐํ ์ต์
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/www
# ๋ฆฌ์์ค ์ ํ
LimitNOFILE=65536
LimitNPROC=512
[Install]
WantedBy=multi-user.target
์ด ์ค์ ์ ์๋ฏธ:โข AmbientCapabilities: ์๋น์ค๊ฐ ์์ํ ๋ ๊ฐ์ง capabilities
โข CapabilityBoundingSet: ํ๋ ๊ฐ๋ฅํ capabilities์ ์ํ์
โข NoNewPrivileges: ๊ถํ ์์น ๋ฐฉ์ง
โข ProtectSystem: ์์คํ ๋๋ ํ ๋ฆฌ๋ฅผ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก
โข ProtectHome: ํ ๋๋ ํ ๋ฆฌ ์ ๊ทผ ์ฐจ๋จ
# /etc/systemd/system/netmonitor.service
[Unit]
Description=Network Monitoring Service
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=netmon
Group=netmon
ExecStart=/usr/local/bin/netmonitor --config /etc/netmonitor/config.yml
# ํ์ํ capabilities๋ง ๋ถ์ฌ
AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN
# ๊ฐ๋ ฅํ ๋ณด์ ์ค์
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
# ๋คํธ์ํฌ ๋ค์์คํ์ด์ค๋ ๊ณต์ (๋ชจ๋ํฐ๋ง ํ์)
PrivateNetwork=false
# ๋ก๊ทธ ๋๋ ํ ๋ฆฌ๋ง ์ฐ๊ธฐ ๊ฐ๋ฅ
ReadWritePaths=/var/log/netmonitor
# ์์คํ
์ฝ ํํฐ๋ง (ํ์ดํธ๋ฆฌ์คํธ ๋ฐฉ์)
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
# ์ฌ์์ ์ ์ฑ
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.target
$ systemd-analyze security mywebserver.service
โ Overall exposure level for mywebserver.service: 4.2 MEDIUM ๐
โ PrivateTmp=yes
โ NoNewPrivileges=yes
โ PrivateDevices=no
โ ProtectKernelModules=no
...
๐๏ธ Systemd์ ๊ณ ๊ธ ๋ณด์ ๊ธฐ๋ฅ
ํน์ ์์คํ ์ฝ๋ง ํ์ฉํ๊ฑฐ๋ ์ฐจ๋จํ ์ ์์ด์. seccomp๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ์ฃ !
[Service]
# ๋คํธ์ํฌ ๊ด๋ จ ์์คํ
์ฝ๋ง ํ์ฉ
SystemCallFilter=@network-io @basic-io @file-system
# ๋๋ ์ํํ ์์คํ
์ฝ ์ฐจ๋จ
SystemCallFilter=~@privileged @resources @obsolete
# ํน์ ์์คํ
์ฝ ๋ช
์์ ์ฐจ๋จ
SystemCallFilter=~execve execveat
์ฃผ์ ์์คํ
์ฝ ๊ทธ๋ฃน:โข @basic-io: ๊ธฐ๋ณธ ์ ์ถ๋ ฅ
โข @network-io: ๋คํธ์ํฌ ์์
โข @privileged: ๊ถํ์ด ํ์ํ ์์
โข @resources: ๋ฆฌ์์ค ์ ํ ๋ณ๊ฒฝ
โข @obsolete: ๊ตฌ์ ์์คํ ์ฝ
[Service]
# IPv4์ IPv6๋ง ํ์ฉ
RestrictAddressFamilies=AF_INET AF_INET6
# ๋๋ Unix ์์ผ๋ง ํ์ฉ
RestrictAddressFamilies=AF_UNIX
[Service]
# ์์คํ
๋๋ ํ ๋ฆฌ ๋ณดํธ
ProtectSystem=strict
# ํน์ ๊ฒฝ๋ก๋ง ์ฐ๊ธฐ ํ์ฉ
ReadWritePaths=/var/lib/myapp /var/log/myapp
# ํน์ ๊ฒฝ๋ก๋ ์ฝ๊ธฐ๋ง ํ์ฉ
ReadOnlyPaths=/etc/myapp
# ํน์ ๊ฒฝ๋ก ์์ ์ฐจ๋จ
InaccessiblePaths=/home /root
# ์์ ๋๋ ํ ๋ฆฌ ๊ฒฉ๋ฆฌ
PrivateTmp=true
๐ ๋์ Capability ๊ด๋ฆฌ
์คํ ์ค์ธ ์๋น์ค์ capabilities๋ฅผ ๋์ ์ผ๋ก ํ์ธํ๊ณ ๊ด๋ฆฌํ ์๋ ์์ด์!
$ systemctl status mywebserver.service
$ systemctl show mywebserver.service | grep Cap
CapabilityBoundingSet=cap_net_bind_service
AmbientCapabilities=cap_net_bind_service
ํ๋ก์ธ์ค์ ์ค์ capabilities ํ์ธ:# ์๋น์ค์ ๋ฉ์ธ PID ์ฐพ๊ธฐ
$ systemctl show -p MainPID mywebserver.service
MainPID=1234
# ํด๋น ํ๋ก์ธ์ค์ capabilities ํ์ธ
$ grep Cap /proc/1234/status
$ cat /proc/1234/status | grep Cap
CapInh: 0000000000000400
CapPrm: 0000000000000400
CapEff: 0000000000000400
CapBnd: 0000000000000400
CapAmb: 0000000000000400
# ํด์
$ capsh --decode=0000000000000400
0x0000000000000400=cap_net_bind_service
๐ ๋ค์ํ ์ธ์ด์์ Capabilities ์ฌ์ฉํ๊ธฐ
C ์ธ์ด ์ธ์๋ ์ฌ๋ฌ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ capabilities๋ฅผ ๋ค๋ฃฐ ์ ์์ด์. ๊ฐ ์ธ์ด๋ณ๋ก ์ดํด๋ณผ๊น์? ๐ป
๐ Python
import prctl
import os
def drop_capabilities():
"""๋ชจ๋ capabilities ์ ๊ฑฐ"""
try:
# Bounding set์์ ๋ชจ๋ capabilities ์ ๊ฑฐ
for cap in range(prctl.CAP_LAST_CAP + 1):
try:
prctl.capbset_drop(cap)
except OSError:
pass # ์ด๋ฏธ ์๋ capability
# Ambient capabilities ์ ๊ฑฐ
prctl.cap_ambient.clear()
print("All capabilities dropped successfully")
except Exception as e:
print(f"Error dropping capabilities: {e}")
def keep_only_net_bind():
"""CAP_NET_BIND_SERVICE๋ง ์ ์ง"""
import prctl
# ํ์ํ capability ์ค์
prctl.cap_effective.net_bind_service = True
prctl.cap_permitted.net_bind_service = True
prctl.cap_inheritable.net_bind_service = True
# Ambient์ ์ถ๊ฐ (์ ํ์ฌํญ)
prctl.cap_ambient.net_bind_service = True
print(f"Current capabilities: {prctl.cap_effective}")
def check_capability(cap_name):
"""ํน์ capability ํ์ธ"""
import prctl
cap = getattr(prctl.CAP, cap_name.upper())
has_cap = prctl.capbset_read(cap)
print(f"Has {cap_name}: {has_cap}")
return has_cap
if __name__ == '__main__':
print("Initial capabilities:")
print(f"Effective: {prctl.cap_effective}")
print(f"Permitted: {prctl.cap_permitted}")
# CAP_NET_BIND_SERVICE ํ์ธ
if check_capability('net_bind_service'):
# 80๋ฒ ํฌํธ ๋ฐ์ธ๋ฉ ์๋
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.bind(('0.0.0.0', 80))
print("Successfully bound to port 80!")
sock.close()
except PermissionError:
print("Failed to bind to port 80")
# ๊ถํ ์ ๊ฑฐ
drop_capabilities()
์ค์น:$ pip install python-prctl
โ Java
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
public class CapabilityManager {
// libcap ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ธํฐํ์ด์ค
public interface LibCap extends Library {
LibCap INSTANCE = Native.load("cap", LibCap.class);
Pointer cap_get_proc();
int cap_set_proc(Pointer cap);
Pointer cap_from_text(String text);
String cap_to_text(Pointer cap, Pointer len);
int cap_free(Pointer cap);
}
public static void printCurrentCapabilities() {
Pointer caps = LibCap.INSTANCE.cap_get_proc();
if (caps == null) {
System.err.println("Failed to get capabilities");
return;
}
String capsText = LibCap.INSTANCE.cap_to_text(caps, null);
System.out.println("Current capabilities: " + capsText);
LibCap.INSTANCE.cap_free(caps);
}
public static boolean setCapabilities(String capText) {
Pointer caps = LibCap.INSTANCE.cap_from_text(capText);
if (caps == null) {
System.err.println("Invalid capability text: " + capText);
return false;
}
int result = LibCap.INSTANCE.cap_set_proc(caps);
LibCap.INSTANCE.cap_free(caps);
return result == 0;
}
public static void dropAllCapabilities() {
setCapabilities("="); // ๋น ์ธํธ
System.out.println("All capabilities dropped");
}
public static void main(String[] args) {
System.out.println("=== Java Capability Manager ===");
printCurrentCapabilities();
// CAP_NET_BIND_SERVICE๋ง ์ ์ง
if (setCapabilities("cap_net_bind_service=ep")) {
System.out.println("Successfully set CAP_NET_BIND_SERVICE");
printCurrentCapabilities();
}
// ๋ชจ๋ capabilities ์ ๊ฑฐ
dropAllCapabilities();
printCurrentCapabilities();
}
}
Maven ์์กด์ฑ:<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.12.1</version>
</dependency>
๐ฆ Rust
use caps::{Capability, CapSet, CapsHashSet};
use std::error::Error;
fn print_capabilities() -> Result<(), Box<dyn Error>> {
println!("Current capabilities:");
for set in &[CapSet::Effective, CapSet::Permitted, CapSet::Inheritable] {
let caps = caps::read(None, *set)?;
println!(" {:?}: {:?}", set, caps);
}
Ok(())
}
fn drop_all_capabilities() -> Result<(), Box<dyn Error>> {
let empty = CapsHashSet::new();
caps::set(None, CapSet::Effective, &empty)?;
caps::set(None, CapSet::Permitted, &empty)?;
caps::set(None, CapSet::Inheritable, &empty)?;
println!("All capabilities dropped");
Ok(())
}
fn keep_only_net_bind() -> Result<(), Box<dyn Error>> {
let mut caps = CapsHashSet::new();
caps.insert(Capability::CAP_NET_BIND_SERVICE);
caps::set(None, CapSet::Effective, &caps)?;
caps::set(None, CapSet::Permitted, &caps)?;
println!("Kept only CAP_NET_BIND_SERVICE");
Ok(())
}
fn has_capability(cap: Capability) -> Result<bool, Box<dyn Error>> {
let caps = caps::read(None, CapSet::Effective)?;
Ok(caps.contains(&cap))
}
fn main() -> Result<(), Box<dyn Error>> {
println!("=== Rust Capability Manager ===\n");
print_capabilities()?;
if has_capability(Capability::CAP_NET_BIND_SERVICE)? {
println!("\nHas CAP_NET_BIND_SERVICE - can bind to port 80");
}
println!("\nKeeping only CAP_NET_BIND_SERVICE...");
keep_only_net_bind()?;
print_capabilities()?;
println!("\nDropping all capabilities...");
drop_all_capabilities()?;
print_capabilities()?;
Ok(())
}
Cargo.toml:[dependencies]
caps = "0.5"
๐ข Node.js
const cap = require('cap');
const http = require('http');
// ํ์ฌ capabilities ํ์ธ
function printCapabilities() {
try {
const caps = cap.getCapabilities();
console.log('Current capabilities:', caps);
} catch (err) {
console.error('Failed to get capabilities:', err.message);
}
}
// ํน์ capability ํ์ธ
function hasCapability(capName) {
try {
return cap.hasCapability(capName);
} catch (err) {
return false;
}
}
// Capabilities ์ค์
function setCapabilities(capList) {
try {
cap.setCapabilities(capList);
console.log('Capabilities set successfully');
return true;
} catch (err) {
console.error('Failed to set capabilities:', err.message);
return false;
}
}
// ๋ฉ์ธ ๋ก์ง
console.log('=== Node.js Capability Manager ===\n');
printCapabilities();
if (hasCapability('cap_net_bind_service')) {
console.log('\nHas CAP_NET_BIND_SERVICE - starting server on port 80');
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello from port 80 without root!\n');
});
server.listen(80, () => {
console.log('Server running on port 80');
// ์๋ฒ ์์ ํ capabilities ์ ๊ฑฐ
console.log('\nDropping capabilities after binding...');
setCapabilities([]); // ๋ชจ๋ ์ ๊ฑฐ
printCapabilities();
// nobody ์ฌ์ฉ์๋ก ์ ํ
try {
process.setgid('nobody');
process.setuid('nobody');
console.log('Switched to nobody user');
} catch (err) {
console.error('Failed to switch user:', err.message);
}
});
} else {
console.log('\nNo CAP_NET_BIND_SERVICE - cannot bind to port 80');
}
์ค์น:$ npm install cap
๐ฌ ๋๋ฒ๊น ๊ณผ ํธ๋ฌ๋ธ์ํ
Capabilities๋ฅผ ์ฌ์ฉํ๋ค ๋ณด๋ฉด ์์์น ๋ชปํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ด์. ์ผ๋ฐ์ ์ธ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์์๋ณผ๊น์? ๐ง
โ ์ผ๋ฐ์ ์ธ ๋ฌธ์ ๋ค
$ ./myapp
Error: bind: Operation not permitted
์์ธ:โข ํ์ํ capability๊ฐ ์์
โข Capability๊ฐ effective ์ธํธ์ ์์
โข Bounding set์์ ์ ํ๋จ
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# 1. ํ์ผ์ capabilities ํ์ธ
$ getcap ./myapp
# 2. ํ๋ก์ธ์ค์ capabilities ํ์ธ (์คํ ์ค์ผ ๋)
$ grep Cap /proc/$(pidof myapp)/status
# 3. ํ์ํ capability ๋ถ์ฌ
$ sudo setcap 'cap_net_bind_service=+ep' ./myapp
# 4. ๋ค์ ํ์ธ
$ getcap ./myapp
์์ธ:
โข ์คํฌ๋ฆฝํธ ์ธํฐํ๋ฆฌํฐ(#!/bin/bash ๋ฑ)๋ capabilities๋ฅผ ์์ํ์ง ์์
โข ํ์ผ์ด ๋ณต์ฌ๋๋ฉด์ extended attributes ์์ค
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# ์คํฌ๋ฆฝํธ์ ๊ฒฝ์ฐ: ์ธํฐํ๋ฆฌํฐ์ capability ๋ถ์ฌ
$ sudo setcap 'cap_net_bind_service=+ep' /usr/bin/python3
# ๋๋ ๋ฐ์ด๋๋ฆฌ๋ก ์ปดํ์ผ
$ pyinstaller --onefile script.py
$ sudo setcap 'cap_net_bind_service=+ep' dist/script
# ํ์ผ ๋ณต์ฌ ์ extended attributes ์ ์ง
$ cp --preserve=all source dest
$ rsync -aX source dest
์์ธ:
SELinux๋ AppArmor๊ฐ capabilities๋ณด๋ค ์ฐ์ ํ์ฌ ์ ๊ทผ์ ์ฐจ๋จํ ์ ์์ด์.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# SELinux ์ํ ํ์ธ
$ getenforce
Enforcing
# SELinux ๋ก๊ทธ ํ์ธ
$ sudo ausearch -m avc -ts recent
# ์์๋ก permissive ๋ชจ๋๋ก ์ ํ (ํ
์คํธ์ฉ)
$ sudo setenforce 0
# AppArmor ์ํ ํ์ธ
$ sudo aa-status
# ํน์ ํ๋กํ์ผ ๋นํ์ฑํ (ํ
์คํธ์ฉ)
$ sudo aa-complain /path/to/profile
๐ ๋๋ฒ๊น ๋๊ตฌ์ ๊ธฐ๋ฒ
#!/bin/bash
# cap_debug.sh - Capability ๋๋ฒ๊น
์คํฌ๋ฆฝํธ
echo "=== Capability Debug Information ==="
echo
# ํ์ฌ ํ๋ก์ธ์ค ์ ๋ณด
echo "Current Process (PID: $$):"
grep Cap /proc/$$/status | while read line; do
cap_name=$(echo $line | cut -d: -f1)
cap_hex=$(echo $line | cut -d: -f2 | tr -d ' ')
cap_decoded=$(capsh --decode=$cap_hex 2>/dev/null)
echo " $cap_name: $cap_hex"
echo " โ $cap_decoded"
done
echo
echo "Bounding Set:"
cat /proc/$$/status | grep CapBnd | cut -d: -f2 | tr -d ' ' | \
xargs -I {} capsh --decode={}
echo
echo "Ambient Set:"
cat /proc/$$/status | grep CapAmb | cut -d: -f2 | tr -d ' ' | \
xargs -I {} capsh --decode={}
# ์คํ ํ์ผ ์ ๋ณด
if [ -n "$1" ]; then
echo
echo "File Capabilities for: $1"
getcap "$1"
fi
# Securebits ์ ๋ณด
echo
echo "Securebits:"
grep Seccomp /proc/$$/status
์ฌ์ฉ:$ chmod +x cap_debug.sh
$ ./cap_debug.sh /path/to/myapp
# Capability ๊ด๋ จ ์์คํ
์ฝ ์ถ์
$ strace -e trace=capget,capset,prctl ./myapp
# ์คํจํ ์์คํ
์ฝ๋ง ํ์
$ strace -e trace=capget,capset,prctl -Z ./myapp 2>&1 | grep -i "operation not permitted"
# ๋ ์์ธํ ์ ๋ณด
$ strace -f -e trace=capget,capset,prctl -s 1000 ./myapp
#include <stdio.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <errno.h>
#include <string.h>
void print_cap_set(const char *name, cap_flag_t flag) {
cap_t caps = cap_get_proc();
if (!caps) {
perror("cap_get_proc");
return;
}
printf("%s capabilities:\n", name);
for (int i = 0; i <= CAP_LAST_CAP; i++) {
cap_flag_value_t value;
if (cap_get_flag(caps, i, flag, &value) == 0 && value == CAP_SET) {
char *cap_name = cap_to_name(i);
printf(" %s\n", cap_name);
cap_free(cap_name);
}
}
cap_free(caps);
}
int test_capability(cap_value_t cap) {
cap_t caps = cap_get_proc();
if (!caps) return -1;
cap_flag_value_t value;
int result = cap_get_flag(caps, cap, CAP_EFFECTIVE, &value);
cap_free(caps);
return (result == 0 && value == CAP_SET) ? 1 : 0;
}
int main() {
printf("=== Capability Test Program ===\n\n");
print_cap_set("Effective", CAP_EFFECTIVE);
printf("\n");
print_cap_set("Permitted", CAP_PERMITTED);
printf("\n");
print_cap_set("Inheritable", CAP_INHERITABLE);
printf("\n");
// ํน์ capabilities ํ
์คํธ
printf("Specific capability tests:\n");
const char *test_caps[] = {
"CAP_NET_BIND_SERVICE",
"CAP_NET_RAW",
"CAP_SYS_ADMIN",
"CAP_SETUID",
NULL
};
for (int i = 0; test_caps[i]; i++) {
cap_value_t cap;
if (cap_from_name(test_caps[i], &cap) == 0) {
int has_cap = test_capability(cap);
printf(" %s: %s\n", test_caps[i],
has_cap ? "YES โ" : "NO โ");
}
}
// Securebits ํ์ธ
printf("\nSecurebits: 0x%x\n", prctl(PR_GET_SECUREBITS));
// No new privileges ํ์ธ
printf("No new privileges: %s\n",
prctl(PR_GET_NO_NEW_PRIVS) ? "YES" : "NO");
return 0;
}
์ปดํ์ผ ๋ฐ ์คํ:$ gcc -o captest captest.c -lcap
$ ./captest
๐ ์ฑ๋ฅ ์ํฅ ๋ถ์
Capabilities๋ฅผ ์ฌ์ฉํ๋ฉด ์ฑ๋ฅ์ ์ํฅ์ด ์์๊น์? ์ผ๋ฐ์ ์ผ๋ก ์ํฅ์ ๋ฏธ๋ฏธํ์ง๋ง, ํ์ธํด๋ณผ ์ ์์ด์!
#!/bin/bash
# cap_benchmark.sh
echo "=== Capability Performance Benchmark ==="
# ํ
์คํธ ํ๋ก๊ทธ๋จ (๊ฐ๋จํ ๋คํธ์ํฌ ๋ฐ์ธ๋ฉ)
cat > test_bind.c <<'EOF'
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8080),
.sin_addr.s_addr = INADDR_ANY
};
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
close(sock);
return 0;
}
EOF
gcc -o test_bind test_bind.c
# 1. Root๋ก ์คํ
echo "Test 1: Running as root"
sudo time -p bash -c 'for i in {1..1000}; do ./test_bind; done'
# 2. Capability๋ก ์คํ
echo -e "\nTest 2: Running with CAP_NET_BIND_SERVICE"
sudo setcap 'cap_net_bind_service=+ep' ./test_bind
time -p bash -c 'for i in {1..1000}; do ./test_bind; done'
# 3. Setuid๋ก ์คํ
echo -e "\nTest 3: Running with setuid"
sudo chown root:root ./test_bind
sudo chmod u+s ./test_bind
time -p bash -c 'for i in {1..1000}; do ./test_bind; done'
# ์ ๋ฆฌ
sudo setcap -r ./test_bind
sudo chmod u-s ./test_bind
rm test_bind test_bind.c
๊ฒฐ๊ณผ์ ์ผ๋ก capabilities๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด setuid๋ณด๋ค ์ฝ๊ฐ ๋น ๋ฅด๊ฑฐ๋ ๋น์ทํ ์ฑ๋ฅ์ ๋ณด์ฌ์! ๐
๐ฏ ์ค์ ํ๋ก์ ํธ: ์์ ํ ๋คํธ์ํฌ ์๋ฒ ๊ตฌ์ถ
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ํ๋ก๋์ ํ๊ฒฝ์์ ์ฌ์ฉํ ์ ์๋ ์์ ํ ๋คํธ์ํฌ ์๋ฒ๋ฅผ ๋ง๋ค์ด๋ณผ๊น์? ๐๏ธ
๐ ํ๋ก์ ํธ ์๊ตฌ์ฌํญ
โข Root ๊ถํ ์์ด ์คํ
โข ์ต์ ๊ถํ ์์น ์ ์ฉ
โข ์๋ฒ ์์ ํ ๋ชจ๋ ๋ถํ์ํ ๊ถํ ์ ๊ฑฐ
โข Systemd ํตํฉ
โข ๋ก๊น ๋ฐ ๋ชจ๋ํฐ๋ง
โข ๋ณด์ ๊ฐํ (seccomp, namespace ๋ฑ)
๐ป ์๋ฒ ๊ตฌํ (C)
// secure_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#include <signal.h>
#define PORT 80
#define BACKLOG 128
#define BUFFER_SIZE 4096
volatile sig_atomic_t running = 1;
void signal_handler(int sig) {
running = 0;
}
int drop_capabilities() {
cap_t caps = cap_init();
if (!caps) {
syslog(LOG_ERR, "cap_init failed");
return -1;
}
if (cap_set_proc(caps) == -1) {
syslog(LOG_ERR, "cap_set_proc failed");
cap_free(caps);
return -1;
}
cap_free(caps);
syslog(LOG_INFO, "All capabilities dropped");
return 0;
}
int drop_privileges(const char *username) {
struct passwd *pw = getpwnam(username);
if (!pw) {
syslog(LOG_ERR, "User %s not found", username);
return -1;
}
// ๋ณด์กฐ ๊ทธ๋ฃน ์ค์
if (initgroups(username, pw->pw_gid) == -1) {
syslog(LOG_ERR, "initgroups failed");
return -1;
}
// GID ๋ณ๊ฒฝ
if (setgid(pw->pw_gid) == -1) {
syslog(LOG_ERR, "setgid failed");
return -1;
}
// UID ๋ณ๊ฒฝ
if (setuid(pw->pw_uid) == -1) {
syslog(LOG_ERR, "setuid failed");
return -1;
}
// ๊ถํ ์์น ๋ฐฉ์ง
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
syslog(LOG_ERR, "prctl(PR_SET_NO_NEW_PRIVS) failed");
return -1;
}
syslog(LOG_INFO, "Dropped privileges to user %s (uid=%d, gid=%d)",
username, pw->pw_uid, pw->pw_gid);
return 0;
}
int create_server_socket() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
syslog(LOG_ERR, "socket() failed");
return -1;
}
// SO_REUSEADDR ์ค์
int opt = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
syslog(LOG_WARNING, "setsockopt(SO_REUSEADDR) failed");
}
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(PORT),
.sin_addr.s_addr = INADDR_ANY
};
// ๋ฐ์ธ๋ฉ (CAP_NET_BIND_SERVICE ํ์)
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
syslog(LOG_ERR, "bind() failed on port %d", PORT);
close(sock);
return -1;
}
if (listen(sock, BACKLOG) == -1) {
syslog(LOG_ERR, "listen() failed");
close(sock);
return -1;
}
syslog(LOG_INFO, "Server listening on port %d", PORT);
return sock;
}
void handle_client(int client_sock) {
char buffer[BUFFER_SIZE];
ssize_t bytes_read = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
// ๊ฐ๋จํ HTTP ์๋ต
const char *response =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"<html><body>"
"<h1>Secure Server</h1>"
"<p>Running without root privileges!</p>"
"</body></html>";
send(client_sock, response, strlen(response), 0);
}
close(client_sock);
}
int main(int argc, char *argv[]) {
const char *username = "nobody";
if (argc > 1) {
username = argv[1];
}
// Syslog ์ด๊ธฐํ
openlog("secure_server", LOG_PID | LOG_CONS, LOG_DAEMON);
syslog(LOG_INFO, "Starting secure server...");
// ์๊ทธ๋ ํธ๋ค๋ฌ ์ค์
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// ์๋ฒ ์์ผ ์์ฑ (CAP_NET_BIND_SERVICE ์ฌ์ฉ)
int server_sock = create_server_socket();
if (server_sock == -1) {
syslog(LOG_ERR, "Failed to create server socket");
return 1;
}
// ๊ถํ ์ ๊ฑฐ (์์ ์ค์!)
// 1. ๋จผ์ ์ฌ์ฉ์ ๋ณ๊ฒฝ
if (drop_privileges(username) == -1) {
close(server_sock);
return 1;
}
// 2. ๊ทธ ๋ค์ capabilities ์ ๊ฑฐ
if (drop_capabilities() == -1) {
close(server_sock);
return 1;
}
syslog(LOG_INFO, "Server started successfully with minimal privileges");
// ๋ฉ์ธ ๋ฃจํ
while (running) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_sock = accept(server_sock,
(struct sockaddr*)&client_addr,
&client_len);
if (client_sock == -1) {
if (running) {
syslog(LOG_WARNING, "accept() failed");
}
continue;
}
handle_client(client_sock);
}
close(server_sock);
syslog(LOG_INFO, "Server stopped");
closelog();
return 0;
}
์ปดํ์ผ:$ gcc -o secure_server secure_server.c -lcap
$ sudo setcap 'cap_net_bind_service=+ep' ./secure_server
๐ง Systemd Service ์ค์
# /etc/systemd/system/secure-server.service
[Unit]
Description=Secure HTTP Server
After=network-online.target
Wants=network-online.target
Documentation=https://www.jaenung.net
[Service]
Type=simple
User=nobody
Group=nogroup
# ์คํ ํ์ผ
ExecStart=/usr/local/bin/secure_server
# Capabilities ์ค์
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# ๋ณด์ ๊ฐํ
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
SystemCallArchitectures=native
# ์์คํ
์ฝ ํํฐ
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @obsolete
# ํ์ผ ์์คํ
์ ๊ทผ
ReadWritePaths=/var/log/secure-server
# ๋ฆฌ์์ค ์ ํ
LimitNOFILE=65536
LimitNPROC=512
TasksMax=256
# ์ฌ์์ ์ ์ฑ
Restart=on-failure
RestartSec=10s
StartLimitBurst=5
StartLimitIntervalSec=60s
# ๋ก๊น
StandardOutput=journal
StandardError=journal
SyslogIdentifier=secure-server
[Install]
WantedBy=multi-user.target
์ค์น ๋ฐ ์คํ:$ sudo cp secure_server /usr/local/bin/
$ sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/secure_server
$ sudo systemctl daemon-reload
$ sudo systemctl enable secure-server
$ sudo systemctl start secure-server
$ sudo systemctl status secure-server
๐ ๋ชจ๋ํฐ๋ง ์คํฌ๋ฆฝํธ
#!/bin/bash
# monitor_server.sh
echo "=== Secure Server Monitoring ==="
echo
# ์๋น์ค ์ํ
echo "Service Status:"
systemctl status secure-server --no-pager | head -n 10
echo
# ํ๋ก์ธ์ค ์ ๋ณด
PID=$(systemctl show -p MainPID secure-server | cut -d= -f2)
if [ "$PID" != "0" ]; then
echo "Process Information (PID: $PID):"
echo " User: $(ps -o user= -p $PID)"
echo " Command: $(ps -o cmd= -p $PID)"
echo
# Capabilities
echo "Capabilities:"
grep Cap /proc/$PID/status | while read line; do
cap_name=$(echo $line | cut -d: -f1)
cap_hex=$(echo $line | cut -d: -f2 | tr -d ' ')
echo " $cap_name: $cap_hex"
if [ "$cap_hex" != "0000000000000000" ]; then
capsh --decode=$cap_hex 2>/dev/null | sed 's/^/ /'
fi
done
echo
# ๋คํธ์ํฌ ์ฐ๊ฒฐ
echo "Network Connections:"
ss -tlnp | grep $PID
echo
# ๋ฆฌ์์ค ์ฌ์ฉ๋
echo "Resource Usage:"
echo " Memory: $(ps -o rss= -p $PID | awk '{print $1/1024 " MB"}')"
echo " CPU: $(ps -o %cpu= -p $PID)%"
echo
# ํ์ผ ๋์คํฌ๋ฆฝํฐ
echo "Open File Descriptors: $(ls /proc/$PID/fd | wc -l)"
echo
fi
# ์ต๊ทผ ๋ก๊ทธ
echo "Recent Logs:"
journalctl -u secure-server -n 10 --no-pager
# ๋ณด์ ์ ์
echo
echo "Security Score:"
systemd-analyze security secure-server 2>/dev/null | head -n 5
์ฌ์ฉ:$ chmod +x monitor_server.sh
$ ./monitor_server.sh
๐ Best Practices์ ๊ถ์ฅ์ฌํญ
๋ง์ง๋ง์ผ๋ก capabilities๋ฅผ ์ฌ์ฉํ ๋ ๊ผญ ์ง์ผ์ผ ํ ๋ฒ ์คํธ ํ๋ํฐ์ค๋ฅผ ์ ๋ฆฌํด๋ณผ๊ฒ์! ๐
โข ํ์ํ ์ต์ํ์ capabilities๋ง ๋ถ์ฌํ์ธ์
โข CAP_SYS_ADMIN ๊ฐ์ "๋ง๋ฅ" capability๋ ํผํ์ธ์
โข ์์ ์ด ๋๋๋ฉด ์ฆ์ ๊ถํ์ ์ ๊ฑฐํ์ธ์
// ์ข์ ์
cap_t caps = cap_from_text("cap_net_bind_service=ep");
cap_set_proc(caps);
// ํฌํธ ๋ฐ์ธ๋ฉ
bind(sock, ...);
// ์ฆ์ ์ ๊ฑฐ
cap_from_text("=");
cap_set_proc(caps);
โข Capability ์ค์ ์ ํ๋ก ํญ์ ํ์ธํ์ธ์
โข ์คํจ ์ ์์ ํ๊ฒ ์ข ๋ฃํ์ธ์
โข ๋ก๊น ์ ์ถฉ๋ถํ ํ์ธ์
if (cap_set_proc(caps) == -1) {
syslog(LOG_ERR, "Failed to set capabilities: %s", strerror(errno));
// ์์ ํ๊ฒ ์ข
๋ฃ
exit(1);
}
// ์ค์ ํ์ธ
cap_t current = cap_get_proc();
char *text = cap_to_text(current, NULL);
syslog(LOG_INFO, "Current capabilities: %s", text);
cap_free(text);
cap_free(current);
๊ถํ์ ์ ๊ฑฐํ ๋๋ ์์๊ฐ ์ค์ํด์:
1๏ธโฃ ํ์ํ ์์ ์ํ (์: ํฌํธ ๋ฐ์ธ๋ฉ)
2๏ธโฃ ํ์ผ ๋์คํฌ๋ฆฝํฐ ๋ฑ ๋ฆฌ์์ค ํ๋ณด
3๏ธโฃ Bounding set ์ ํ
4๏ธโฃ ์ฌ์ฉ์/๊ทธ๋ฃน ๋ณ๊ฒฝ
5๏ธโฃ Capabilities ์ ๊ฑฐ
6๏ธโฃ No new privileges ์ค์
7๏ธโฃ Seccomp ํํฐ ์ ์ฉ
// 1. ํฌํธ ๋ฐ์ธ๋ฉ
bind(sock, ...);
// 2. Bounding set ์ ํ
for (int i = 0; i <= CAP_LAST_CAP; i++) {
prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
}
// 3. ์ฌ์ฉ์ ๋ณ๊ฒฝ
setuid(nobody_uid);
// 4. Capabilities ์ ๊ฑฐ
cap_t empty = cap_init();
cap_set_proc(empty);
cap_free(empty);
// 5. No new privileges
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
// 6. Seccomp (์ ํ์ฌํญ)
// install_seccomp_filter();
โข ๊ฐ๋ฐ ํ๊ฒฝ์์ ์ถฉ๋ถํ ํ ์คํธํ์ธ์
โข ๊ฐ capability๊ฐ ์ ๋ง ํ์ํ์ง ํ์ธํ์ธ์
โข ์๋ํ๋ ๋ณด์ ํ ์คํธ๋ฅผ ๊ตฌ์ถํ์ธ์
#!/bin/bash
# test_capabilities.sh
echo "Testing capability requirements..."
# ๊ฐ capability๋ฅผ ํ๋์ฉ ์ ๊ฑฐํ๋ฉฐ ํ
์คํธ
for cap in net_bind_service net_raw sys_admin; do
echo "Testing without CAP_${cap^^}..."
# Capability ์ ๊ฑฐ
sudo setcap -r ./myapp
sudo setcap "cap_net_bind_service,cap_net_raw,cap_sys_admin-cap_$cap=ep" ./myapp
# ํ
์คํธ ์คํ
if ./myapp --test; then
echo " โ Works without CAP_${cap^^}"
else
echo " โ Requires CAP_${cap^^}"
fi
done
โข ์ ํน์ capability๊ฐ ํ์ํ์ง ๋ฌธ์ํํ์ธ์
โข ๋ณด์ ๊ด๋ จ ๊ฒฐ์ ์ฌํญ์ ๊ธฐ๋กํ์ธ์
โข README์ ์ค์น ๋ฐ ์ค์ ๋ฐฉ๋ฒ์ ๋ช ์ํ์ธ์
## Security Configuration
This application requires the following capabilities:
- **CAP_NET_BIND_SERVICE**: Required to bind to port 80
- Used in: src/server.c:123
- Alternative: Run on port 8080 and use reverse proxy
- **CAP_NET_RAW**: Required for ICMP ping functionality
- Used in: src/monitor.c:456
- Can be disabled with --no-ping flag
### Installation
```bash
sudo setcap 'cap_net_bind_service,cap_net_raw=+ep' ./myapp
```
### Verification
```bash
getcap ./myapp
# Expected: cap_net_bind_service,cap_net_raw=ep
```
โข ์์คํ ์ ๋ชจ๋ capabilities๋ฅผ ์ ๊ธฐ์ ์ผ๋ก ๊ฒํ ํ์ธ์
โข ๋ถํ์ํ ๊ถํ์ด ์๋์ง ํ์ธํ์ธ์
โข ๋ณด์ ์ ๋ฐ์ดํธ๋ฅผ ์ฃผ์ํ์ธ์
#!/bin/bash
# audit_capabilities.sh
echo "=== Capability Audit Report ==="
echo "Date: $(date)"
echo
echo "Files with capabilities:"
sudo find / -type f -exec getcap {} \; 2>/dev/null | \
while read line; do
file=$(echo $line | cut -d' ' -f1)
caps=$(echo $line | cut -d'=' -f2-)
echo "File: $file"
echo " Capabilities: $caps"
echo " Owner: $(ls -l $file | awk '{print $3":"$4}')"
echo " Modified: $(stat -c %y $file)"
echo
done
echo "Running processes with capabilities:"
for pid in /proc/[0-9]*; do
caps=$(grep CapEff $pid/status 2>/dev/null | awk '{print $2}')
if [ "$caps" != "0000000000000000" ]; then
cmd=$(cat $pid/cmdline 2>/dev/null | tr '\0' ' ')
echo "PID: $(basename $pid)"
echo " Command: $cmd"
echo " Capabilities: $(capsh --decode=$caps)"
echo
fi
done
๐ฌ ๋ง๋ฌด๋ฆฌ
์! ์ ๋ง ๊ธด ์ฌ์ ์ด์์ฃ ? ๐ ๋ฆฌ๋
์ค capabilities์ ๋ํด ์ ๋ง ๋ง์ ๊ฒ์ ๋ฐฐ์ ์ด์!
์ฐ๋ฆฌ๋ capabilities๊ฐ ๋ฌด์์ธ์ง, ์ ํ์ํ์ง๋ถํฐ ์์ํด์, ์ค์ ๋ก ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง, ์ด๋ค ํจ์ ์ด ์๋์ง, ๊ทธ๋ฆฌ๊ณ ํ๋ก๋์
ํ๊ฒฝ์์ ์์ ํ๊ฒ ์ ์ฉํ๋ ๋ฐฉ๋ฒ๊น์ง ๋ชจ๋ ์ดํด๋ดค์ด์. ๐
ํต์ฌ ํฌ์ธํธ๋ฅผ ๋ค์ ํ๋ฒ ์ ๋ฆฌํ๋ฉด:
๐น Capabilities๋ root ๊ถํ์ ์ธ๋ฐํ๊ฒ ๋๋ ์ ํ์ํ ๊ถํ๋ง ๋ถ์ฌํ ์ ์๊ฒ ํด์ค์
๐น ์ต์ ๊ถํ ์์น์ ํญ์ ๊ธฐ์ตํ์ธ์ - ํ์ํ ๊ฒ๋ง, ํ์ํ ๋งํผ๋ง!
๐น Systemd์ ํตํฉํ๋ฉด ๋์ฑ ๊ฐ๋ ฅํ ๋ณด์ ์ฒด๊ณ๋ฅผ ๊ตฌ์ถํ ์ ์์ด์
๐น ๋ค์ํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ capabilities๋ฅผ ํ์ฉํ ์ ์์ด์
๐น ํ
์คํธ, ๋ชจ๋ํฐ๋ง, ๋ฌธ์ํ๋ ํ์์์!
๋ณด์์ ํ ๋ฒ ์ค์ ํ๊ณ ๋๋๋ ๊ฒ ์๋๋ผ ์ง์์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ ๊ฐ์ ํด์ผ ํ๋ ๊ณผ์ ์ด์์. ์ฌ๋ฌ๋ถ์ ์์คํ
๊ณผ ์ ํ๋ฆฌ์ผ์ด์
์ capabilities๋ฅผ ์ ์ฉํ๋ฉด์, ๋ ์์ ํ๊ณ ๊ฒฌ๊ณ ํ ์ํํธ์จ์ด๋ฅผ ๋ง๋ค์ด๊ฐ์๊ธธ ๋ฐ๋๊ฒ์! ๐ช
๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ๋ ๊น์ด ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด, ์ฌ๋ฅ๋ท์์ ๋ฆฌ๋
์ค ๋ณด์ ์ ๋ฌธ๊ฐ๋ค๊ณผ ์ํตํด๋ณด์ธ์. ํจ๊ป ๋ฐฐ์ฐ๊ณ ์ฑ์ฅํ๋ ๊ฒ์ด ๊ฐ์ฅ ๋น ๋ฅธ ๊ธธ์ด๋๊น์! ๐
Happy coding, and stay secure! ๐ก๏ธโจ
๐ ์ด ๊ธ์ด ๋์์ด ๋์
จ๋์?
์ฌ๋ฅ๋ท์์ ๋ ๋ง์ ์ง์์ ๊ณต์ ํ๊ณ ๋ฐฐ์๋ณด์ธ์!
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.