
서비스 · 프로세스 관리 38 / 50 · Part 4. 로그·스케줄링·운영
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
30편에서 서비스를 root가 아닌 계정으로 실행하는 것만으로도 피해 범위가 크게 줄어든다는 것을 확인했다. 그러나 전용 계정이라도 읽을 수 있는 파일은 모두 읽고, 쓸 수 있는 곳에는 쓸 수 있다. 웹 앱이 탈취되면 공격자는 그 계정으로 /home을 뒤지고, /tmp에 파일을 두고, 권한 상승을 시도할 수 있다.
systemd는 이런 행동을 서비스 단위로 막는 샌드박스 옵션 을 제공한다. 이번 글에서는 systemd-analyze security로 25편의 labapp 서비스가 얼마나 노출되어 있는지 측정하고, drop-in 하나로 샌드박스를 적용해 점수와 실제 동작이 어떻게 바뀌는지 확인한다.
| 분류 | 설정 | 효과 |
|---|---|---|
| 파일 시스템 | ProtectSystem=strict | / 전체 읽기 전용 (full은 /usr·/boot·/etc만) |
ProtectHome=yes | /home, /root, /run/user 숨김 | |
PrivateTmp=yes | 서비스 전용 /tmp, /var/tmp | |
ReadWritePaths= | strict 상태에서 쓰기를 허용할 경로 | |
StateDirectory= / LogsDirectory= | 서비스 전용 쓰기 디렉터리를 자동 생성 | |
| 권한 | NoNewPrivileges=yes | SUID·파일 Capability로 권한을 늘릴 수 없음 |
CapabilityBoundingSet= | 가질 수 있는 Capability 상한 (30편) | |
| 장치·커널 | PrivateDevices=yes | 실제 장치 접근 차단 |
ProtectKernelTunables= / ProtectKernelModules= | /proc/sys 쓰기, 모듈 적재 차단 | |
ProtectControlGroups=yes | cgroup 수정 차단 | |
| 네트워크·시스템 콜 | RestrictAddressFamilies= | 허용 소켓 종류 제한 |
SystemCallFilter=@system-service | 일반 서비스용 시스템 콜 목록만 허용 | |
RestrictNamespaces=yes | 새 네임스페이스 생성 차단 |
서비스의 샌드박스 설정을 항목별로 평가해 0.0(가장 안전)~10.0(가장 노출) 점수를 매긴다. ✗ 표시된 항목이 개선 후보다. 점수는 설정 기준의 평가이므로, 애플리케이션 자체의 취약점과는 별개다.

systemctl restart labapp (샌드박스 drop-in 적용)
→ PID 1 fork → 자식에서 exec 전에
새 마운트 네임스페이스 생성
/ 를 읽기 전용으로 다시 마운트 (ProtectSystem=strict)
/home 을 빈 디렉터리로 가림 (ProtectHome)
전용 /tmp 를 /tmp/systemd-private-*/ 로 연결 (PrivateTmp)
no_new_privs 플래그 설정 (NoNewPrivileges)
Capability 제거 · seccomp 필터 적용
→ execve(python3 app.py)
이 모든 제한은 커널 기능(네임스페이스·seccomp·Capability) 으로 구현되며, 앱 코드는 전혀 바뀌지 않는다. 설정이 커널에서 지원되지 않으면 서비스가 226/NAMESPACE 등으로 시작 실패하므로(24편), 적용 후 반드시 동작을 확인한다.
# 1) 적용 전 노출 점수와 실제 접근 범위
systemd-analyze security labapp.service --no-pager | tail -3
systemd-analyze security labapp.service --no-pager | grep -E '✗' | head -10
systemd-run --wait -p User=analyst --pipe /bin/bash -c 'ls /home; touch /tmp/probe && echo "/tmp 쓰기 가능"; cat /etc/hostname'
# 2) 샌드박스 drop-in 적용
cat > /etc/systemd/system/labapp.service.d/50-sandbox.conf <<'EOF'
[Service]
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
LockPersonality=yes
CapabilityBoundingSet=
SystemCallFilter=@system-service
EOF
systemctl daemon-reload; systemctl restart labapp; systemctl is-active labapp
systemd-analyze security labapp.service --no-pager | tail -1
python3 -c 'import urllib.request as u; print("HTTP", u.urlopen("http://127.0.0.1:8082/").status)'
# 3) 샌드박스 안에서 본 세상
systemd-run --wait --pipe -p User=analyst -p ProtectSystem=strict -p ProtectHome=yes -p PrivateTmp=yes -p NoNewPrivileges=yes \
/bin/bash -c 'ls /home; touch /tmp/probe && echo "/tmp 쓰기 가능(격리된 tmp)"; ls /tmp; touch /opt/x; sudo -n id | head -1'
ls /tmp | grep -c systemd-private



텍스트 원본(실제 출력):
[root@rocky9-lab ~]# systemd-analyze security labapp.service --no-pager | tail -3
✗ UMask= Files created by service are world-readable by default 0.1
→ Overall exposure level for labapp.service: 9.2 UNSAFE 😨
[root@rocky9-lab ~]# systemd-analyze security labapp.service --no-pager | grep -E '✗' | head -10
✗ RemoveIPC= Service user may leave SysV IPC objects around 0.1
✗ RootDirectory=/RootImage= Service runs within the host's root directory 0.1
✗ CapabilityBoundingSet=~CAP_SYS_TIME Service processes may change the system clock 0.2
✗ NoNewPrivileges= Service processes may acquire new privileges 0.2
✗ PrivateDevices= Service potentially has access to hardware devices 0.2
✗ ProtectClock= Service may write to the hardware clock or system clock 0.2
✗ CapabilityBoundingSet=~CAP_SYS_PACCT Service may use acct() 0.1
✗ CapabilityBoundingSet=~CAP_KILL Service may send UNIX signals to arbitrary processes 0.1
✗ ProtectKernelLogs= Service may read from or write to the kernel log ring buffer 0.2
✗ CapabilityBoundingSet=~CAP_WAKE_ALARM Service may program timers that wake up the system 0.1
[root@rocky9-lab ~]# systemd-run --wait -p User=analyst --pipe /bin/bash -c 'ls /home; touch /tmp/probe && echo "/tmp 쓰기 가능"; cat /etc/hostname' 2>&1
Running as unit: run-u306.service
analyst
/tmp 쓰기 가능
rocky9-lab
Finished with result: success
Main processes terminated with: code=exited/status=0
Service runtime: 14ms
[root@rocky9-lab ~]# cat > /etc/systemd/system/labapp.service.d/50-sandbox.conf <<'EOF'
> [Service]
> NoNewPrivileges=yes
> ProtectSystem=strict
> ProtectHome=yes
> PrivateTmp=yes
> PrivateDevices=yes
> ProtectKernelTunables=yes
> ProtectKernelModules=yes
> ProtectControlGroups=yes
> RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
> RestrictNamespaces=yes
> LockPersonality=yes
> CapabilityBoundingSet=
> SystemCallFilter=@system-service
> EOF
[root@rocky9-lab ~]# systemctl daemon-reload; systemctl restart labapp; sleep 1; systemctl is-active labapp
active
[root@rocky9-lab ~]# systemd-analyze security labapp.service --no-pager | tail -1
→ Overall exposure level for labapp.service: 2.4 OK 🙂
[root@rocky9-lab ~]# python3 -c 'import urllib.request as u; print("HTTP", u.urlopen("http://127.0.0.1:8082/").status)'
HTTP 200
[root@rocky9-lab ~]# systemd-run --wait --pipe -p User=analyst -p ProtectSystem=strict -p ProtectHome=yes -p PrivateTmp=yes -p NoNewPrivileges=yes /bin/bash -c 'ls /home; touch /tmp/probe && echo "/tmp 쓰기 가능(격리된 tmp)"; ls /tmp; touch /opt/x 2>&1; sudo -n id 2>&1 | head -1' 2>&1
Running as unit: run-u311.service
ls: cannot open directory '/home': Permission denied
/tmp 쓰기 가능(격리된 tmp)
probe
touch: cannot touch '/opt/x': Read-only file system
sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
Finished with result: success
Main processes terminated with: code=exited/status=0
Service runtime: 26ms
[root@rocky9-lab ~]# ls /tmp | grep -c systemd-private
4
| 관찰 | 의미 |
|---|---|
적용 전 Overall exposure level ... 9.2 UNSAFE | User=만 지정한 서비스도 설정 기준으로는 거의 무방비다 |
✗ NoNewPrivileges=, PrivateDevices=, ProtectClock=, CapabilityBoundingSet=~CAP_KILL ... | 개선할 항목과 각 항목의 가중치가 표시된다 |
샌드박스 없는 analyst: /home 목록, /tmp 쓰기, /etc/hostname 읽기 성공 | 전용 계정이라도 파일 권한이 허용하는 범위는 모두 접근한다 |
drop-in 적용 후 active, 점수 2.4 OK | 설정 파일 하나로 노출 점수가 크게 낮아졌다 |
HTTP 200 | 웹 앱 기능은 그대로 동작한다. 샌드박싱은 기능을 유지하면서 불필요한 권한만 제거 한다 |
샌드박스 안 ls /home → Permission denied | ProtectHome이 사용자 홈을 가렸다 |
/tmp 쓰기 가능, ls /tmp → probe만 | PrivateTmp로 서비스 전용 /tmp 를 받았다. 다른 서비스나 사용자의 파일은 보이지 않는다 |
touch /opt/x → Read-only file system | ProtectSystem=strict로 전체가 읽기 전용이다 |
sudo → "no new privileges" 거부 | NoNewPrivileges가 SUID 실행을 통한 권한 상승을 막았다 |
호스트 /tmp의 systemd-private-* 4개 | PrivateTmp 서비스들이 실제로 쓰는 디렉터리. 서비스별로 분리되어 있다 |
| 주제 | 내용 |
|---|---|
| 피해 범위 축소 | 웹 앱 취약점으로 명령 실행을 당해도 홈 디렉터리 열람, 시스템 파일 변조, 권한 상승 시도가 커널 수준에서 차단 된다 |
| 우선 적용 대상 | 외부에 노출된 서비스(웹, API, 파일 업로드 처리), 사용자 입력을 파싱하는 서비스 |
| 점수의 한계 | 점수는 "설정이 얼마나 촘촘한가"다. 점수가 낮아도 앱 취약점·자격 증명 노출은 별도로 관리해야 한다 |
| 배포판 기본값 | 최근 배포판의 시스템 서비스(journald, logind, resolved 등)는 이미 샌드박스를 적용하고 있다. systemd-analyze security(인자 없이)로 전체 서비스를 비교할 수 있다 |
| 설정 해제 감시 | 28편처럼 drop-in으로 샌드박스를 끄는 변경 도 가능하다. systemd-delta로 변경을 감시한다 |
[정기 점검] systemd-analyze security --no-pager | sort -k2 -n -r | head -15
→ 노출 점수가 높은 서비스 목록 (EXPOSURE, PREDICATE)
↓
[우선순위] 외부 포트를 연 서비스(16편) ∩ 점수 높은 서비스 → 개선 권고 1순위
↓
[개선] drop-in 으로 옵션 추가 → 기능 테스트 → 점수 재측정 → 변경 이력 기록
↓
[변경 감시] systemd-delta --type=extended ← 샌드박스 drop-in 이 제거·완화되지 않았는가
| 점검 명령 | 목적 |
|---|---|
systemd-analyze security | 전체 서비스 노출 점수 |
systemd-analyze security UNIT | 항목별 상세 |
systemctl show UNIT -p ProtectSystem,NoNewPrivileges,PrivateTmp | 실제 적용 값 |
| 실수 | 결과 | 예방 |
|---|---|---|
ProtectSystem=strict 후 로그·데이터 쓰기 실패 | 서비스 오류 | StateDirectory=, LogsDirectory=, ReadWritePaths= |
ProtectHome=yes인데 앱이 /home 아래에 있음 | 실행 실패 | 앱을 /opt·/srv로, 또는 read-only |
SystemCallFilter를 과하게 좁힘 | 특정 기능에서 SIGSYS 종료 | @system-service부터 시작, 로그로 조정 |
| 점수만 보고 적용 후 테스트 생략 | 운영 장애 | 기능 테스트 필수 |
| 모든 옵션을 한 번에 적용 | 원인 파악 어려움 | 단계적으로 적용 |
[ ] systemd-analyze security 로 노출 점수를 측정했다
[ ] 샌드박스 없는 계정의 접근 범위를 확인했다
[ ] drop-in 으로 샌드박스 옵션을 적용했다
[ ] 점수가 9.2 → 2.4 로 낮아지고 기능은 유지되는 것을 확인했다
[ ] ProtectHome, PrivateTmp, ProtectSystem, NoNewPrivileges 효과를 직접 확인했다
[ ] 샌드박스 완화 변경을 systemd-delta 로 감시할 수 있다
ProtectSystem, ProtectHome, PrivateTmp, NoNewPrivileges, CapabilityBoundingSet이 핵심 옵션이다.systemd-analyze security로 노출 점수를 측정하고 개선 항목을 찾는다 (실측 9.2 → 2.4).다음 글 「39. 부팅 시 자동 실행 경로 총정리」 에서는 지금까지 다룬 systemd unit, timer, cron, at, 셸 초기화 파일을 한데 모아 "이 서버에서 자동으로 실행되는 것" 전체 목록 을 뽑는 점검 스크립트를 만든다. 무해한 항목을 직접 심어 두고 스크립트가 모두 찾아내는지 확인한다.