파일 · 권한 · 사용자 관리 45 / 50 · Part 5. 권한 상승과 Linux 보안
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (Docker 격리 컨테이너, 테스트 계정analyst)
기초편 연계: 「리눅스 시스템 기초」 20. 최소 권한 원칙
지금까지 배운 권한·소유권·특수 권한·sudo·ACL은 모두 하나의 원칙을 향한다. 최소 권한 원칙(Principle of Least Privilege) 이다.
모든 주체(사용자·프로세스·서비스)는 자신의 작업에 필요한 최소한의 권한만 가져야 한다.
이 원칙이 왜 중요한가? 침해는 언제든 일어날 수 있다는 전제 위에서, 침해가 나도 피해를 그 권한 범위 안에 가두기 위해서다. 웹 서버가 root로 돌면 웹 취약점 하나가 전체 장악으로 이어지지만, 전용 계정으로 돌면 피해가 그 계정 권한으로 제한된다.
이번 글에서는 최소 권한을 실제로 구현하는 방법 — 전용 계정, 정확한 파일 권한, capability, systemd 격리 — 을 다룬다.
| 계층 | 최소 권한 수단 |
|---|---|
| 프로세스 | 전용 계정, capability |
| 파일 | 정확한 rwx, ACL(44편) |
| 서비스 | systemd(User=, ProtectSystem=) |
| 권한 상승 | sudo 최소 규칙(42편) |
| 커널 강제 | SELinux / AppArmor |
각 서비스는 전용 계정(nologin 시스템 계정)으로 실행한다. nginx는 nginx, postgres는 postgres 계정으로. 이렇게 하면 한 서비스가 침해돼도 다른 서비스·시스템은 보호된다(40편).
전통적으로 "80 포트 바인딩" 같은 특권 작업은 root가 필요했다. capability 는 root의 권한을 잘게 나눠, 필요한 능력 하나만 부여할 수 있게 한다. 예를 들어 cap_net_bind_service만 주면 root 없이 낮은 포트를 열 수 있다. SUID(전체 root)보다 훨씬 안전하다.

최소 권한 설계의 핵심은 격리 다. /srv/app 예시를 보자.
bin/app: root:appgrp 750 — 실행은 appgrp가 하되, 수정은 root만. 침해된 서비스가 자기 실행 파일을 변조하지 못한다.data/config.yml: appsvc:appgrp 640 — 서비스만 읽는다.logs/: appsvc:appgrp 750 — 서비스만 쓴다.appsvc 계정: nologin 시스템 계정 — 침해돼도 대화형 셸이 없다.이 구성에서 웹 취약점으로 appsvc 권한을 얻은 공격자는 config를 읽고 로그를 쓸 수 있을 뿐, 실행 파일 변조·다른 서비스 접근·root 권한 획득이 막힌다. 피해가 appsvc 권한 안에 갇힌다.
capability는 이를 프로세스 수준에서 강화한다. setcap cap_net_bind_service=+ep app으로 app에 포트 바인딩 능력만 주면, app이 침해돼도 그 능력 외의 root 권한은 없다.
root 권한 실습.
appsvc는 nologin 시스템 계정,/srv/app은 최소 권한으로 구성했다.
# 1) 서비스 전용 계정 (nologin)
id appsvc
# 2) 최소 권한 디렉터리 구조
ls -lR /srv/app
# 3) capability — root 없이 특정 권한만
cp /bin/true /tmp/netbind
setcap 'cap_net_bind_service=+ep' /tmp/netbind && getcap /tmp/netbind
# 4) 시스템에 설정된 capability 파일
getcap -r /usr/bin 2>/dev/null | head -5

텍스트 원본(실제 출력):
[root@rocky9-lab ~]# echo "=== 최소 권한 구성: 서비스 전용 계정 + 최소 파일 권한 ==="
=== 최소 권한 구성: 서비스 전용 계정 + 최소 파일 권한 ===
[root@rocky9-lab ~]# id appsvc
uid=998(appsvc) gid=1001(appgrp) groups=1001(appgrp)
[root@rocky9-lab ~]# ls -lR /srv/app
/srv/app:
total 12
drwxr-xr-x 2 root appgrp 4096 Sep 24 15:07 bin
drwxr-xr-x 2 root appgrp 4096 Sep 24 15:07 data
drwxr-x--- 2 appsvc appgrp 4096 Sep 24 15:07 logs
/srv/app/bin:
total 28
-rwxr-x--- 1 root appgrp 27936 Sep 24 15:07 app
/srv/app/data:
total 4
-rw-r----- 1 appsvc appgrp 11 Sep 24 15:07 config.yml
/srv/app/logs:
total 0
[root@rocky9-lab ~]# echo "=== 서비스가 root 가 아닌 appsvc 로 실행되면 침해 범위 = appsvc 권한 ==="
=== 서비스가 root 가 아닌 appsvc 로 실행되면 침해 범위 = appsvc 권한 ===
[root@rocky9-lab ~]# echo "=== capabilities: root 없이 특정 권한만 (예: 80 포트 바인딩) ==="
=== capabilities: root 없이 특정 권한만 (예: 80 포트 바인딩) ===
[root@rocky9-lab ~]# cp /bin/true /tmp/netbind; setcap 'cap_net_bind_service=+ep' /tmp/netbind 2>/dev/null && getcap /tmp/netbind
/tmp/netbind cap_net_bind_service=ep
[root@rocky9-lab ~]# echo "=== 시스템 전체 SUID 대신 capability 로 최소화 가능 ==="
=== 시스템 전체 SUID 대신 capability 로 최소화 가능 ===
[root@rocky9-lab ~]# getcap -r /usr/bin 2>/dev/null | head -5
/usr/bin/newuidmap cap_setuid=ep
/usr/bin/newgidmap cap_setgid=ep
| 출력 | 해석 |
|---|---|
uid=998(appsvc) gid=1001(appgrp) groups=1001(appgrp) | 시스템 UID(998), 전용 그룹만 |
drwxr-x--- appsvc appgrp logs | 로그 디렉터리는 서비스만 접근 |
-rwxr-x--- root appgrp app | 실행은 appgrp, 수정은 root만 |
-rw-r----- appsvc appgrp config.yml | 설정은 서비스만 읽기 |
/tmp/netbind cap_net_bind_service=ep | root 없이 포트 바인딩 능력만 부여 |
getcap -r /usr/bin → newuidmap cap_setuid=ep 등 | 시스템도 SUID 대신 capability로 최소화한 도구들 |
-rwxr-x--- root appgrp app이 핵심 설계다. 서비스는 실행만 하고 수정은 못 한다. 침해돼도 자기 코드를 바꿔 지속성을 심기 어렵다.
getcap 결과의 newuidmap·newgidmap은 실제 시스템이 최소 권한을 적용한 예다. 예전에는 SUID였을 이 도구들이 이제 필요한 capability(cap_setuid)만 갖는다.
| 원칙 | 적용 |
|---|---|
| 서비스는 전용 계정 | root 실행 금지, nologin 시스템 계정 |
| 실행/수정 분리 | 실행 권한은 서비스, 수정은 root |
| 데이터 최소 노출 | 설정·비밀은 서비스만 읽기(640/600) |
| SUID 대신 capability | 필요한 능력만 부여 |
| systemd 강화 | User=, ProtectSystem=strict, NoNewPrivileges= |
| 커널 강제 | SELinux(Rocky)/AppArmor(Ubuntu)로 추가 격리 |
systemd 유닛 예:
[Service]
User=appsvc
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/srv/app/logs
이 설정은 서비스가 지정 경로 외에 쓰지 못하게 하고, 권한 상승(SUID)을 무력화한다.
[설계 감사] 서비스 실행 계정 점검
ps -eo user,comm | awk '$1=="root"' # root 실행 서비스 검토
systemctl show <svc> -p User -p NoNewPrivileges
↓
[이상] 웹·앱 서비스가 root 실행 / 서비스 계정에 광범위 권한
↓
[Detection] 최소 권한 위반 = 침해 시 피해 확대 요인. 사전 점검으로 완화
↓
[Evidence] 서비스 계정 권한, capability(getcap -r /), SUID 목록(47편)
↓
[Response] 전용 계정 전환, 권한 축소, systemd 강화
| 관점 | 내용 |
|---|---|
| 사전 완화 | 최소 권한은 탐지가 아니라 피해 축소 다. 침해를 막지 못해도 범위를 줄인다 |
| 점검 | root로 도는 서비스, 과도한 capability, 광범위 sudo를 감사 |
| Defense in Depth | 파일 권한 + 계정 격리 + capability + SELinux를 겹친다 |
| 실수 | 결과 | 예방 |
|---|---|---|
| 서비스를 root로 실행 | 침해 시 전체 장악 | 전용 계정 |
편의로 777·광범위 권한 | 최소 권한 위반 | 필요한 최소만 |
| SUID로 특권 부여 | 전체 root 노출 | capability |
| 설정 파일을 넓게 공개 | 비밀 노출 | 640/600, 서비스만 |
| systemd 격리 미사용 | 방어 계층 부족 | ProtectSystem 등 |
[ ] 서비스 전용 nologin 계정을 확인했다
[ ] 실행/수정을 분리한 파일 권한을 확인했다
[ ] setcap 으로 capability 를 부여했다
[ ] getcap 으로 시스템의 capability 파일을 확인했다
[ ] SUID 대신 capability 가 나은 이유를 설명할 수 있다
[ ] 최소 권한이 피해 범위를 가두는 원리를 이해했다
User=·ProtectSystem=·NoNewPrivileges=)와 SELinux/AppArmor로 겹겹이 방어한다.다음 글 「46. World-Writable 파일 점검」 에서는 최소 권한 위반의 대표 사례 — 누구나 쓸 수 있는(world-writable) 파일과 디렉터리 — 를 find로 탐색하고, Sticky Bit 유무로 위험을 구분하는 실전 점검 명령을 다룬다.