6단계
익명 폼 하드닝
25 분
익명 폼 하드닝
로그인 없는 문의 · 댓글 · 제보 폼. 가장 자주 남용당하는 표적. CAPTCHA 먼저가 아니라 다층 저비용 방어의 합 이 효과적.
1. 위협 모델
| 위협 | 특징 | 빈도 |
|---|---|---|
| 스팸 봇 | 자동 submit · 많은 IP | 90%+ |
| 수동 스팸 | 사람이 복붙 | 중간 |
| 대상 공격 | 특정 허위 제보 | 낮음 |
| 리소스 고갈 | 초당 수천 요청 | 드뭄 |
90% 를 차지하는 자동 봇만 막아도 운영이 크게 편해집니다.
2. Honey-pot (1 차)
<input
type="text"
name="website"
tabIndex={-1}
autoComplete="off"
aria-hidden="true"
style={{ position: "absolute", left: "-9999px", opacity: 0 }}
/>
export async function POST(req: Request) {
const body = await req.json();
if (body.website?.trim()) {
return NextResponse.json({ ok: true }, { status: 200 });
// 200 반환 — 봇이 재시도하지 않음
}
// 정상 경로
}
사용자는 보이지 않는 필드를 채우지 않음. 봇은 HTML 파싱만 해서 CSS 를 안 봄.
3. IP 해싱 (2 차)
import { createHash } from "crypto";
function hashIp(ip: string) {
return createHash("sha256").update(ip).digest("hex").slice(0, 32);
}
const ip = (req.headers.get("x-forwarded-for")?.split(",")[0] ?? "").trim();
await db.query(
"INSERT INTO inquiries (..., ip_hash) VALUES (..., $1)",
[hashIp(ip)]
);
- 중복 submit 식별 가능
- 원문 IP 는 DB · 로그 어디에도 없음 (GDPR · PIPA 친화)
4. Rate limit (3 차)
await redis.incr(`inq:${ipHash}:${Math.floor(Date.now() / 60000)}`);
// 분당 3 건 초과 → 거절
Redis 가 없으면 Postgres 로도 가능 (count(*) FROM inquiries WHERE ip_hash = $1 AND created_at > now() - interval '1 minute').
5. 입력 검증 (zod) + 길이 상한
import { z } from "zod";
const Schema = z.object({
name: z.string().max(40).optional(),
email: z.string().email().max(120).optional(),
subject: z.string().max(80).optional(),
message: z.string().min(5).max(2000),
website: z.string().optional(), // honey-pot
});
max 가 중요. 메가바이트 payload 로 DB · 메모리 소진 공격 저비용 차단.
6. XSS — 표시 시점 sanitize
import DOMPurify from "isomorphic-dompurify";
const safe = DOMPurify.sanitize(marked.parse(message));
<div dangerouslySetInnerHTML={{ __html: safe }} />
저장은 원문 그대로. 렌더 시점에만 sanitize. 저장 시 인코딩하면 재편집이 곤란.
7. PII 최소화
CREATE TABLE inquiries (
id BIGSERIAL PRIMARY KEY,
name TEXT, -- 옵션
email TEXT, -- 옵션
message TEXT NOT NULL,
user_agent TEXT, -- 500자 truncate
ip_hash TEXT, -- 해시만
status VARCHAR(12) NOT NULL DEFAULT 'new'
CHECK (status IN ('new','read','replied','archived')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
- email · name 필수로 만들지 말 것 (가짜 입력 증가)
- user_agent 500자 truncate (긴 UA 가 공격 시그널)
8. 상태 플로우
new (신규) → read (확인) → replied (답변) → archived (보관)
4 단계로 충분. 운영자가 trackable.
9. 언제 CAPTCHA?
위 6 가지로 99% 커버. 다음 시점에서만 추가:
- 하루 수십 건 이상 꾸준한 스팸
- 타겟팅 공격
- 결제 · 쿠폰처럼 금전 가치 있는 액션
Cloudflare Turnstile · hCaptcha 가 reCAPTCHA 보다 UX · 프라이버시 우수.
10. 자주 걸리는 자리
- CAPTCHA 먼저 도입 — UX 손해 크고 봇 solving 서비스로 우회됨
- raw IP 저장 — 개인정보법 의무 발생. 해싱 권장
- 4xx 반환 — 봇이 학습해 회피. honey-pot 는 200 이 유리
- 관리자 UI raw HTML 렌더 — admin 도 XSS 안전 지대 아님
하고픈 말
honey-pot 하나만으로도 첫 해 스팸의 80% 가 걸립니다. 운영 로그에서 진짜 위협 패턴을 보고 나서 다음 단계 추가하는 순서가 아프지 않습니다.
Next
- data/12-multi-pg-pool-orchestration
- backend/13-audit-log-pattern
🎉 웹 보안의 기초 — JWT · OAuth · OWASP 완주를 축하해요
이어서 어떤 걸 배워 볼까요?