홈서버를 운영하다보면 반드시 마주치는 것
80, 443 포트 개방 후 바로 개처럼 달려드는.. 봇들

(제발 그만 좀 들어와..)
이것저것 내부적으로 쓰는 것들이야 이중 인증이나 내부 IP로만 사용하도록
굳이 DNS 로 연결하지 않았습니다만 블로그도 운영하고
일부 셀프호스트는 지인들에게까지도 개방하고 있기 때문에...
홈서버에 리버스 프록시를 붙이고 도메인을 연결한 순간, 로그에 이상한 요청이 쌓이기 시작했습니다.
이럴 줄 몰랐냐고요? 알았는데 너무 많이들어와요.. ㅎㅎ
GET /.env HTTP/1.1
GET /.env.backup HTTP/1.1
GET /var/www/.env HTTP/1.1
GET /.env.preprod HTTP/1.1
GET /blog/.git/config HTTP/1.1
GET /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php HTTP/1.1
GET /config/database.yml HTTP/1.1
아무도 모르는 도메인인데, 공개한 지 몇 시간 만에 이런 요청이 들어옵니다. 봇이 IP 대역을 무차별로 스캔하면서 열린 포트를 찾고, 알려진 취약점 경로를 전부 때려보는 거예요.
"내 서버는 작으니까 괜찮겠지"는 착각이었습니다. 크기와 상관없이, 인터넷에 열려 있으면 타겟이 됩니다.
대략.. 1주일 로그를 까봤다
Traefik access log 기준, 1주일 동안:
| 항목 | 수치 |
|---|---|
| 총 요청 수 | ~65,000건 |
| 의심 요청 (버킷 전달) | ~40,000건 |
| 로컬 탐지 → 자동 ban | 50건 |
| 커뮤니티 블랙리스트 차단 | 30,540 IP |
(AI 로 로그를 분류해보니...)
전체 요청의 60% 이상이 의심스러운 트래픽이었습니다. 정상 사용자보다 봇이 더 많이 방문하는 셈이에요.
어떤 공격이 오는가
| 유형 | 건수 | 뭘 노리는지 |
|---|---|---|
| HTTP probing | 236 | 존재하는 경로 탐색 |
| 민감 파일 접근 | 129 | .env, .git/config, DB 설정 |
| 기술 스택 탐지 | 120 | 어떤 프레임워크 쓰는지 파악 |
| Admin 패널 탐색 | 95 | /admin, /wp-admin, /phpmyadmin |
| WordPress 스캔 | 94 | WP 플러그인 취약점 |
| 알려진 CVE 공격 | 46 | ThinkPHP, PHPUnit, Jira 등 |
| 백도어 시도 | 20 | 웹쉘 업로드 시도 |
재밌는 건 — 저는 PHP도 WordPress도 안 쓰는데, 봇은 그런 거 신경 안 씁니다. 가능한 모든 취약점을 전부 시도해봐요.
어디서 오는가
캐나다: 21건 (대부분 Microsoft Azure)
미국: 16건
독일: 6건 (VPS 호스팅 업체)
일본: 3건 (Tencent Cloud)
싱가포르: 3건
클라우드 VPS에서 오는 게 대부분입니다. 공격자가 직접 자기 IP를 쓰는 게 아니라, 클라우드 인스턴스를 빌려서 스캔을 돌리는 거죠.
CrowdSec: 커뮤니티 기반 방어
이걸 수동으로 막으려면 끝이 없습니다. IP 하나 차단해봤자 다른 IP로 또 옵니다.
회사에서 AWS CloudFront + WAF를 운영하면서 느낀 게 있었어요. WAF의 진짜 가치는 개별 룰이 아니라 Managed Rule Group — 즉 AWS나 서드파티가 관리하는 위협 인텔리전스 기반 IP 차단 목록이었습니다. 직접 룰을 하나하나 만드는 건 한계가 있고, 결국 "이미 알려진 악성 IP를 얼마나 빨리 공유받느냐"가 핵심이더라고요.
홈서버에 AWS WAF를 붙일 수는 없지만, 같은 원리로 동작하는 셀프호스팅 솔루션이 CrowdSec이었습니다. 커뮤니티가 곧 Managed Rule Group 역할을 하는 거죠.
CrowdSec의 핵심 아이디어는 단순합니다:
- 로컬 탐지: 내 서버 로그를 분석해서 악성 패턴을 감지하면 ban
- 커뮤니티 공유: 전 세계 CrowdSec 사용자가 탐지한 악성 IP를 공유
- 선제 차단: 다른 서버에서 이미 ban된 IP는 내 서버에 도달하기 전에 차단
약 3만개 IP가 커뮤니티 블랙리스트로 선제 차단되고 있었습니다. 이 IP들은 내 서버를 공격한 적 없지만, 다른 서버를 공격한 이력이 있어서 미리 막혀 있는 거예요.
Traefik + CrowdSec Bouncer 연동
구조는 이렇습니다:
요청 → Traefik → CrowdSec Bouncer 플러그인 → 서비스
│
├─ IP가 블랙리스트에 있음 → 403 차단
└─ 정상 → 통과
Traefik 플러그인으로 동작하니까, 별도 프록시 레이어 없이 기존 구조에 끼워넣을 수 있습니다.
설치
# docker-compose.yml (예시)
services:
crowdsec:
image: crowdsecurity/crowdsec:v1.7
environment:
- COLLECTIONS=crowdsecurity/linux crowdsecurity/traefik
- BOUNCER_KEY_firewall=${CROWDSEC_BOUNCER_KEY}
volumes:
- crowdsec-db:/var/lib/crowdsec/data/
- crowdsec-config:/etc/crowdsec/
- ./traefik/logs:/var/log/traefik:ro # Traefik 로그를 읽어야 함
핵심은 Traefik access log를 CrowdSec에 마운트하는 것. CrowdSec이 이 로그를 실시간으로 파싱하면서 악성 패턴을 감지합니다.
Traefik 플러그인 설정
# traefik 설정
experimental:
plugins:
crowdsec-bouncer:
moduleName: "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
version: "v1.4.1"
# 동적 미들웨어 설정
http:
middlewares:
crowdsec:
plugin:
crowdsec-bouncer:
crowdsecLapiScheme: http
crowdsecLapiHost: crowdsec:8080
crowdsecLapiKey: ${BOUNCER_API_KEY}
이 미들웨어를 원하는 라우터에 붙이면 끝입니다. 저는 외부 노출되는 서비스 전부에 걸어뒀어요.
로컬 탐지: 시나리오 기반
CrowdSec은 "시나리오"라는 단위로 공격을 탐지합니다. 커뮤니티가 만든 시나리오를 설치하면 됩니다.
# 설치된 시나리오 확인
cscli scenarios list
# 주요 시나리오들
crowdsecurity/http-probing # 경로 무차별 탐색
crowdsecurity/http-sensitive-files # .env, .git 등 접근
crowdsecurity/http-admin-interface-probing # 관리자 페이지 탐색
crowdsecurity/http-wordpress-scan # WP 취약점 스캔
crowdsecurity/thinkphp-cve-2018-20062 # ThinkPHP RCE
시나리오가 트리거되면 해당 IP를 자동으로 ban합니다. 기본 4시간, 반복 위반 시 더 길어져요.
실제로 잡힌 것들
현재 활성 ban 목록 일부:
IP: 144.31.xxx.xxx (US) — ThinkPHP CVE + HTTP probing + PHPUnit CVE
→ 하나의 IP가 3가지 공격을 동시에 시도. 전형적인 자동화 스캔.
IP: 45.135.xxx.xxx (DE, VPS 호스팅) — HTTP probing + 민감 파일 접근
→ 11건의 요청을 빠르게 보냄. .env 파일 변형을 전부 시도.
IP: 20.151.xxx.xxx (CA, Azure) — Admin 인터페이스 탐색
→ /admin, /administrator, /wp-admin 등 4개 경로 시도.
패턴이 보이죠? 한 IP가 여러 공격을 동시에 시도합니다. 하나라도 걸리면 즉시 ban되니까, 나머지 시도는 전부 차단됩니다.
fail2ban과 뭐가 다른가
fail2ban도 로그 기반 차단을 하는데, CrowdSec을 선택한 이유:
| fail2ban | CrowdSec | |
|---|---|---|
| 탐지 범위 | 내 서버 로그만 | 내 서버 + 커뮤니티 공유 |
| 선제 차단 | ❌ | ✅ (30,000+ IP 사전 차단) |
| 시나리오 | 정규식 기반 | 구조화된 시나리오 (leaky bucket) |
| Traefik 연동 | 별도 iptables 조작 필요 | 플러그인으로 깔끔하게 |
| 관리 | 텍스트 설정 파일 | CLI + 웹 대시보드 |
커뮤니티 블랙리스트가 결정적이었습니다. 내 서버를 공격하기 전에 이미 차단되어 있으니까, 로그에 남지도 않아요. fail2ban은 "맞고 나서 막는" 방식이고, CrowdSec은 "맞기 전에 막는" 방식입니다.
한계와 주의점
만능은 아닙니다.
오탐 가능성 — 커뮤니티 블랙리스트에 정상 IP가 포함될 수 있어요. 특히 공유 VPN이나 CGNAT 환경에서. 중요한 서비스라면 화이트리스트를 관리해야 합니다.
우회 가능 — IP 기반 차단이라 IP를 바꾸면 끝입니다. 정교한 공격자는 매번 새 IP를 쓰니까요. 이건 CrowdSec만의 한계가 아니라 IP 기반 방어의 근본적 한계입니다.
로그 의존 — Traefik이 로그를 안 남기면 탐지를 못 합니다. access log 설정이 필수예요.
그래도 자동화된 대량 스캔의 90% 이상은 걸러줍니다. 나머지 10%는 SSO나 애플리케이션 레벨 인증으로 막으면 됩니다.
돌이켜보면
좋았던 점: 설치 후 신경 쓸 게 거의 없습니다. 커뮤니티 블랙리스트가 자동 업데이트되고, 로컬 탐지도 알아서 돌아가니까요. 가끔 cscli decisions list로 뭐가 잡혔나 구경하는 정도.
아쉬웠던 점: 대시보드가 없으면 "지금 뭘 막고 있는지" 가시성이 떨어집니다. CrowdSec Console(클라우드)을 연결하면 되긴 하는데, 셀프호스팅 취지와 좀 맞지 않아서 CLI로 버티고 있어요.
다시 한다면: 처음부터 넣었을 겁니다. "나중에 보안 신경 쓰지 뭐" 하고 미루다가 로그를 보고 소름 돋았거든요. 포트를 여는 순간 방어도 같이 시작해야 합니다.