์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ›ก๏ธ ๋ฆฌ๋ˆ…์Šค ๋Šฅ๋ ฅ(Capabilities) ๊ด€๋ฆฌ

๐Ÿ›ก๏ธ ๋ฆฌ๋ˆ…์Šค ๋Šฅ๋ ฅ(Capabilities) ๊ด€๋ฆฌ

์šด์˜์ฒด์ œ ๋ณด์•ˆ์˜ ํ•ต์‹ฌ, ์„ธ๋ฐ€ํ•œ ๊ถŒํ•œ ์ œ์–ด์˜ ์„ธ๊ณ„๋กœ!

๐ŸŽฏ ๋ฆฌ๋ˆ…์Šค Capabilities, ์™œ ์•Œ์•„์•ผ ํ• ๊นŒ?

์—ฌ๋Ÿฌ๋ถ„, ๋ฆฌ๋ˆ…์Šค ์‹œ์Šคํ…œ์—์„œ ํ”„๋กœ๊ทธ๋žจ์„ ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ์ด๋Ÿฐ ๊ณ ๋ฏผ ํ•œ ๋ฒˆ์ฏค ํ•ด๋ณด์…จ์„ ๊ฑฐ์˜ˆ์š”. "์ด ํ”„๋กœ๊ทธ๋žจ์ด 80๋ฒˆ ํฌํŠธ๋ฅผ ์—ด์–ด์•ผ ํ•˜๋Š”๋ฐ, root ๊ถŒํ•œ์„ ์ค˜์•ผ ํ•˜๋‚˜?" ๐Ÿค”

์ „ํ†ต์ ์œผ๋กœ ๋ฆฌ๋ˆ…์Šค๋Š” ๊ถŒํ•œ์„ ๋”ฑ ๋‘ ๊ฐ€์ง€๋กœ ๋‚˜๋ˆด์–ด์š”. ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž(unprivileged)์™€ ์Šˆํผ์œ ์ €(root). ๋งˆ์น˜ "ํ‰๋ฏผ ์•„๋‹ˆ๋ฉด ์™•" ๊ฐ™์€ ๊ทน๋‹จ์ ์ธ ๊ตฌ์กฐ์˜€์ฃ . ๊ทผ๋ฐ ์ƒ๊ฐํ•ด๋ณด์„ธ์š”. ๋‹จ์ง€ ๋„คํŠธ์›Œํฌ ํฌํŠธ ํ•˜๋‚˜ ์—ด๋ ค๊ณ  ํ”„๋กœ๊ทธ๋žจ์— root ๊ถŒํ•œ์„ ์ฃผ๋Š” ๊ฑด ๋งˆ์น˜ ํŽธ์˜์  ๊ฐ€๋Š”๋ฐ Ferrari๋ฅผ ๋ชฐ๊ณ  ๊ฐ€๋Š” ๊ฒƒ๊ณผ ๊ฐ™์•„์š”. ์™„์ „ ์˜ค๋ฒ„์ฃ ! ๐Ÿ˜…

๋ฐ”๋กœ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ์žฅํ•œ ๊ฒŒ Capabilities์ž…๋‹ˆ๋‹ค. root์˜ ๋ง‰๊ฐ•ํ•œ ๊ถŒํ•œ์„ ์ž˜๊ฒŒ ์ชผ๊ฐœ์„œ, ํ•„์š”ํ•œ ๊ถŒํ•œ๋งŒ ๋”ฑ ์ค„ ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“  ๊ฑฐ์˜ˆ์š”. ๋ณด์•ˆ ์ธก๋ฉด์—์„œ ์—„์ฒญ๋‚œ ํ˜์‹ ์ด์—ˆ์ฃ !

๐Ÿ’ก ์žฌ๋Šฅ๋„ท Tip: ๋ฆฌ๋ˆ…์Šค ์‹œ์Šคํ…œ ๋ณด์•ˆ์ด๋‚˜ ํ”„๋กœ๊ทธ๋žจ ๊ฐœ๋ฐœ ๊ด€๋ จํ•ด์„œ ๋” ๊นŠ์ด ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€๋“ค์˜ ์ง€์‹์„ ๊ณต์œ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์š”!
์ „ํ†ต์  ๊ถŒํ•œ vs Capabilities ์ „ํ†ต์  ๋ฐฉ์‹ ROOT ๋ชจ๋“  ๊ถŒํ•œ USER ์ œํ•œ๋œ ๊ถŒํ•œ ์–‘๊ทน๋‹จ ๊ตฌ์กฐ Capabilities ๋ฐฉ์‹ NET SYS DAC KILL CHOWN SETUID ์„ธ๋ฐ€ํ•œ ๊ถŒํ•œ ๋ถ„๋ฆฌ ์ง„ํ™”

๐Ÿ“š Capabilities์˜ ์—ญ์‚ฌ์™€ ๋ฐฐ๊ฒฝ

Capabilities๋Š” POSIX 1003.1e ์ดˆ์•ˆ์—์„œ ์ฒ˜์Œ ์ œ์•ˆ๋˜์—ˆ์–ด์š”. ๋น„๋ก ์ด ํ‘œ์ค€์€ ๊ณต์‹์ ์œผ๋กœ ์ฑ„ํƒ๋˜์ง€ ์•Š์•˜์ง€๋งŒ, ๋ฆฌ๋ˆ…์Šค ์ปค๋„ 2.2 ๋ฒ„์ „๋ถ€ํ„ฐ ๋ถ€๋ถ„์ ์œผ๋กœ ๊ตฌํ˜„๋˜๊ธฐ ์‹œ์ž‘ํ–ˆ์ฃ . ๊ทธ๋ฆฌ๊ณ  ์ปค๋„ 2.6.24 ๋ฒ„์ „์—์„œ ํŒŒ์ผ ๊ธฐ๋ฐ˜ capabilities๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด์„œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹ค์šฉํ™”๋˜์—ˆ์–ด์š”! ๐ŸŽ‰

์™œ ์ด๋Ÿฐ ์‹œ์Šคํ…œ์ด ํ•„์š”ํ–ˆ์„๊นŒ์š”? ์ „ํ†ต์ ์ธ Unix ์‹œ์Šคํ…œ์—์„œ๋Š” ํŠน์ • ์ž‘์—…(privileged operations)์„ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด ๋ฌด์กฐ๊ฑด root ๊ถŒํ•œ์ด ํ•„์š”ํ–ˆ์–ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด:

โ€ข 1024๋ฒˆ ์ดํ•˜์˜ ํฌํŠธ ๋ฐ”์ธ๋”ฉ ๐Ÿ”Œ
โ€ข ์‹œ์Šคํ…œ ์‹œ๊ฐ„ ๋ณ€๊ฒฝ โฐ
โ€ข ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ โšก
โ€ข ํŒŒ์ผ ์†Œ์œ ๊ถŒ ๋ณ€๊ฒฝ ๐Ÿ“
โ€ข ๋„คํŠธ์›Œํฌ ์ธํ„ฐํŽ˜์ด์Šค ์„ค์ • ๐ŸŒ

์ด๋Ÿฐ ์ž‘์—…๋“ค์„ ์œ„ํ•ด ํ”„๋กœ๊ทธ๋žจ์— setuid root ๋น„ํŠธ๋ฅผ ์„ค์ •ํ•˜๋ฉด, ๊ทธ ํ”„๋กœ๊ทธ๋žจ์€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ์™„์ „ํ•œ root ๊ถŒํ•œ์„ ๊ฐ–๊ฒŒ ๋ผ์š”. ๋ณด์•ˆ ๊ด€์ ์—์„œ ๋ณด๋ฉด ์ •๋ง ์œ„ํ—˜ํ•œ ์ผ์ด์ฃ ! ๋งŒ์•ฝ ๊ทธ ํ”„๋กœ๊ทธ๋žจ์— ๋ฒ„๊ทธ๋‚˜ ์ทจ์•ฝ์ ์ด ์žˆ๋‹ค๋ฉด? ๊ณต๊ฒฉ์ž๊ฐ€ ์‹œ์Šคํ…œ ์ „์ฒด๋ฅผ ์žฅ์•…ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฑฐ์˜ˆ์š”. ๐Ÿ˜ฑ

โš ๏ธ ๋ณด์•ˆ ๊ฒฝ๊ณ : setuid root ํ”„๋กœ๊ทธ๋žจ์€ ํ•ญ์ƒ ๋ณด์•ˆ ๊ฐ์‚ฌ์˜ ์ตœ์šฐ์„  ๋Œ€์ƒ์ž…๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•˜๋ฉด capabilities๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ•„์š”ํ•œ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ์•ˆ์ „ํ•ด์š”!

๐Ÿ” Capabilities์˜ ๊ตฌ์กฐ์™€ ์ข…๋ฅ˜

๋ฆฌ๋ˆ…์Šค capabilities๋Š” root ๊ถŒํ•œ์„ ์•ฝ 40์—ฌ ๊ฐœ์˜ ๋…๋ฆฝ์ ์ธ ๊ถŒํ•œ์œผ๋กœ ๋‚˜๋ˆ ์š”. ๊ฐ capability๋Š” CAP_๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง€๊ณ  ์žˆ์ฃ . ์ฃผ์š” capabilities๋ฅผ ์‚ดํŽด๋ณผ๊นŒ์š”?

๐ŸŒ ๋„คํŠธ์›Œํฌ ๊ด€๋ จ Capabilities

CAP_NET_BIND_SERVICE
1024๋ฒˆ ์ดํ•˜์˜ ํŠน๊ถŒ ํฌํŠธ(privileged port)์— ๋ฐ”์ธ๋”ฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด์—์š”. ์›น ์„œ๋ฒ„๋‚˜ ๋ฉ”์ผ ์„œ๋ฒ„์ฒ˜๋Ÿผ 80๋ฒˆ, 443๋ฒˆ ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์— ํ•„์ˆ˜์ ์ด์ฃ !

CAP_NET_ADMIN
๋„คํŠธ์›Œํฌ ์ธํ„ฐํŽ˜์ด์Šค ์„ค์ •, ๋ผ์šฐํŒ… ํ…Œ์ด๋ธ” ์ˆ˜์ •, ๋ฐฉํ™”๋ฒฝ ๊ทœ์น™ ์„ค์ • ๋“ฑ ๋„คํŠธ์›Œํฌ ๊ด€๋ฆฌ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋„คํŠธ์›Œํฌ ๊ด€๋ฆฌ ๋„๊ตฌ์— ํ•„์š”ํ•œ ๊ถŒํ•œ์ด์—์š”.

CAP_NET_RAW
RAW ์†Œ์ผ“๊ณผ PACKET ์†Œ์ผ“์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด์—์š”. ping ๋ช…๋ น์–ด๋‚˜ ๋„คํŠธ์›Œํฌ ์Šค๋‹ˆํผ ๊ฐ™์€ ๋„๊ตฌ์— ํ•„์š”ํ•˜์ฃ . ๐Ÿ”ง

๐Ÿ’พ ํŒŒ์ผ ์‹œ์Šคํ…œ ๊ด€๋ จ Capabilities

CAP_DAC_OVERRIDE
ํŒŒ์ผ์˜ ์ฝ๊ธฐ, ์“ฐ๊ธฐ, ์‹คํ–‰ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ์–ด์š”. DAC๋Š” Discretionary Access Control์˜ ์•ฝ์ž์˜ˆ์š”. ๋ฐฑ์—… ํ”„๋กœ๊ทธ๋žจ ๊ฐ™์€ ๊ณณ์—์„œ ์œ ์šฉํ•˜๊ฒŒ ์“ฐ์ด์ฃ .

CAP_DAC_READ_SEARCH
ํŒŒ์ผ ์ฝ๊ธฐ์™€ ๋””๋ ‰ํ† ๋ฆฌ ๊ฒ€์ƒ‰ ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ์–ด์š”. CAP_DAC_OVERRIDE๋ณด๋‹ค๋Š” ์ œํ•œ์ ์ธ ๊ถŒํ•œ์ด์—์š”.

CAP_CHOWN
ํŒŒ์ผ์˜ ์†Œ์œ ์ž(UID)์™€ ๊ทธ๋ฃน(GID)์„ ์ž„์˜๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด์—์š”. ํŒŒ์ผ ๊ด€๋ฆฌ ๋„๊ตฌ์— ํ•„์š”ํ•˜์ฃ .

CAP_FOWNER
ํŒŒ์ผ ์†Œ์œ ์ž๊ฐ€ ์•„๋‹ˆ์–ด๋„ ํŒŒ์ผ์˜ ๊ถŒํ•œ, ํƒ€์ž„์Šคํƒฌํ”„ ๋“ฑ์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์–ด์š”. ๐Ÿ“

โš™๏ธ ์‹œ์Šคํ…œ ๊ด€๋ฆฌ ๊ด€๋ จ Capabilities

CAP_SYS_ADMIN
๋‹ค์–‘ํ•œ ์‹œ์Šคํ…œ ๊ด€๋ฆฌ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด์—์š”. ๋งˆ์šดํŠธ, ์Šค์™‘ ์„ค์ •, ํ˜ธ์ŠคํŠธ๋ช… ๋ณ€๊ฒฝ ๋“ฑ ์ •๋ง ๋งŽ์€ ์ž‘์—…์ด ํฌํ•จ๋˜์–ด ์žˆ์–ด์„œ "๋งŒ๋Šฅ capability"๋ผ๊ณ  ๋ถˆ๋ฆฌ๊ธฐ๋„ ํ•ด์š”. ํ•˜์ง€๋งŒ ๊ทธ๋งŒํผ ์กฐ์‹ฌํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜์ฃ ! โšก

CAP_SYS_TIME
์‹œ์Šคํ…œ ์‹œ๊ฐ„๊ณผ ํ•˜๋“œ์›จ์–ด ํด๋Ÿญ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด์š”. NTP ๋ฐ๋ชฌ ๊ฐ™์€ ์‹œ๊ฐ„ ๋™๊ธฐํ™” ํ”„๋กœ๊ทธ๋žจ์— ํ•„์š”ํ•ด์š”.

CAP_SYS_BOOT
์‹œ์Šคํ…œ์„ ์žฌ๋ถ€ํŒ…ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด์—์š”. reboot() ์‹œ์Šคํ…œ ์ฝœ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ฃ .

CAP_SYS_MODULE
์ปค๋„ ๋ชจ๋“ˆ์„ ๋กœ๋“œํ•˜๊ณ  ์–ธ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์–ด์š”. ์‹œ์Šคํ…œ ๋ณด์•ˆ์— ๋งค์šฐ ์ค‘์š”ํ•œ ๊ถŒํ•œ์ด๋ผ ์‹ ์ค‘ํ•˜๊ฒŒ ๋‹ค๋ค„์•ผ ํ•ด์š”! ๐Ÿ”

๐Ÿ‘ค ํ”„๋กœ์„ธ์Šค ๊ด€๋ จ Capabilities

CAP_KILL
๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœ์„ธ์Šค์— ์‹œ๊ทธ๋„์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์–ด์š”. ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ ๋„๊ตฌ์— ํ•„์š”ํ•œ ๊ถŒํ•œ์ด์ฃ .

CAP_SETUID / CAP_SETGID
ํ”„๋กœ์„ธ์Šค์˜ UID์™€ GID๋ฅผ ์ž„์˜๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์–ด์š”. ์‚ฌ์šฉ์ž ์ „ํ™˜์ด ํ•„์š”ํ•œ ๋ฐ๋ชฌ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์‚ฌ์šฉ๋ผ์š”.

CAP_SYS_PTRACE
๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค๋ฅผ ptrace()๋กœ ์ถ”์ ํ•˜๊ณ  ๋””๋ฒ„๊น…ํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋””๋ฒ„๊ฑฐ๋‚˜ ํ”„๋กœํŒŒ์ผ๋ง ๋„๊ตฌ์— ํ•„์š”ํ•˜์ฃ . ๐Ÿ›

CAP_SYS_NICE
ํ”„๋กœ์„ธ์Šค์˜ ์šฐ์„ ์ˆœ์œ„(nice value)๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ , ์‹ค์‹œ๊ฐ„ ์Šค์ผ€์ค„๋ง ์ •์ฑ…์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ”’ ๋ณด์•ˆ ๊ด€๋ จ Capabilities

CAP_SETPCAP
๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค์˜ capabilities๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋งค์šฐ ๊ฐ•๋ ฅํ•œ ๊ถŒํ•œ์ด๋ผ ์กฐ์‹ฌํ•ด์•ผ ํ•ด์š”!

CAP_SETFCAP
ํŒŒ์ผ์— capabilities๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด์—์š”. ์‹œ์Šคํ…œ ๊ด€๋ฆฌ์ž์—๊ฒŒ ํ•„์š”ํ•œ ๊ถŒํ•œ์ด์ฃ .

CAP_SYS_CHROOT
chroot() ์‹œ์Šคํ…œ ์ฝœ์„ ์‚ฌ์šฉํ•ด์„œ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ƒŒ๋“œ๋ฐ•์Šค ํ™˜๊ฒฝ ๊ตฌ์ถ•์— ์œ ์šฉํ•ด์š”. ๐Ÿ“ฆ

๐ŸŽญ Capabilities์˜ ์„ธ ๊ฐ€์ง€ ์„ธํŠธ

๊ฐ ํ”„๋กœ์„ธ์Šค์™€ ํŒŒ์ผ์€ capabilities๋ฅผ ์„ธ ๊ฐ€์ง€ ์„ธํŠธ๋กœ ๊ด€๋ฆฌํ•ด์š”. ์ด๊ฒŒ ์ข€ ๋ณต์žกํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ฐจ๊ทผ์ฐจ๊ทผ ์„ค๋ช…ํ•ด๋“œ๋ฆด๊ฒŒ์š”! ๐Ÿ˜Š

๐Ÿ“‹ ํ”„๋กœ์„ธ์Šค Capability ์„ธํŠธ

1. Permitted (P)
ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” 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 ์„ธํŠธ

1. Permitted (ํŒŒ์ผ)
ํŒŒ์ผ์ด ์‹คํ–‰๋  ๋•Œ ํ”„๋กœ์„ธ์Šค์˜ permitted ์„ธํŠธ์— ์ž๋™์œผ๋กœ ์ถ”๊ฐ€๋˜๋Š” capabilities์˜ˆ์š”.

2. Inheritable (ํŒŒ์ผ)
ํ”„๋กœ์„ธ์Šค์˜ inheritable ์„ธํŠธ์™€ AND ์—ฐ์‚ฐ๋˜์–ด ์ƒˆ ํ”„๋กœ์„ธ์Šค์˜ permitted ์„ธํŠธ์— ์ถ”๊ฐ€๋ผ์š”.

3. Effective (ํŒŒ์ผ)
ํŒŒ์ผ์˜ ๊ฒฝ์šฐ ์ด๊ฑด ๋‹จ์ˆœํžˆ ๋น„ํŠธ ํ”Œ๋ž˜๊ทธ์˜ˆ์š”. ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ์‹คํ–‰ ์‹œ permitted ์„ธํŠธ์˜ ๋ชจ๋“  capability๊ฐ€ effective ์„ธํŠธ๋กœ ์ž๋™ ํ™œ์„ฑํ™”๋ผ์š”! ๐Ÿš€
๐Ÿ’ก ์ดํ•ด๋ฅผ ๋•๋Š” ๋น„์œ : Permitted๋Š” "์ง€๊ฐ‘์— ์žˆ๋Š” ๋ชจ๋“  ์นด๋“œ", Effective๋Š” "์ง€๊ธˆ ์†์— ๋“ค๊ณ  ์žˆ๋Š” ์นด๋“œ", Inheritable์€ "์ž๋…€์—๊ฒŒ ๋ฌผ๋ ค์ค„ ์ˆ˜ ์žˆ๋Š” ์นด๋“œ"๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์‰ฌ์›Œ์š”!
Capability ์„ธํŠธ ๊ฐ„์˜ ๊ด€๊ณ„ ํ”„๋กœ์„ธ์Šค Capabilities Permitted (P) ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ตœ๋Œ€ ๊ถŒํ•œ Effective (E) ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ ๊ถŒํ•œ Inheritable (I) ์ƒ์† ๊ฐ€๋Šฅํ•œ ๊ถŒํ•œ Bounding / Ambient ํŒŒ์ผ Capabilities File Permitted ์‹คํ–‰ ์‹œ ๋ถ€์—ฌ๋  ๊ถŒํ•œ File Inheritable ์ƒ์† ์กฐ๊ฑด๋ถ€ ๊ถŒํ•œ File Effective (bit) ์ž๋™ ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ execve() ์‹คํ–‰ ์‹œ

๐Ÿ› ๏ธ Capabilities ์‹ค์ „ ํ™œ์šฉ๋ฒ•

์ด์ œ ์‹ค์ œ๋กœ capabilities๋ฅผ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ณผ๊นŒ์š”? ๋ฆฌ๋ˆ…์Šค์—์„œ๋Š” ์—ฌ๋Ÿฌ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์–ด์š”!

๐Ÿ”ง getcap๊ณผ setcap ๋ช…๋ น์–ด

๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋„๊ตฌ๋Š” getcap๊ณผ setcap์ด์—์š”. ํŒŒ์ผ์˜ capabilities๋ฅผ ํ™•์ธํ•˜๊ณ  ์„ค์ •ํ•˜๋Š” ๋ช…๋ น์–ด์ฃ .

ํŒŒ์ผ์˜ 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
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ: setcap์€ ํŒŒ์ผ์˜ extended attributes์— ์ •๋ณด๋ฅผ ์ €์žฅํ•ด์š”. ํŒŒ์ผ์„ ๋ณต์‚ฌํ•  ๋•Œ cp -a๋‚˜ rsync -X๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด capabilities๊ฐ€ ์‚ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์–ด์š”!

๐Ÿ” capsh - Capability Shell

capsh๋Š” capabilities๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ณ  ๋””๋ฒ„๊น…ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•œ ๋„๊ตฌ์˜ˆ์š”.

ํ˜„์žฌ ํ”„๋กœ์„ธ์Šค์˜ 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 ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ์š”!

๊ธฐ๋ณธ ์˜ˆ์ œ - Capability ํ™•์ธํ•˜๊ธฐ:

#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
๊ณ ๊ธ‰ ์˜ˆ์ œ - Capability ์„ค์ •ํ•˜๊ธฐ:

#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;
}
์ด ์ฝ”๋“œ๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ์‹œ์ž‘ํ•  ๋•Œ ๋ถˆํ•„์š”ํ•œ ๊ถŒํ•œ์„ ๋ชจ๋‘ ๋ฒ„๋ฆฌ๊ณ  ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์œ ์ง€ํ•˜๋Š” ํŒจํ„ด์ด์—์š”. ๋ณด์•ˆ ๊ฐ•ํ™”์˜ ํ•ต์‹ฌ ๊ธฐ๋ฒ•์ด์ฃ ! ๐Ÿ”
โœ… Best Practice: ํ”„๋กœ๊ทธ๋žจ์€ ์‹œ์ž‘ ์‹œ ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ capabilities๋งŒ ์œ ์ง€ํ•˜๊ณ , ์ž‘์—…์ด ๋๋‚˜๋ฉด ์ฆ‰์‹œ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์ด ์ข‹์•„์š”. "์ตœ์†Œ ๊ถŒํ•œ ์›์น™(Principle of Least Privilege)"์„ ํ•ญ์ƒ ๊ธฐ์–ตํ•˜์„ธ์š”!

๐ŸŽ“ ์‹ค์ „ ์‹œ๋‚˜๋ฆฌ์˜ค์™€ ์˜ˆ์ œ

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ ์ƒํ™ฉ์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ์‚ดํŽด๋ณผ๊นŒ์š”? ์‹ค๋ฌด์—์„œ ์ž์ฃผ ๋งˆ์ฃผ์น˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋“ค์ด์—์š”! ๐Ÿ’ผ

๐ŸŒ ์‹œ๋‚˜๋ฆฌ์˜ค 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);
    }
});
๐Ÿ’ก Pro Tip: ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” Node.js ๋ฐ”์ด๋„ˆ๋ฆฌ ์ž์ฒด์— capability๋ฅผ ์ฃผ๋Š” ๊ฒƒ๋ณด๋‹ค, ํŠน์ • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐ”์ด๋„ˆ๋ฆฌ์—๋งŒ ์ฃผ๋Š” ๊ฒƒ์ด ๋” ์•ˆ์ „ํ•ด์š”. ๋˜๋Š” systemd์˜ AmbientCapabilities๋ฅผ ํ™œ์šฉํ•  ์ˆ˜๋„ ์žˆ์–ด์š”!

๐Ÿ” ์‹œ๋‚˜๋ฆฌ์˜ค 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: ์‹œ๊ฐ„ ๋™๊ธฐํ™” ๋ฐ๋ชฌ

์‹œ์Šคํ…œ ์‹œ๊ฐ„์„ ์กฐ์ •ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ์š”?

C ํ”„๋กœ๊ทธ๋žจ ์˜ˆ์ œ:

#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(&current_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๋ฅผ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด์š”!

Docker์—์„œ ํŠน์ • capability๋งŒ ๋ถ€์—ฌํ•˜๊ธฐ:

# ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  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
โœ… ์ปจํ…Œ์ด๋„ˆ ๋ณด์•ˆ Best Practice: ์ปจํ…Œ์ด๋„ˆ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋งŽ์€ capabilities๋ฅผ ๊ฐ€์ง€๊ณ  ์‹œ์ž‘ํ•ด์š”. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ํ•ญ์ƒ --cap-drop=ALL๋กœ ์‹œ์ž‘ํ•ด์„œ ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์ถ”๊ฐ€ํ•˜์„ธ์š”!

๐Ÿ” ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ๊ณผ ํ•จ์ •

Capabilities๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ง€๋งŒ, ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์˜คํžˆ๋ ค ๋ณด์•ˆ ์œ„ํ—˜์ด ๋  ์ˆ˜ ์žˆ์–ด์š”. ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ๋“ค์„ ์•Œ์•„๋ณผ๊นŒ์š”? ๐Ÿ˜ฐ

โš ๏ธ ์ผ๋ฐ˜์ ์ธ ์‹ค์ˆ˜๋“ค

1. CAP_SYS_ADMIN์˜ ๊ณผ๋„ํ•œ ์‚ฌ์šฉ

CAP_SYS_ADMIN์€ "๋งŒ๋Šฅ capability"๋ผ๊ณ  ๋ถˆ๋ฆด ์ •๋„๋กœ ๋งŽ์€ ๊ถŒํ•œ์„ ํฌํ•จํ•ด์š”. ์ด๊ฑธ ๋ถ€์—ฌํ•˜๋Š” ๊ฑด ๊ฑฐ์˜ root ๊ถŒํ•œ์„ ์ฃผ๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•ด์š”! ๐Ÿ˜ฑ

๋‚˜์œ ์˜ˆ:
$ sudo setcap 'cap_sys_admin=+ep' /usr/bin/myapp
์ข‹์€ ์˜ˆ:
์ •ํ™•ํžˆ ์–ด๋–ค ์ž‘์—…์ด ํ•„์š”ํ•œ์ง€ ํŒŒ์•…ํ•˜๊ณ , ๋” ๊ตฌ์ฒด์ ์ธ capability๋ฅผ ์ฐพ์•„๋ณด์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด ๋งˆ์šดํŠธ ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค๋ฉด CAP_SYS_ADMIN ๋Œ€์‹  ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.
2. ํŒŒ์ผ ๋ณต์‚ฌ ์‹œ Capabilities ์†์‹ค

์ผ๋ฐ˜์ ์ธ 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
3. Ambient Capabilities์˜ ์œ„ํ—˜์„ฑ

Ambient capabilities๋Š” ํŽธ๋ฆฌํ•˜์ง€๋งŒ, ์ž์‹ ํ”„๋กœ์„ธ์Šค์— ์ž๋™์œผ๋กœ ์ „ํŒŒ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์˜ํ•ด์•ผ ํ•ด์š”!

// ์œ„ํ—˜ํ•œ ์ฝ”๋“œ
prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_RAW, 0, 0);
system("/bin/bash");  // bash๋„ CAP_NET_RAW๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋จ!
์ž์‹ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์ „์— ambient capabilities๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ฒŒ ์•ˆ์ „ํ•ด์š”.

๐Ÿ›ก๏ธ ๋ณด์•ˆ ๊ฐ•ํ™” ๊ธฐ๋ฒ•

1. Securebits ํ™œ์šฉํ•˜๊ธฐ

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 ๋ณ€ํ˜•: ํ•ด๋‹น ์„ค์ •์„ ์ž ๊ฐ€์„œ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ฆ
2. No New Privileges ํ”Œ๋ž˜๊ทธ

์ด ํ”Œ๋ž˜๊ทธ๋ฅผ ์„ค์ •ํ•˜๋ฉด ํ”„๋กœ์„ธ์Šค์™€ ๊ทธ ์ž์‹๋“ค์ด ๋” ๋งŽ์€ ๊ถŒํ•œ์„ ์–ป์„ ์ˆ˜ ์—†์–ด์š”!

#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
3. Capability Bounding Set ์ œํ•œ

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๊ฐ€ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š”์ง€ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•ด์š”!

์‹œ์Šคํ…œ ์ „์ฒด์—์„œ 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,...
๐Ÿ’ก ๋ชจ๋‹ˆํ„ฐ๋ง Tip: auditd๋ฅผ ์„ค์ •ํ•ด์„œ capabilities ์‚ฌ์šฉ์„ ๋กœ๊น…ํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋ณด์•ˆ ๊ฐ์‚ฌ์— ๋งค์šฐ ์œ ์šฉํ•˜์ฃ !

# /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
๋ณด์•ˆ ์œ„ํ—˜ vs ์•ˆ์ „ํ•œ ์‚ฌ์šฉ ์œ„ํ—˜ํ•œ ํŒจํ„ด CAP_SYS_ADMIN ๋‚จ์šฉ Ambient caps ๋ฌด๋ถ„๋ณ„ ์‚ฌ์šฉ Capabilities ๊ฒ€์ฆ ์—†์Œ ํŒŒ์ผ ๋ณต์‚ฌ ์‹œ ์†์‹ค ๋ชจ๋‹ˆํ„ฐ๋ง ๋ถ€์žฌ ๊ถŒํ•œ ์ œ๊ฑฐ ์‹คํŒจ ๋ณด์•ˆ ์ทจ์•ฝ์  ์ฆ๊ฐ€ ์•ˆ์ „ํ•œ ํŒจํ„ด ์ตœ์†Œ ๊ถŒํ•œ ์›์น™ Securebits ํ™œ์šฉ No new privileges Bounding set ์ œํ•œ ์ •๊ธฐ์  ๊ฐ์‚ฌ ์ž‘์—… ํ›„ ๊ถŒํ•œ ์ œ๊ฑฐ ๊ฐ•๋ ฅํ•œ ๋ณด์•ˆ ์ฒด๊ณ„

๐Ÿ”ง 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 Best Practice: systemd-analyze security ๋ช…๋ น์–ด๋กœ ์„œ๋น„์Šค์˜ ๋ณด์•ˆ ์ ์ˆ˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”!

$ systemd-analyze security mywebserver.service

โ†’ Overall exposure level for mywebserver.service: 4.2 MEDIUM ๐Ÿ™
  
โœ“ PrivateTmp=yes
โœ“ NoNewPrivileges=yes
โœ— PrivateDevices=no
โœ— ProtectKernelModules=no
...

๐ŸŽ›๏ธ Systemd์˜ ๊ณ ๊ธ‰ ๋ณด์•ˆ ๊ธฐ๋Šฅ

1. SystemCallFilter - ์‹œ์Šคํ…œ ์ฝœ ํ•„ํ„ฐ๋ง

ํŠน์ • ์‹œ์Šคํ…œ ์ฝœ๋งŒ ํ—ˆ์šฉํ•˜๊ฑฐ๋‚˜ ์ฐจ๋‹จํ•  ์ˆ˜ ์žˆ์–ด์š”. seccomp๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜์ฃ !

[Service]
# ๋„คํŠธ์›Œํฌ ๊ด€๋ จ ์‹œ์Šคํ…œ ์ฝœ๋งŒ ํ—ˆ์šฉ
SystemCallFilter=@network-io @basic-io @file-system

# ๋˜๋Š” ์œ„ํ—˜ํ•œ ์‹œ์Šคํ…œ ์ฝœ ์ฐจ๋‹จ
SystemCallFilter=~@privileged @resources @obsolete

# ํŠน์ • ์‹œ์Šคํ…œ ์ฝœ ๋ช…์‹œ์  ์ฐจ๋‹จ
SystemCallFilter=~execve execveat
์ฃผ์š” ์‹œ์Šคํ…œ ์ฝœ ๊ทธ๋ฃน:
โ€ข @basic-io: ๊ธฐ๋ณธ ์ž…์ถœ๋ ฅ
โ€ข @network-io: ๋„คํŠธ์›Œํฌ ์ž‘์—…
โ€ข @privileged: ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ์ž‘์—…
โ€ข @resources: ๋ฆฌ์†Œ์Šค ์ œํ•œ ๋ณ€๊ฒฝ
โ€ข @obsolete: ๊ตฌ์‹ ์‹œ์Šคํ…œ ์ฝœ
2. RestrictAddressFamilies - ๋„คํŠธ์›Œํฌ ์ œํ•œ

[Service]
# IPv4์™€ IPv6๋งŒ ํ—ˆ์šฉ
RestrictAddressFamilies=AF_INET AF_INET6

# ๋˜๋Š” Unix ์†Œ์ผ“๋งŒ ํ—ˆ์šฉ
RestrictAddressFamilies=AF_UNIX
3. ํŒŒ์ผ ์‹œ์Šคํ…œ ์ ‘๊ทผ ์ œ์–ด

[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

python-prctl ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ:

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

JNA๋ฅผ ์‚ฌ์šฉํ•œ Capabilities ์ œ์–ด:

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

caps crate ์‚ฌ์šฉ:

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

cap ๋ชจ๋“ˆ ์‚ฌ์šฉ:

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
๐Ÿ’ก ์–ธ์–ด๋ณ„ Tip: ๊ฐ ์–ธ์–ด๋งˆ๋‹ค capabilities๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ฐฉ์‹์ด ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฅด์ง€๋งŒ, ํ•ต์‹ฌ ๊ฐœ๋…์€ ๋™์ผํ•ด์š”. ํ”„๋กœ์ ํŠธ์— ๋งž๋Š” ์–ธ์–ด์™€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ ํƒํ•˜์„ธ์š”! ์žฌ๋Šฅ๋„ท์—์„œ ๊ฐ ์–ธ์–ด๋ณ„ ์ „๋ฌธ๊ฐ€์˜ ์กฐ์–ธ์„ ๊ตฌํ•  ์ˆ˜๋„ ์žˆ์–ด์š”.

๐Ÿ”ฌ ๋””๋ฒ„๊น…๊ณผ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

Capabilities๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณผ๊นŒ์š”? ๐Ÿ”ง

โŒ ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ๋“ค

๋ฌธ์ œ 1: "Operation not permitted" ์—๋Ÿฌ

$ ./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
๋ฌธ์ œ 2: Capabilities๊ฐ€ ์‹คํ–‰ ์‹œ ์‚ฌ๋ผ์ง

์›์ธ:
โ€ข ์Šคํฌ๋ฆฝํŠธ ์ธํ„ฐํ”„๋ฆฌํ„ฐ(#!/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
๋ฌธ์ œ 3: SELinux/AppArmor์™€์˜ ์ถฉ๋Œ

์›์ธ:
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

๐Ÿ” ๋””๋ฒ„๊น… ๋„๊ตฌ์™€ ๊ธฐ๋ฒ•

1. ์ƒ์„ธํ•œ Capability ์ •๋ณด ํ™•์ธ

#!/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
2. Strace๋กœ ์‹œ์Šคํ…œ ์ฝœ ์ถ”์ 

# 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
3. Capability ํ…Œ์ŠคํŠธ ํ”„๋กœ๊ทธ๋žจ

#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๋ณด๋‹ค ์•ฝ๊ฐ„ ๋น ๋ฅด๊ฑฐ๋‚˜ ๋น„์Šทํ•œ ์„ฑ๋Šฅ์„ ๋ณด์—ฌ์š”! ๐Ÿš€

๐ŸŽฏ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์•ˆ์ „ํ•œ ๋„คํŠธ์›Œํฌ ์„œ๋ฒ„ ๊ตฌ์ถ•

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์•ˆ์ „ํ•œ ๋„คํŠธ์›Œํฌ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ์š”? ๐Ÿ—๏ธ

๐Ÿ“‹ ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์‚ฌํ•ญ

โ€ข 80๋ฒˆ ํฌํŠธ์—์„œ HTTP ์„œ๋ฒ„ ์‹คํ–‰
โ€ข 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๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ผญ ์ง€์ผœ์•ผ ํ•  ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค๋ฅผ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ์š”! ๐Ÿ“

1. ์ตœ์†Œ ๊ถŒํ•œ ์›์น™ (Principle of Least Privilege)

โ€ข ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ 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);
2. ๋ฐฉ์–ด์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ (Defensive Programming)

โ€ข 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);
3. ๊ณ„์ธต์  ๊ถŒํ•œ ์ œ๊ฑฐ (Layered Privilege Dropping)

๊ถŒํ•œ์„ ์ œ๊ฑฐํ•  ๋•Œ๋Š” ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•ด์š”:

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();
4. ํ…Œ์ŠคํŠธ์™€ ๊ฒ€์ฆ

โ€ข ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์ถฉ๋ถ„ํžˆ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”
โ€ข ๊ฐ 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
5. ๋ฌธ์„œํ™”

โ€ข ์™œ ํŠน์ • 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
```
6. ์ •๊ธฐ์ ์ธ ๊ฐ์‚ฌ

โ€ข ์‹œ์Šคํ…œ์˜ ๋ชจ๋“  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๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ง€๋งŒ ๋งŒ๋Šฅ์€ ์•„๋‹ˆ์—์š”. SELinux, AppArmor, seccomp, namespaces ๋“ฑ ๋‹ค๋ฅธ ๋ณด์•ˆ ๊ธฐ์ˆ ๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ๊ฐ•๋ ฅํ•œ ๋ณด์•ˆ ์ฒด๊ณ„๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์–ด์š”!

๐ŸŽฌ ๋งˆ๋ฌด๋ฆฌ

์™€! ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์ฃ ? ๐ŸŽ‰ ๋ฆฌ๋ˆ…์Šค capabilities์— ๋Œ€ํ•ด ์ •๋ง ๋งŽ์€ ๊ฒƒ์„ ๋ฐฐ์› ์–ด์š”!

์šฐ๋ฆฌ๋Š” capabilities๊ฐ€ ๋ฌด์—‡์ธ์ง€, ์™œ ํ•„์š”ํ•œ์ง€๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ, ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€, ์–ด๋–ค ํ•จ์ •์ด ์žˆ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๊นŒ์ง€ ๋ชจ๋‘ ์‚ดํŽด๋ดค์–ด์š”. ๐Ÿ˜Š

ํ•ต์‹ฌ ํฌ์ธํŠธ๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ •๋ฆฌํ•˜๋ฉด:

๐Ÿ”น Capabilities๋Š” root ๊ถŒํ•œ์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ๋‚˜๋ˆ ์„œ ํ•„์š”ํ•œ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜์š”
๐Ÿ”น ์ตœ์†Œ ๊ถŒํ•œ ์›์น™์„ ํ•ญ์ƒ ๊ธฐ์–ตํ•˜์„ธ์š” - ํ•„์š”ํ•œ ๊ฒƒ๋งŒ, ํ•„์š”ํ•œ ๋งŒํผ๋งŒ!
๐Ÿ”น Systemd์™€ ํ†ตํ•ฉํ•˜๋ฉด ๋”์šฑ ๊ฐ•๋ ฅํ•œ ๋ณด์•ˆ ์ฒด๊ณ„๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์–ด์š”
๐Ÿ”น ๋‹ค์–‘ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ capabilities๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”
๐Ÿ”น ํ…Œ์ŠคํŠธ, ๋ชจ๋‹ˆํ„ฐ๋ง, ๋ฌธ์„œํ™”๋Š” ํ•„์ˆ˜์˜ˆ์š”!

๋ณด์•ˆ์€ ํ•œ ๋ฒˆ ์„ค์ •ํ•˜๊ณ  ๋๋‚˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์ง€์†์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ๊ฐœ์„ ํ•ด์•ผ ํ•˜๋Š” ๊ณผ์ •์ด์—์š”. ์—ฌ๋Ÿฌ๋ถ„์˜ ์‹œ์Šคํ…œ๊ณผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— capabilities๋ฅผ ์ ์šฉํ•˜๋ฉด์„œ, ๋” ์•ˆ์ „ํ•˜๊ณ  ๊ฒฌ๊ณ ํ•œ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ๋งŒ๋“ค์–ด๊ฐ€์‹œ๊ธธ ๋ฐ”๋ž„๊ฒŒ์š”! ๐Ÿ’ช

๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๊ฑฐ๋‚˜ ๋” ๊นŠ์ด ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ๋ฆฌ๋ˆ…์Šค ๋ณด์•ˆ ์ „๋ฌธ๊ฐ€๋“ค๊ณผ ์†Œํ†ตํ•ด๋ณด์„ธ์š”. ํ•จ๊ป˜ ๋ฐฐ์šฐ๊ณ  ์„ฑ์žฅํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ๋น ๋ฅธ ๊ธธ์ด๋‹ˆ๊นŒ์š”! ๐Ÿš€

Happy coding, and stay secure! ๐Ÿ›ก๏ธโœจ

๐ŸŽ“ ์ด ๊ธ€์ด ๋„์›€์ด ๋˜์…จ๋‚˜์š”?
์žฌ๋Šฅ๋„ท์—์„œ ๋” ๋งŽ์€ ์ง€์‹์„ ๊ณต์œ ํ•˜๊ณ  ๋ฐฐ์›Œ๋ณด์„ธ์š”!

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0