홈서버를 운영하다보면 반드시 마주치는 것
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로 버티고 있어요.
다시 한다면: 처음부터 넣었을 겁니다. 나중에 보안 신경 쓰지 뭐 하고 미루다가 로그를 보고 소름 돋았거든요. 포트를 여는 순간 방어도 같이 시작해야 합니다.