security
CHAPTER 75 / 84
읽기 약 2분
FUNCTION
이상 패턴 탐지: 브루트포스
핵심 개념
같은 IP 반복 실패 로그인을 시간 윈도우 기반으로 탐지하고 자동 차단한다.
본문
브루트포스의 흔적
# /var/log/auth.log — SSH 브루트포스
Apr 27 10:23:11 server sshd[1234]: Failed password for invalid user admin from 198.51.100.99 port 22
Apr 27 10:23:13 server sshd[1235]: Failed password for invalid user root from 198.51.100.99 port 22
Apr 27 10:23:15 server sshd[1236]: Failed password for invalid user test from 198.51.100.99 port 22
... (수백~수천 라인)특징:
- 같은 IP 반복
- 다양한 사용자명 시도
- 짧은 시간 간격 (1~5초)
- 자정~새벽 시간대 집중
슬라이딩 윈도우 카운팅
# ⚠️ 이 코드는 허가된 환경에서만 사용하세요.
import re
from datetime import datetime, timedelta
from collections import defaultdict, deque
# auth.log SSH 실패 패턴
SSH_FAILED = re.compile(
r'^(?P<month>\w{3})\s+(?P<day>\d+)\s+'
r'(?P<time>\d{2}:\d{2}:\d{2})\s+'
r'\S+\s+sshd\[\d+\]:\s+'
r'Failed password\s+'
r'(?:for\s+(?:invalid user\s+)?(?P<user>\S+)\s+)?'
r'from\s+(?P<ip>\S+)\s+port\s+\d+'
)
def parse_auth_time(month: str, day: str, time_str: str, year: int = 2026) -> datetime:
"""auth.log 시간을 datetime으로 (연도는 추정)."""
return datetime.strptime(f'{year} {month} {day} {time_str}', '%Y %b %d %H:%M:%S')
def detect_bruteforce(
log_path: str,
threshold: int = 10, # 5분 안에 10회 실패
window_minutes: int = 5,
) -> dict:
"""슬라이딩 윈도우 기반 브루트포스 탐지."""
# IP별 실패 시각 큐
failures: dict[str, deque] = defaultdict(deque)
detected = {} # IP → {'count': N, 'first': time, 'last': time, 'users': set()}
window = timedelta(minutes=window_minutes)
with open(log_path, encoding='utf-8', errors='replace') as f:
for line in f:
m = SSH_FAILED.search(line)
if not m:
continue
try:
ts = parse_auth_time(m['month'], m['day'], m['time'])
except ValueError:
continue
ip = m['ip']
user = m['user'] or 'unknown'
# 윈도우 밖의 오래된 기록 제거
q = failures[ip]
while q and q[0][0] < ts - window:
q.popleft()
q.append((ts, user))
# 임계값 초과 → 탐지
if len(q) >= threshold:
if ip not in detected:
detected[ip] = {
'count': len(q),
'first_seen': q[0][0].isoformat(),
'last_seen': ts.isoformat(),
'users_tried': set(),
}
detected[ip]['count'] = len(q)
detected[ip]['last_seen'] = ts.isoformat()
detected[ip]['users_tried'].update(u for _, u in q)
# set → list로 (JSON 직렬화)
for d in detected.values():
d['users_tried'] = sorted(d['users_tried'])[:20] # 최대 20개
return detected자동 차단 (fail2ban 흉내)
# 탐지된 IP를 iptables로 차단 (sudo 권한 필요)
import subprocess
def block_ip(ip: str, reason: str = 'bruteforce') -> bool:
"""iptables로 IP 차단."""
try:
subprocess.run([
'sudo', 'iptables', '-A', 'INPUT',
'-s', ip, '-j', 'DROP',
'-m', 'comment', '--comment', f'auto-block:{reason}',
], check=True, timeout=5)
log.warning(f'⛔ 차단됨: {ip} ({reason})')
return True
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
log.error(f'차단 실패 {ip}: {e}')
return False
def block_detected(detected: dict, min_count: int = 50):
"""50회 이상 실패한 IP만 차단 — 오탐 방지."""
blocked = []
for ip, info in detected.items():
if info['count'] >= min_count:
if block_ip(ip, f'bruteforce-{info["count"]}'):
blocked.append(ip)
return blocked화이트리스트 — 오탐 방지
import ipaddress
# 신뢰 IP는 절대 차단 안 함
WHITELIST = [
ipaddress.ip_network('192.168.0.0/16'), # 사내망
ipaddress.ip_network('127.0.0.0/8'), # 로컬
ipaddress.ip_address('203.0.113.10'), # 본사 외부 IP
]
def is_whitelisted(ip_str: str) -> bool:
try:
ip = ipaddress.ip_address(ip_str)
for entry in WHITELIST:
if isinstance(entry, ipaddress.IPv4Network):
if ip in entry:
return True
elif ip == entry:
return True
except ValueError:
return False
return False실시간 모니터링
import time
def tail_and_detect(log_path: str, threshold: int = 10):
"""tail -f 처럼 실시간 모니터링."""
failures: dict[str, deque] = defaultdict(deque)
window = timedelta(minutes=5)
with open(log_path, encoding='utf-8', errors='replace') as f:
f.seek(0, 2) # 끝으로 이동
while True:
line = f.readline()
if not line:
time.sleep(0.5)
continue
m = SSH_FAILED.search(line)
if not m:
continue
ip = m['ip']
if is_whitelisted(ip):
continue
try:
ts = parse_auth_time(m['month'], m['day'], m['time'])
except ValueError:
continue
q = failures[ip]
while q and q[0] < ts - window:
q.popleft()
q.append(ts)
if len(q) == threshold: # 임계 도달 순간만
log.warning(f'🚨 브루트포스 감지: {ip} ({len(q)}회/5분)')
# block_ip(ip, 'bruteforce')⚠️ 운영 주의사항
- 차단 전 logging만 + 사람 확인 권장 (자동 차단 = 자기 차단 위험)
- NAT 환경: 100명이 같은 공용 IP를 공유 — 1명의 실수로 100명 차단
- 실제 운영: fail2ban, CrowdSec, Cloudflare WAF 활용
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 브루트포스 탐지 코드의 슬라이딩 윈도우·화이트리스트·차단 로직을 오탐 측면에서 분석하고 개선해줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
fail2ban / CrowdSec / Cloudflare WAF의 자체 Python 구현 대비 장단점을 비용·정확도·운영 측면에서 비교해줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 auth.log 한 달치를 분석해서 브루트포스 IP·시간대 패턴·공격 빈도를 시각화 가능한 종합 리포트로 만들어줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 SSH 보안 트렌드 — fail2ban vs SSH key only vs Cloudflare Tunnel 1인 SaaS의 가성비 솔직히 알려줘.
⭐ 이것만 기억하세요
이상 패턴 탐지: 브루트포스는 이 3가지만 확실히 잡으세요
1.슬라이딩 윈도우(5분 내 10회+)로 브루트포스를 정확히 탐지하면서 정상 사용자 오탐을 막을 수 있다
2.IP 차단은 화이트리스트 + 임계값 + 사람 검증 3단 안전망이 필수 — 자동 차단의 위험을 줄인다
3.다음 챕터에서 SQL Injection·XSS·경로 탐색 같은 웹 공격 패턴을 탐지한다
공유하기
진행도 75 / 84