본문 바로가기
Infra2026년 8월 22일7분 읽기

systemd 서비스 하드닝 — systemd-analyze security로 공격면 줄이기

YS
김영삼
조회 3
systemd 서비스 하드닝 — systemd-analyze security로 공격면 줄이기

systemd 서비스 하드닝은 유닛 파일에 샌드박싱 지시어를 추가해서, 서비스가 침해당했을 때 공격자가 할 수 있는 일을 미리 좁혀두는 작업이다. 파일시스템을 읽기 전용으로 만들고, 커널 기능을 뺏고, 불필요한 시스템 콜을 차단하는 식이다. 놀라운 건 이 모든 걸 컨테이너나 별도 도구 없이 ExecStart 주변에 몇 줄 추가하는 것만으로 할 수 있다는 점이다.

내가 이걸 진지하게 파기 시작한 건 systemd-analyze security라는 명령을 우연히 돌려보고 나서였다. 자체 제작한 서비스가 "UNSAFE"라는 빨간 글씨와 함께 노출 점수 9.6을 받은 걸 보고 좀 부끄러웠다. 오늘은 그 점수를 어떻게 1점대까지 내렸는지 적는다.

먼저 현재 상태를 측정한다

하드닝은 감으로 하면 안 된다. 측정 가능한 지표부터 확보한다. systemd에는 서비스별로 노출 점수를 매겨주는 내장 도구가 있다.

systemd-analyze security myapp.service

실행하면 각 지시어별로 exposure(노출도)를 매기고, 마지막에 0(안전)부터 10(위험)까지의 종합 점수와 등급을 보여준다. 처음 돌리면 대부분의 커스텀 서비스가 8~10 사이에 몰려 있을 것이다. 기본값이 "아무 제한 없음"이기 때문이다. 이 명령이 좋은 건, 지시어를 하나 추가할 때마다 점수가 얼마나 떨어지는지 즉시 피드백을 준다는 점이다.

가장 효과 큰 지시어 몇 개

지시어가 수십 개지만, 처음부터 다 넣을 필요는 없다. 투자 대비 효과가 큰 것부터 넣는다. 아래는 내가 거의 모든 서비스에 기본으로 넣는 조합이다.

[Service]
# 파일시스템 보호
ProtectSystem=strict        # /usr, /boot 등 전부 읽기 전용
ProtectHome=true            # /home, /root 접근 차단
ReadWritePaths=/var/lib/myapp   # 쓰기 필요한 곳만 예외
PrivateTmp=true             # 서비스 전용 /tmp 격리

# 권한 상승 차단
NoNewPrivileges=true        # setuid로도 권한 못 올림
RestrictSUIDSGID=true

# 커널 표면 축소
ProtectKernelTunables=true  # /proc/sys 쓰기 금지
ProtectKernelModules=true   # 모듈 로드 금지
ProtectControlGroups=true
RestrictNamespaces=true

# 네트워크·시스템콜
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources

ProtectSystem=strict는 체감 효과가 가장 크다. 이걸 켜면 서비스는 파일시스템 전체를 읽기 전용으로 보게 되고, 쓰기가 필요한 경로만 ReadWritePaths로 명시적으로 열어준다. 만약 서비스가 침해돼도 시스템 바이너리를 덮어쓰거나 백도어를 심는 게 막힌다.

하드닝의 사고방식은 "무엇을 막을까"가 아니라 "무엇을 허용할까"다. 기본을 전부 닫고 필요한 것만 여는 순간, 공격면은 극적으로 줄어든다.

SystemCallFilter, 조금 더 설명

SystemCallFilter=@system-service는 일반적인 서비스가 쓰는 시스템 콜 묶음만 허용하고 나머지는 차단한다. 뒤에 붙인 ~@privileged @resources는 그중에서도 권한·리소스 조작 계열을 추가로 뺀다. 앞의 ~가 "제외"를 뜻한다.

주의할 점 하나. 이 필터를 너무 조이면 앱이 특정 시스템 콜을 못 써서 이상하게 죽는다. 그럴 땐 journalctl에서 SECCOMP 키워드로 로그를 보면 어떤 콜이 막혔는지 나온다. 나는 처음에 @resources를 뺐다가 메모리를 많이 쓰는 워커가 죽어서, 한참 헤맨 적이 있다. 로그를 봤으면 5분에 끝날 일이었다.

지시어별 효과를 표로

지시어막는 것주의
ProtectSystem=strict시스템 파일 변조ReadWritePaths로 쓰기 경로 열기
NoNewPrivileges=true권한 상승sudo 호출하는 서비스는 깨짐
PrivateTmp=true/tmp 통한 공격거의 항상 안전하게 켜기 가능
RestrictAddressFamilies불필요한 소켓UNIX 소켓 쓰면 AF_UNIX 포함

override로 안전하게 적용하기

패키지가 설치한 유닛 파일을 직접 고치면 업데이트 때 덮어써진다. 그래서 나는 항상 drop-in override를 쓴다.

sudo systemctl edit myapp.service

이러면 /etc/systemd/system/myapp.service.d/override.conf가 열리고, 여기 적은 내용이 원본 위에 얹힌다. 하드닝 지시어를 여기 넣고 daemon-reload 후 재시작하면 된다. 한 서비스씩 적용하고, 매번 systemd-analyze security로 점수가 내려가는지, 앱이 여전히 잘 도는지 확인하는 게 안전하다. 개인적으로는 한 번에 다 넣고 뭐가 깨졌는지 몰라 헤매느니, 한 줄씩 넣고 확인하는 게 훨씬 빠르다고 느꼈다.

자주 묻는 질문

systemd 하드닝이 컨테이너 격리를 대체하나요?

완전히 대체하진 않지만 상당 부분 겹칩니다. ProtectSystem, PrivateTmp, RestrictNamespaces 같은 지시어는 컨테이너 없이도 파일시스템·네임스페이스 격리를 제공합니다. 컨테이너를 쓸 수 없는 베어메탈 서비스나 시스템 데몬에 특히 유용합니다.

지시어를 넣었더니 서비스가 안 뜹니다.

대부분 쓰기 경로가 막혔거나 시스템 콜이 차단된 경우입니다. journalctl 로그에서 "Read-only file system" 또는 "SECCOMP"를 검색하세요. 전자는 ReadWritePaths에 해당 경로를 추가하고, 후자는 SystemCallFilter를 완화하면 해결됩니다.

어느 지시어부터 넣는 게 좋나요?

부작용이 거의 없으면서 효과가 큰 것부터 시작하세요. PrivateTmp, NoNewPrivileges, ProtectHome은 대부분의 서비스에서 안전하게 켤 수 있습니다. ProtectSystem=strict와 SystemCallFilter는 앱 동작을 확인하며 신중하게 적용하는 게 좋습니다.

노출 점수를 어디까지 낮춰야 하나요?

0에 집착할 필요는 없습니다. 인터넷에 노출된 서비스라면 3점 이하(OK 등급)를 목표로 잡되, 앱이 정상 동작하는 선에서 멈추면 됩니다. 점수를 낮추려다 기능이 깨지면 본말전도입니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록