대상 OS: Ubuntu Server 24.04 LTS
SSH는 “가장 먼저 두드려지는 문”이고, sshd는 그 문을 여는 데몬입니다.
보통은 방화벽/키 인증/계정정책으로 문 앞을 강화하지만, 오늘은 문 자체(sshd 프로세스)를 더 단단하게 만드는 방법을 다룹니다.
핵심 아이디어는 간단합니다: sshd가 침해되더라도 (취약점, 설정 실수, 0-day 등) 프로세스가 할 수 있는 일을 systemd로 강하게 제한합니다.
이 글은 Ubuntu 24.04의 OpenSSH + systemd를 기준으로, “운영 중 서버에서 안전하게 점진 적용”하는 절차로 설명합니다.
---
0) 전제: sshd는 이미 보안 기능이 많지만, ‘프로세스 격리’는 별개다
OpenSSH는 기본적으로 권한 분리(privilege separation)와 여러 방어기제를 갖고 있습니다.
하지만 서비스 데몬이 뚫렸을 때 피해를 줄이는 “커널/런타임 수준의 격리”는 별도의 층입니다.
systemd의 샌드박싱 옵션은 다음을 특히 잘합니다.
- 파일시스템을 읽기 전용으로 묶기
- /tmp 같은 임시공간을 분리하기
- 커널 인터페이스 접근을 차단하기
- 시스템콜을 좁히기
- 필요 없는 capability를 제거하기
---
1) 적용 전 체크(중요): 현재 sshd 유닛 경로와 설정 확인
먼저 현재 sshd 유닛이 어디서 로드되는지 확인합니다.
systemctl status ssh
systemctl cat ssh
systemctl show -p FragmentPath -p DropInPaths ssh
OpenSSH 데몬 설정 테스트도 “적용 전/후” 동일하게 검증할 수 있게 준비합니다.
sshd -t
sshd -T | head
추가로, 장애 대비를 위해 **현재 SSH 세션을 끊지 말고** 별도의 루트 콘솔(예: KVM/시리얼/클라우드 콘솔)을 확보해 둡니다.
---
2) Drop-in으로만 바꾸기: 패키지 기본 유닛은 건드리지 않는다
Ubuntu에서는 패키지 업데이트로 기본 유닛 파일이 바뀔 수 있으므로, `/etc/systemd/system/ssh.service.d/`에 드롭인을 만들어야 합니다.
sudo systemctl edit ssh
위 명령을 실행하면 편집기가 열립니다. 아래 내용은 **드롭인 파일에 들어갈 ‘예시’**이며, 실제 적용은 단계적으로 하세요.
---
3) 1차 하드닝(리스크 낮음): 파일시스템/임시공간/권한 상승 차단
아래는 일반적으로 충돌이 적고 효과가 큰 옵션들입니다.
- `NoNewPrivileges=yes`: setuid/setcap 같은 “추가 권한 획득” 경로를 막습니다.
- `PrivateTmp=yes`: sshd의 /tmp를 분리해 다른 서비스와 임시파일을 공유하지 않게 합니다.
- `ProtectSystem=strict`: 루트 파일시스템을 거의 읽기전용으로 묶습니다.
- `ProtectHome=yes`: /home 접근을 제한합니다(환경에 따라 영향 있을 수 있음).
드롭인 예시(1차):
sudo mkdir -p /etc/systemd/system/ssh.service.d
sudo tee /etc/systemd/system/ssh.service.d/10-sandboxing-1.conf >/dev/null <<'EOF'
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
# 커널 인터페이스/디바이스 쪽도 같이
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
PrivateDevices=yes
EOF
적용 및 재시작:
sudo systemctl daemon-reload
sudo systemctl restart ssh
sudo systemctl --no-pager status ssh
접속 검증(별도 터미널에서):
ssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no user@server
만약 재시작 후 접속이 안 되면, 콘솔에서 드롭인을 잠시 치우고 롤백합니다.
sudo mv /etc/systemd/system/ssh.service.d/10-sandboxing-1.conf /root/
sudo systemctl daemon-reload
sudo systemctl restart ssh
---
4) 2차 하드닝(중간 리스크): capability 최소화로 ‘sshd가 할 수 있는 것’ 더 줄이기
sshd는 저포트(22/tcp) 바인딩과 인증/세션 관리를 위해 일부 capability가 필요할 수 있습니다.
환경에 따라 다르므로, “무작정 다 깎기”보다 **적정선을 찾는 방식**이 안전합니다.
현재 sshd 프로세스가 어떤 capability로 실행되는지 참고용으로 확인합니다.
pidof sshd
sudo cat /proc/$(pidof sshd | awk '{print $1}')/status | sed -n 's/^CapEff:/CapEff:/p'
권장 접근:
1) 먼저 CapabilityBoundingSet을 넓게 시작한다.
2) 실패가 없으면 조금씩 줄인다.
예시(2차):
sudo tee /etc/systemd/system/ssh.service.d/20-capabilities.conf >/dev/null <<'EOF'
[Service]
# 예시: 필요 최소만 남기는 방향(환경에 따라 조정)
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_CHOWN CAP_DAC_OVERRIDE
AmbientCapabilities=
EOF
재시작 및 점검:
sudo systemctl daemon-reload
sudo systemctl restart ssh
sudo systemctl --no-pager status ssh
주의: 배스천 호스트나 특수 PAM 모듈, Kerberos/SSSD 연동 등 구성에 따라 추가 capability가 필요할 수 있습니다.
---
5) 3차 하드닝(상대적 고위험): SystemCallFilter로 ‘익스플로잇 이후’ 움직임 제한
`SystemCallFilter=`는 강력하지만, 잘못 적용하면 정상 동작도 깨질 수 있습니다.
그래서 운영에서는 아래 순서를 권장합니다.
1) 먼저 Audit(로그)로 “막혔을 때 무엇이 문제인지” 확인할 관측 지점을 만든다.
2) 이후 좁은 allowlist(또는 blocklist)를 점진적으로 적용한다.
우선 관측 강화(로그/진단용)로 `LogLevel=DEBUG3` 같은 sshd 자체 디버그는 운영에 부담이 크니 권하지 않습니다.
대신 systemd 쪽에서 실패 원인을 파악할 수 있게 journal을 확인합니다.
sudo journalctl -u ssh -b --no-pager | tail -n 200
SystemCallFilter는 예시로 “기본 deny가 아니라, 위험한 계열을 먼저 차단”하는 쪽이 현실적입니다.
(완전한 allowlist는 환경별 테스트 부담이 큽니다.)
예시(3차: 위험군 차단 시작):
sudo tee /etc/systemd/system/ssh.service.d/30-syscalls.conf >/dev/null <<'EOF'
[Service]
# 커널/모듈/네임스페이스/마운트 등 침해 후 확장에 자주 쓰는 계열을 차단하는 방향(환경별 검증 필요)
SystemCallFilter=~@mount @swap @reboot @module @raw-io @privileged @obsolete
SystemCallErrorNumber=EPERM
EOF
재시작 후, 실제 로그인/포워딩/세션 유지/서브시스템(SFTP 등) 사용까지 업무 시나리오로 확인합니다.
sudo systemctl daemon-reload
sudo systemctl restart ssh
ssh user@server 'id; uname -a'
장애 시 롤백은 동일합니다.
sudo rm -f /etc/systemd/system/ssh.service.d/30-syscalls.conf
sudo systemctl daemon-reload
sudo systemctl restart ssh
---
6) 네트워크 노출을 더 줄이는 보너스: sshd가 볼 수 있는 주소 패밀리 제한
sshd가 IPv4만 쓰는 환경이라면 IPv6를 끄는 것도 공격면 감소에 도움이 될 수 있습니다.
systemd 레벨에서 주소 패밀리를 제한할 수도 있습니다.
sudo tee /etc/systemd/system/ssh.service.d/40-network.conf >/dev/null <<'EOF'
[Service]
RestrictAddressFamilies=AF_UNIX AF_INET
EOF
적용 후 접속(특히 IPv6 경로)이 필요한 환경인지 반드시 확인하세요.
---
7) 운영 팁: “한 번에 끝내기”가 아니라 ‘안전한 롤아웃’이 답이다
아래 순서로 적용하면 실패 비용을 줄일 수 있습니다.
1) 1차(파일시스템/임시공간/커널 인터페이스)만 먼저 적용하고, 24시간 운영한다.
2) 2차(capability)를 적용하고, 인증 방식/계정/포워딩/배너/로그인 후 작업을 점검한다.
3) 3차(systemcall filter)는 사내 표준 시나리오 테스트 후, 점진적으로 확장한다.
4) 변경 이력은 드롭인 파일 단위로 남기고, 장애 시 “파일 하나 제거 → 재시작”으로 복구 루트를 단순화한다.
현재 적용된 샌드박싱 옵션을 한 번에 확인하는 커맨드:
systemd-analyze security ssh --no-pager
systemctl show ssh | egrep 'NoNewPrivileges|PrivateTmp|ProtectSystem|ProtectHome|SystemCallFilter|CapabilityBoundingSet|RestrictAddressFamilies'
---
마무리
sshd 하드닝은 “설정 한 줄”로 끝나지 않습니다.
하지만 systemd 샌드박싱을 곁들이면, 방화벽/인증정책이 뚫리는 최악의 순간에도 피해 반경을 줄이는 강력한 보험이 됩니다.
특히 외부 노출 서버(배스천, CI 에이전트 게이트웨이, 운영 점프박스)는 sshd를 ‘서비스 하나’로 보고 격리 강도를 끌어올릴 가치가 큽니다.