codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
에듀›웹 보안의 기초 — JWT · OAuth · OWASP›6단계

6단계

익명 폼 하드닝

0회 조회

익명 폼 하드닝

로그인 없는 문의 · 댓글 · 제보 폼. 가장 자주 남용당하는 표적. 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

← 5단계

Rate limit + CORS + 보안 헤더

7단계 →

7단계 — 이메일 인증과 OTP