새벽 3시, 고객사 서버 로그에 이상한 패턴이 쌓입니다. root, admin, ubuntu 계정으로 초당 수십 번 로그인을 시도하는 기록들. 전 세계 수백 개 IP에서 쏟아지는 무차별 대입 공격입니다. 패스워드 123456, password, admin1234를 하나씩 넣어봅니다. 운이 나쁘면 한 번에 뚫립니다.
공개 IP에 22번 포트가 열려있는 모든 서버는 지금 이 순간도 같은 공격을 받고 있습니다. 이 모듈은 그 공격을 구조적으로 차단하는 방법을 다룹니다.
- 1SSH 공개키 인증 구조를 이해하고 키를 생성해 서버에 등록할 수 있다
- 2sshd_config 핵심 보안 설정을 적용하고 sshd -t로 검증한 뒤 안전하게 재로드할 수 있다
- 3배스천 호스트 구조를 이해하고 ProxyJump로 내부 서버에 접속할 수 있다
- 4SSH 접속 시도 로그를 확인하고 fail2ban으로 무차별 대입을 차단할 수 있다
- 5Permission denied (publickey) 등 흔한 SSH 오류를 권한 문제와 설정 관점에서 해결할 수 있다
systemctl status sshd 2>/dev/null || systemctl status ssh 2>/dev/nullsudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak && echo backup OKSSH 인증 방식
패스워드 인증 vs 공개키 인증
공개 IP에 22번 포트를 열어둔 서버는 매 순간 자동화된 무차별 대입 공격의 표적이 됩니다. 패스워드 인증은 네트워크로 전달되는 값이 노출되거나 추측될 수 있어, 짧거나 단순한 비밀번호는 수만 건의 시도 끝에 뚫립니다. 공개키 인증은 비밀키가 클라이언트를 절대 벗어나지 않는 구조로, 서버에 탈취당할 정보가 없고 수학적으로 추측이 불가능합니다.

22번 포트를 인터넷에 열어둔 서버는 지금 이 순간에도 자동화된 무차별 대입 공격을 받고 있습니다. 사무실 동료 계정이 password123이었다면, 수십만 개 비밀번호를 자동으로 시도하는 봇이 결국 뚫습니다. 한 번 뚫리면 그 서버를 발판 삼아 내부 네트워크로 이동하고, DB 서버까지 도달합니다. 공개키 인증은 이 경로를 구조적으로 차단합니다. 비밀키(private key)는 절대 클라이언트를 벗어나지 않습니다. 서버는 공개키(public key)만 갖고 있으며, 로그인 시 수학적 서명 검증으로 인증합니다. 추측하거나 가로챌 것이 없습니다.
# 키 쌍 생성
ssh-keygen -t ed25519 -C "infra-admin@company.com"
# 프로덕션에서는 패스프레이즈 반드시 설정
# 공개키를 서버에 등록
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@192.168.1.100
# 서버에서 권한 설정 (틀리면 인증 자체를 거부함)
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
핵심 권한 규칙:
| 대상 | 권한 |
|---|---|
~/.ssh/ 디렉터리 | 700 — 본인만 접근 |
~/.ssh/authorized_keys | 600 — 본인만 읽기/쓰기 |
~/.ssh/id_ed25519 (비밀키) | 600 — 본인만 읽기/쓰기 |
sshd 설정 강화
sshd_config 핵심 보안 설정
기본 SSH 설정 그대로 운영 중인 서버는 보안 감사에서 예외 없이 지적을 받습니다. root 직접 로그인이 허용되어 있거나, 패스워드 인증이 켜져 있으면 — 공개 IP에 노출된 서버는 무차별 대입 공격의 직접 타겟입니다. 설정 하나하나가 공격 표면을 줄이는 역할을 하며, 잘못 적용하면 서버에 영구히 잠길 수 있습니다. 반드시 현재 세션을 유지한 채 새 터미널로 테스트합니다.
sudo vi /etc/ssh/sshd_config
# 기본 22는 자동화 스캐너가 항상 시도 — 비표준 포트로 노이즈 감소
Port 22222
# root 직접 로그인 금지 — 감사 추적을 위해 일반 계정 로그인 후 sudo
PermitRootLogin no
# 패스워드 인증 비활성화 — 공개키 등록 완료 확인 후 변경
PasswordAuthentication no
PubkeyAuthentication yes
# 허용할 계정 명시 — 목록에 없는 계정은 키가 있어도 접속 불가
AllowUsers infra-admin deploy-bot
# 인증 시간 초과 및 재시도 제한
LoginGraceTime 60
MaxAuthTries 4
PermitEmptyPasswords no
X11Forwarding no
설정 후 반드시 이 순서를 지킵니다.
# 1단계: 문법 검증 (오류 있으면 여기서 중단)
sudo sshd -t && echo CONFIG OK
# 2단계: 재로드 (기존 연결 유지, restart 아님)
sudo systemctl reload sshd
# 3단계: 새 터미널에서 접속 테스트 — 현재 세션 유지한 채
배스천 호스트 구조
단일 진입점으로 공격 표면 최소화
내부 서버 10대가 각각 22번 포트를 인터넷에 열어두면 공격자에게 10개의 진입 기회를 주는 셈입니다. 그중 하나라도 취약한 설정이 있으면 전체 내부망이 위험해집니다. 배스천 호스트는 외부에서 SSH로 접근 가능한 서버를 단 한 대로 줄이고, 나머지 내부 서버는 프라이빗 네트워크에만 존재하게 만듭니다. 접속 경로가 단일화되면 보안 설정 집중도가 높아지고, 모든 SSH 접근 기록이 한 곳에 남아 감사 추적도 훨씬 쉬워집니다.

내부 서버 10대가 각각 22번 포트를 인터넷에 열면 공격 목표가 10개입니다. 배스천 호스트(Bastion Host)는 인터넷과 내부 네트워크 사이의 단일 진입점으로, 내부 서버는 공인 IP 없이 배스천을 통해서만 접근합니다. 모든 SSH 접속 로그가 한 곳에 집중되어 감사(audit) 추적도 쉬워집니다.
# ProxyJump 방식 — 배스천을 경유해 내부 서버로 직접 접속
ssh -J infra-admin@bastion.company.com admin@10.0.1.50
# ~/.ssh/config에 등록해 단축 사용
# ~/.ssh/config
Host bastion
HostName bastion.company.com
User infra-admin
Port 22222
IdentityFile ~/.ssh/id_ed25519
Host web-01
HostName 10.0.1.50
User admin
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
Host db-01
HostName 10.0.2.10
User dbadmin
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
# 권한 설정
chmod 600 ~/.ssh/config
# 등록 후 사용 — bastion 경유가 자동으로 처리됨
ssh web-01
ssh db-01
SSH 접속 모니터링
로그 확인과 fail2ban 무차별 대입 방어
무차별 대입 공격은 막고 있다고 생각했는데, 로그를 보니 하루에 수만 건의 실패 기록이 쌓이고 있었습니다. 동일한 IP에서 반복 시도하는데도 차단이 안 됐고, 결국 어느 날 취약한 비밀번호를 가진 계정이 뚫렸습니다. fail2ban은 지정 횟수 이상 실패한 IP를 방화벽에서 자동으로 차단합니다. 로그를 모니터링하지 않으면 이미 뚫리고 있다는 사실을 모를 수 있습니다.
# 접속 시도 로그 확인
sudo tail -50 /var/log/secure # RHEL/Rocky
sudo tail -50 /var/log/auth.log # Ubuntu/Debian
sudo journalctl -u sshd --since "1 hour ago" --no-pager # systemd 공통
# 실패한 로그인 시도만 필터
sudo grep "Failed password" /var/log/secure | tail -20
# 무차별 대입 IP 상위 10개
sudo grep "Failed password" /var/log/secure | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -10
# 성공한 로그인 이력 / 실패 이력 / 계정별 마지막 로그인
last -20 && sudo lastb | head -10 && lastlog | grep -v "Never logged"
fail2ban은 로그를 감시하다가 지정 횟수 이상 실패하면 해당 IP를 방화벽에서 자동 차단합니다.
sudo dnf install -y fail2ban # RHEL/Rocky
sudo apt-get install -y fail2ban # Ubuntu
# /etc/fail2ban/jail.local — jail.conf는 수정 금지, local에 오버라이드
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = 22222
logpath = /var/log/secure
maxretry = 3
bantime = 86400
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd # 차단 현황 확인
sudo fail2ban-client set sshd unbanip 45.33.32.156 # 실수 차단 해제
- ~/.ssh/ 에 비밀키와 .pub 공개키 두 파일이 생성되었는가
- 비밀키 권한이 -rw------- (600)인가
- 서버의 authorized_keys 권한이 -rw------- (600)인가
- ssh -o PasswordAuthentication=no 로 패스워드 없이 접속되는가
- journalctl -u sshd 에서 Accepted publickey 로그가 찍히는가
- /etc/ssh/sshd_config.bak 파일이 존재하는가
- sshd -t 결과 오류 없이 종료되었는가
- systemctl status sshd 에서 active (running) 상태인가
- 새 터미널에서 공개키 인증으로 접속이 성공하는가
- 패스워드로 접속 시도 시 Permission denied (publickey) 가 뜨는가
실제 업무에서 이 지식이 쓰이는 상황:
금융권과 공공기관 서버 운영에서 SSH 접근 통제는 정보보호 관리체계(ISMS-P) 심사 항목입니다. 심사관이 실제로 확인하는 것:
grep "PermitRootLogin" /etc/ssh/sshd_config # → no
grep "PasswordAuthentication" /etc/ssh/sshd_config # → no
grep "AllowUsers\|AllowGroups" /etc/ssh/sshd_config # 허용 목록 존재
sudo fail2ban-client status sshd # 차단 현황
sudo lastb | head -20 # 실패 로그인 이력
AWS로 처음 이전하는 스타트업이 흔히 겪는 실수가 내부 서버 보안 그룹에 0.0.0.0/0:22를 열어두는 것입니다. 올바른 구성은 배스천 EC2(공인 IP, 특정 사무실 IP만 허용) → 내부 서버(프라이빗 서브넷, 배스천 IP만 허용) 구조입니다. 이 구조에서 개발자 PC가 탈취되어도 배스천 계정과 키까지 없으면 내부 서버에 도달할 수 없습니다.
다음 모듈에서는 OS 보안 패치와 미들웨어 버전 업 절차를 다룹니다.