Search
🙏

컨테이너 공격 표면 최소화: Linux Capabilities로 Root 권한 제거하기

웹 서버를 운영할 때 흔히 마주치는 딜레마가 있습니다.
웹 서버는 표준 포트인 80번을 사용해야 합니다
그런데 리눅스에서 1024번 미만 포트는 Root 권한이 필요합니다
Root로 실행하면 해킹 시 전체 시스템이 위험해집니다

기초 사전 지식: 왜 Root가 위험한가

컨테이너를 Root로 실행하면, 해당 컨테이너가 해킹당했을 때 공격자가 Root 권한을 획득합니다. 최악의 경우 Container Escape를 통해 호스트 서버 전체를 장악할 수 있습니다.
[해커] → [Nginx 취약점 공격] → [컨테이너 Root 획득] → [호스트 탈출] → [전체 시스템 장악]
Plain Text
복사

대안책: Linux Capabilities

Linux Capabilities는 Root 권한을 잘게 쪼개어 필요한 것만 부여하는 기능입니다.
Capability
기능
NET_BIND_SERVICE
1024 미만 포트 바인딩
NET_RAW
Raw 소켓 생성 (ping 등)
SYS_TIME
시스템 시간 변경
SYS_ADMIN
다양한 관리 작업
이 중 NET_BIND_SERVICE만 부여하면 Root가 아니어도 80번 포트를 열 수 있습니다.

Step 1: ConfigMap 생성

Root 권한을 버리면 Nginx/var/log, /var/run 같은 시스템 경로에 쓸 수 없습니다. 모든 쓰기 경로를 /tmp로 우회하는 설정을 만듭니다.
01-configmaps.yaml 파일을 생성해보죠.
apiVersion: v1 kind: ConfigMap metadata: name: secure-nginx-conf data: nginx.conf: | worker_processes 1; error_log /tmp/error.log warn; pid /tmp/nginx.pid; events { worker_connections 1024; } http { access_log /tmp/access.log; client_body_temp_path /tmp/client_body; proxy_temp_path /tmp/proxy; fastcgi_temp_path /tmp/fastcgi; uwsgi_temp_path /tmp/uwsgi; scgi_temp_path /tmp/scgi; include /etc/nginx/mime.types; include /etc/nginx/conf.d/*.conf; } default.conf: | server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; } } index.html: | {"status": "ok", "message": "Secure Nginx Running"}
YAML
복사
kubectl apply -f 01-configmaps.yaml 출력 확인: # configmap/secure-nginx-conf created
Bash
복사

Step 2: 보안 설정이 적용된 Pod 생성

02-secure-pod.yaml 파일을 생성하세요. securityContext 부분이 핵심입니다.
apiVersion: v1 kind: Pod metadata: name: secure-webserver labels: app: secure-nginx spec: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 securityContext: runAsUser: 1000 # Root(0)가 아닌 일반 유저 runAsGroup: 3000 allowPrivilegeEscalation: false # 권한 상승 차단 capabilities: drop: ["ALL"] # 모든 Capability 제거 add: ["NET_BIND_SERVICE"] # 80번 포트 바인딩만 허용 volumeMounts: - name: config mountPath: /etc/nginx/nginx.conf subPath: nginx.conf - name: config mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf - name: config mountPath: /usr/share/nginx/html/index.html subPath: index.html volumes: - name: config configMap: name: secure-nginx-conf
YAML
복사
각 설정 값 비교
설정
효과
runAsUser
1000
UID 1000번 유저로 실행 (Root 아님)
runAsGroup
3000
GID 3000번 그룹으로 실행
allowPrivilegeEscalation
false
SetUID 등을 통한 권한 상승 차단
capabilities.drop
["ALL"]
모든 특수 권한 제거
capabilities.add
["NET_BIND_SERVICE"]
80번 포트 바인딩 권한만 부여
kubectl apply -f 02-secure-pod.yaml
Bash
복사
Pod 상태를 확인합니다.
kubectl get pod secure-webserver
Bash
복사
Running 상태가 되면 성공입니다.
NAME READY STATUS RESTARTS AGE secure-webserver 1/1 Running 0 10s
Plain Text
복사

Step 3: 동작 확인

Nginx가 80번 포트에서 정상 동작하는지 확인합니다.
kubectl port-forward pod/secure-webserver 8080:80
Bash
복사
다른 터미널에서 요청을 보냅니다.
curl http://localhost:8080
Bash
복사
정상 응답이 오면 성공입니다.
{"status": "ok", "message": "Secure Nginx Running"}
JSON
복사

Step 4: 보안 효과 검증

이제 해커가 컨테이너에 침투했다고 가정하고, 어떤 공격이 차단되는지 확인해봅니다.
컨테이너 셸에 접속합니다.
kubectl exec -it secure-webserver -- sh
Bash
복사

테스트 1: 시스템 파일 변조

echo "hack" >> /etc/hosts
Bash
복사
결과:
sh: can't create /etc/hosts: Permission denied
Plain Text
복사
차단 이유: runAsUser: 1000이므로 Root 소유 파일에 쓸 수 없습니다.

테스트 2: 패키지 설치 (해커는 악성 패키지 설치 가정)

apk add curl
Bash
복사
결과:
ERROR: Unable to lock database: Permission denied
Plain Text
복사
차단 이유: 패키지 매니저는 Root 권한이 필요합니다.

테스트 3: 현재 사용자 확인

id
Bash
복사
결과:
uid=1000 gid=3000 groups=3000
Plain Text
복사
Root(uid=0)가 아닌 일반 유저로 실행 중임을 확인할 수 있습니다.

Step 5: 추가 보안 강화 (Ping 차단)

최신 리눅스 커널은 기본적으로 일반 유저도 ping을 사용할 수 있습니다.
cat /proc/sys/net/ipv4/ping_group_range
Bash
복사
0 2147483647
Plain Text
복사
이 범위는 "거의 모든 사용자가 ping 가능"을 의미합니다.
네트워크 정찰까지 차단하려면 sysctls 설정을 추가합니다.
03-secure-pod-hardened.yaml 파일을 생성하세요.
apiVersion: v1 kind: Pod metadata: name: secure-webserver-hardened labels: app: secure-nginx spec: securityContext: sysctls: - name: net.ipv4.ping_group_range value: "1 0" # Ping 비활성화 containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 securityContext: runAsUser: 1000 runAsGroup: 3000 allowPrivilegeEscalation: false capabilities: drop: ["ALL"] add: ["NET_BIND_SERVICE"] volumeMounts: - name: config mountPath: /etc/nginx/nginx.conf subPath: nginx.conf - name: config mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf - name: config mountPath: /usr/share/nginx/html/index.html subPath: index.html volumes: - name: config configMap: name: secure-nginx-conf
YAML
복사
주목할 점은 sysctls가 Pod 레벨(spec.securityContext)에, capabilities는 컨테이너 레벨(containers[].securityContext)에 정의된다는 것입니다.
kubectl apply -f 03-secure-pod-hardened.yaml
Bash
복사

Ping 차단 확인

kubectl exec -it secure-webserver-hardened -- sh
Bash
복사
ping 8.8.8.8
Bash
복사
결과:
PING 8.8.8.8 (8.8.8.8): 56 data bytes ping: permission denied (are you root?)
Plain Text
복사
네트워크 정찰도 차단되었죠!

정리: 적용된 보안 계층

계층
설정
차단하는 공격
신분 제한
runAsUser: 1000
시스템 파일 변조, 패키지 설치
권한 상승 차단
allowPrivilegeEscalation: false
SetUID 악용, Container Escape
Capability 최소화
drop: ["ALL"], add: ["NET_BIND_SERVICE"]
대부분의 시스템 조작
커널 설정
sysctls: ping_group_range
네트워크 정찰 (ping)

트러블슈팅

Pod가 CrashLoopBackOff 상태일 때

Nginx 로그를 확인합니다.
kubectl logs secure-webserver
Bash
복사
흔한 원인:
에러 메시지
원인
해결
Permission denied: /var/log
로그 경로가 /tmp로 변경되지 않음
nginx.conf의 error_log 경로 확인
bind() failed: Permission denied
NET_BIND_SERVICE가 없음
capabilities.add 확인

sysctls 적용이 안 될 때

클러스터에서 unsafe sysctls가 허용되어 있는지 확인합니다. kubelet 설정에 다음이 필요할 수 있습니다.
--allowed-unsafe-sysctls=net.ipv4.ping_group_range
Plain Text
복사

참고 자료