조회수: 0
목차
왜 SSH 무차별 공격을 신경 써야 할까
인터넷에 공개된 리눅스 서버라면 거의 예외 없이 SSH 포트를 향한 무차별 대입(브루트포스) 공격 로그가 쌓인다. 비밀번호가 충분히 강하더라도, 계속해서 접속 시도가 들어오면 다음과 같은 문제가 생긴다.
- auth.log, secure 로그 급격한 증가로 로그 관리 부담
- 인증 시도로 인한 CPU·메모리 사용량 상승
- 취약한 계정이나 오래된 계정이 남아 있을 경우 실제 침해 위험
fail2ban 같은 전용 도구를 쓰면 편하지만, 작은 서버나 단순한 환경에서는 패키지 설치 없이 기본 도구와 쉘 스크립트만으로도 어느 정도 방어 체계를 갖출 수 있다.
전체 구조 한눈에 이해하기
쉘 스크립트로 SSH 무차별 공격을 막는 기본 방식은 다음 네 단계로 정리할 수 있다.
- SSH 실패 로그를 주기적으로 읽어온다.
- 실패 횟수가 기준치를 넘는 IP를 추출한다.
- 해당 IP를 iptables/nftables에 차단 규칙으로 추가한다.
- 일정 기간이 지나면 차단 목록을 정리하거나, 필요시 해제한다.
이 네 단계만 이해하면 나머지는 구현 디테일 차이일 뿐이다. 아래에서는 가장 단순하면서도 바로 적용할 수 있는 패턴을 예제로 살펴본다.
SSH 로그 위치와 포맷 이해하기
우선 어떤 로그를 기준으로 실패 시도를 판단할지 확인해야 한다. 대표적인 배포판 기준으로 SSH 관련 인증 로그는 다음에 기록된다.
- Debian/Ubuntu 계열:
/var/log/auth.log - CentOS/RHEL/AlmaLinux 계열:
/var/log/secure
로그 파일에는 다음과 비슷한 형태의 문장이 남는다.
Failed password for root from 1.2.3.4 port 54321 ssh2
Invalid user test from 5.6.7.8 port 60000 ssh2
여기서 공통된 패턴은 Failed password 또는 Invalid user 뒤에 공격 IP가 from x.x.x.x 형식으로 나온다는 점이다. 이 부분을 grep과 awk로 뽑아내면 된다.
예를 들어 Ubuntu 계열에서 최근 SSH 실패 IP 리스트를 보려면 다음처럼 확인할 수 있다.
grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr
여기서 $(NF-3)는 끝에서 세 번째 필드, 즉 from 뒤에 나오는 IP 주소를 의미한다. 이 값들을 기준으로 일정 횟수 이상 실패한 IP를 차단 대상으로 삼을 수 있다.
기본 차단 스크립트 예제 만들기
이제 실제로 주기적으로 실행할 쉘 스크립트를 하나 작성해 보자. 예시는 Ubuntu 계열, iptables를 사용하는 환경을 기준으로 한다.
#!/bin/bash
LOGFILE="/var/log/auth.log"
THRESHOLD=10
BANLIST="/var/log/ssh_banned_ip.list"
grep "Failed password" "$LOGFILE" \
| awk '{print $(NF-3)}' \
| grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' \
| sort \
| uniq -c \
| while read COUNT IP; do
if [ "$COUNT" -ge "$THRESHOLD" ]; then
# 이미 차단된 IP인지 확인
if ! iptables -C INPUT -s "$IP" -j DROP 2>/dev/null; then
iptables -A INPUT -s "$IP" -j DROP
echo "$(date '+%F %T') $IP $COUNT" >> "$BANLIST"
fi
fi
done
이 스크립트의 핵심 포인트는 다음과 같다.
THRESHOLD=10: 실패 횟수가 10회 이상인 IP만 차단BANLIST: 언제 어떤 IP를 차단했는지 기록해 두는 파일iptables -C: 이미 동일 규칙이 있는지 체크 후 중복 추가 방지iptables -A: INPUT 체인에 해당 IP DROP 규칙 추가
실제 서버 환경에서는 /usr/local/sbin/ssh_ban.sh 같은 위치에 저장하고 실행 권한을 부여한다.
chmod +x /usr/local/sbin/ssh_ban.sh
cron에 등록해 자동으로 실행하기
수동으로 스크립트를 실행할 수도 있지만, SSH 공격은 상시 발생하므로 일정 간격으로 자동 실행되도록 cron에 등록하는 편이 좋다.
root 계정에서 다음 명령으로 크론 편집 화면을 연다.
crontab -e
그리고 예를 들어 5분마다 스크립트를 실행하고 싶다면 다음 한 줄을 추가한다.
*/5 * * * * /usr/local/sbin/ssh_ban.sh >/dev/null 2>&1
이렇게 하면 매 5분마다 최근 로그를 기준으로 새로운 공격 IP를 찾아 차단 규칙에 추가하게 된다.
주의할 점은 로그 파일이 순환(rotatelog)되면 예전 공격 정보는 사라진다는 점이다. 너무 긴 기간을 커버하려 하기보다, 일정 시간 동안의 공격에 대해 빠르게 차단하는 데 초점을 두는 편이 합리적이다.
nftables 환경에서의 응용
최근 배포판에서는 iptables 대신 nftables를 사용하는 경우가 많다. nftables를 쓰더라도 기본 아이디어는 같다. 단, 명령어 문법만 조금 달라진다.
예를 들어 간단한 ssh_blacklist 체인을 하나 만들고, 스크립트에서는 그 체인에 차단할 IP를 추가하는 방식으로 구성할 수 있다.
nft 설정 예시(한 번만 실행):
nft add table inet filter
nft add chain inet filter ssh_blacklist "{ type filter hook input priority 0; }"
nft add rule inet filter ssh_blacklist tcp dport 22 ip saddr { } drop
그리고 쉘 스크립트에서는 다음과 같이 IP를 ruleset에 추가하는 식으로 응용할 수 있다.
nft add element inet filter ssh_blacklist { $IP }
환경마다 초기 설정 방법이 다를 수 있으므로, 운영 중인 배포판의 nftables 문서를 함께 확인하면서 적용하는 편이 안전하다.
한 번 막은 IP를 어떻게 관리할 것인가
fail2ban은 일정 시간이 지나면 자동으로 차단을 해제하는 기능을 제공한다. 단순 쉘 스크립트로 구현할 때는 다음과 같은 전략을 선택할 수 있다.
- 영구 차단
- SSH 포트에 반복적으로 실패를 남기는 IP는 다시 들어올 가능성이 낮다고 보고 계속 막아 둔다.
- 규칙 수가 너무 많아지면 iptables 성능에 영향을 줄 수 있어 주기적인 정리 필요.
- 기간 제한 차단
BANLIST에 시간 정보까지 남겨 두고, 별도 스크립트에서 오래된 규칙을 삭제한다.- 예: 7일이 지난 IP는 iptables에서 삭제.
예를 들어 오래된 규칙을 정리하는 간단한 스크립트는 다음과 같이 만들 수 있다.
#!/bin/bash
BANLIST="/var/log/ssh_banned_ip.list"
DAYS=7
tmpfile=$(mktemp)
while read DATE TIME IP COUNT; do
if [ "$(date -d "$DATE" +%s)" -ge "$(date -d "-$DAYS days" +%s)" ]; then
echo "$DATE $TIME $IP $COUNT" >> "$tmpfile"
else
iptables -D INPUT -s "$IP" -j DROP 2>/dev/null
fi
done < "$BANLIST"
mv "$tmpfile" "$BANLIST"
이 스크립트를 하루에 한 번 cron으로 돌리면, 7일보다 오래된 차단 규칙을 자동으로 정리할 수 있다.
실수 방지를 위한 체크포인트
쉘 스크립트로 방화벽을 자동 조작할 때는 작은 실수 하나로 정상 사용자까지 차단될 수 있다. 운영 전에 다음 항목을 점검하는 것이 좋다.
- 본인의 고정 IP, 사내망 IP가 차단 대상에 포함되지 않는지
- 특정 국가나 프록시를 통째로 막지 않는지, 패턴 매칭이 과도하지 않은지
- 스크립트에 오타가 없는지, 테스트 환경에서 충분히 검증했는지
- 서버에 직접 접속 가능한 백도어(콘솔, 클라우드 콘솔, VPN 등)를 확보했는지
또한, 처음 도입할 때는 iptables -A INPUT -s IP -j DROP 대신 LOG 타깃을 사용해 실제로 어떤 IP가 후보로 올라오는지부터 확인해 보는 것도 좋은 안전 장치가 된다.
fail2ban 대신 쉘 스크립트를 쓸 때의 한계와 보완책
쉘 스크립트만으로도 기본적인 SSH 무차별 공격 방어는 가능하지만, 다음과 같은 한계가 있다.
- 로그 포맷이 조금만 바뀌어도 스크립트를 수정해야 한다.
- 여러 서비스를 함께 보호하려면 스크립트 복잡도가 급격히 늘어난다.
- 분산 공격, 대규모 IP 풀을 활용한 공격에는 효과가 제한적이다.
따라서 다음과 같은 보완책을 함께 적용하는 편이 좋다.
- SSH 포트 변경(22 포트 대신 다른 포트 사용)
- 비밀번호 로그인 비활성화, 공개키 기반 인증만 허용
- root 직접 로그인 금지, 별도 유저 + sudo 사용
- VPN 뒤에서만 SSH를 열어 두는 구조
이런 기본 보안 설정을 해 둔 상태에서, 쉘 스크립트 차단 로직을 더해 두면 공격 로그와 자원 낭비를 크게 줄일 수 있다.
마무리
fail2ban을 설치하지 않더라도, 리눅스가 기본으로 제공하는 로그와 iptables, 간단한 쉘 스크립트만으로 SSH 무차별 공격을 어느 정도 제어할 수 있다.
로그에서 실패한 IP를 추출하고, 기준 횟수 이상이면 자동으로 방화벽에 차단 규칙을 추가하는 흐름을 한 번 구축해 두면, 같은 패턴의 공격이 들어와도 서버는 훨씬 안정적으로 버틸 수 있다. 운영 환경에서 바로 쓰기 전에는 반드시 테스트 서버에서 충분히 시험해 보고, 차단 목록 관리와 SSH 기본 보안 설정을 함께 적용하는 것이 안정적인 운영에 도움이 된다.