허니팟은 여전히 효과적: CAPTCHA 없이 스팸 가입을 막는 3가지 방법

Fred· AI Engineer & Developer Educator6 min read

허니팟은 여전히 효과적: CAPTCHA 없이 스팸 가입을 막는 3가지 방법

지난주, SEO Friend 대기자 명단 가입에서 의심스러운 것을 발견했습니다. 일회용 도메인에서 수십 개의 이메일 주소가 서로 몇 밀리초 간격으로 제출되었고, 러시아와 네덜란드의 IP 주소에서 발생했습니다. 전형적인 봇 팜 행동입니다.

명백한 해결책은? 폼에 CAPTCHA를 붙이는 것입니다. 하지만 CAPTCHA는 짜증납니다. 실제 사용자를 좌절시키고, 전환율을 떨어뜨리며, 솔직히—현대 봇들은 어쨌든 꽤 잘 풀고 있습니다. 저는 인간에게는 보이지 않지만 봇에게는 치명적인 것을 원했습니다.

허니팟의 등장입니다.

허니팟이란?

허니팟은 예측 가능한 행동을 악용하여 자동화된 시스템을 잡도록 설계된 함정입니다. 웹 보안에서 폼 허니팟은 간단한 원칙으로 작동합니다: 봇은 찾는 모든 폼 필드를 자동 채우지만, 인간은 볼 수 있는 필드만 채웁니다.

인간이 볼 수 없는 숨겨진 필드를 추가하고 그 필드가 채워지면—봇을 잡은 것입니다.

오래된 기술이지만 2025년에도 여전히 놀라울 정도로 잘 작동합니다. 타이밍 검사와 결합하면 사용자 마찰 없이 대부분의 스팸 가입을 막을 수 있습니다.

문제: 봇 팜 가입

허니팟을 구현하기 전, 제가 다루던 상황입니다. 러시아, 네덜란드, 독일의 데이터 센터에서 온 IP들이 제 대기자 명단 폼을 공격했습니다. 제출은 비인간적인 속도로 들어왔습니다—페이지 로드 후 0.2에서 0.5초. 이메일 주소는 모두 tempmail과 guerrillamail 같은 일회용 도메인에서 왔습니다. 그리고 찾을 수 있는 모든 폼 필드를 채우고 있었는데, 이것이 그들의 패망이 됩니다.

봇들이 왜 이러는 걸까요? 이메일 시스템을 수확하고 있습니다—확인 이메일이 반송되는지 보기 위해 폼을 제출하고, 이메일 파이프라인이 작동하는지 확인합니다. 시간과 이메일 할당량을 낭비하게 하려고 정크 데이터로 데이터베이스를 오염시킵니다. 나중에 크리덴셜 스터핑 공격을 위해 이메일 주소를 테스트합니다. 때로는 경쟁자 방해입니다—숫자를 신뢰할 수 없도록 쓰레기 데이터로 메트릭을 부풀립니다.

봇들은 같은 /24 IP 블록에서 오고, 몇 분의 1초 만에 제출하며, 명백한 일회용 이메일을 사용했습니다. 허니팟 탐지에 완벽한 후보들입니다.

예시 1: 숨겨진 필드 함정

이것은 2000년대 초반부터 봇을 막아온 고전적인 허니팟 기술입니다. 개념은 아름답게 간단합니다: 인간에게는 보이지 않지만 봇에게는 보이는 폼 필드를 추가합니다. 봇이 페이지의 모든 필드를 자동 채울 때(항상 그렇듯이) 숨겨진 필드도 채웁니다—그리고 그들을 잡았습니다.

실제 이메일 입력과 함께 폼에 추가할 HTML입니다:

<form id="waitlist-form" action="/api/signup" method="POST">
  <!-- 인간이 채우는 실제 필드 -->
  <label for="email">이메일 주소</label>
  <input type="email" id="email" name="email" required>

  <!-- 허니팟 필드 - 인간에게 완전히 보이지 않음 -->
  <input
    type="text"
    name="website"
    style="position:absolute;left:-9999px"
    tabindex="-1"
    autocomplete="off"
    aria-hidden="true"
  >

  <button type="submit">대기자 명단 가입</button>
</form>

서버 측에서 검사는 간단합니다. website 필드에 값이 있으면 봇을 잡은 것입니다:

app.post('/api/signup', async (c) => {
  const { email, website } = await c.req.parseBody();

  // 허니팟 필드에 값이 있으면 봇이 채운 것
  if (website) {
    console.log('허니팟으로 봇 감지:', email);

    // 가짜 성공 반환 - 봇은 작동했다고 생각함
    return c.json({
      success: true,
      message: '가입해 주셔서 감사합니다!'
    });
  }

  // 실제 인간 - 정상적으로 처리
  await saveToDatabase(email);
  return c.json({ success: true, message: '환영합니다!' });
});

여기서 중요한 세부 사항: 오류 대신 가짜 성공 응답을 반환합니다. 오류 메시지를 반환하면 정교한 봇이 잡혔다는 것을 감지하고 다른 접근 방식을 시도할 수 있습니다—필드를 건너뛰거나 다른 IP를 사용하는 등. 성공을 반환하면 봇 운영자는 로그에서 녹색 체크 표시를 보고 더 쉬운 대상으로 이동합니다. 데이터베이스는 깨끗하게 유지되고 그들은 무슨 일이 일어났는지 절대 모릅니다.

예시 2: 타이밍 검사

인간은 페이지를 읽고 폼을 채우는 데 시간이 걸립니다. 가장 빠른 타이피스트도 폼을 스캔하고, 이메일 필드를 클릭하고, 주소를 입력하고, 제출을 누르는 데 몇 초가 필요합니다. 실제 사람은 페이지를 로드하고 300밀리초 만에 폼을 제출할 수 없습니다.

봇은 할 수 있습니다. 그리고 끊임없이 합니다.

이 허니팟 기술은 페이지 로드와 폼 제출 사이의 시간을 측정합니다. 3초 미만은 거의 확실히 자동화입니다.

먼저, 페이지 로드 타임스탬프를 저장할 숨겨진 필드를 폼에 추가합니다:

<form id="waitlist-form" action="/api/signup" method="POST">
  <label for="email">이메일 주소</label>
  <input type="email" id="email" name="email" required>

  <!-- 타이밍 검사 필드 - 페이지 로드 시간 캡처 -->
  <input type="hidden" name="loadedAt" id="loadedAtField">

  <button type="submit">대기자 명단 가입</button>
</form>

<script>
  // 페이지가 로드되자마자 타임스탬프 기록
  document.getElementById('loadedAtField').value = Date.now();
</script>

서버에서 제출 시간과 로드 시간을 비교합니다:

app.post('/api/signup', async (c) => {
  const { email, loadedAt } = await c.req.parseBody();

  // 사용자가 제출하는 데 걸린 시간 계산
  if (loadedAt) {
    const elapsed = Date.now() - parseInt(loadedAt, 10);

    // 3초 미만? 인간은 그렇게 빨리 타이핑하지 않습니다.
    if (elapsed < 3000) {
      console.log(`봇 감지: ${elapsed}ms 만에 제출`);

      // 속도 제한 오류 반환 - 약간 더 그럴듯함
      return c.json({
        error: '제출하기 전에 잠시 기다려 주세요.'
      }, 429);
    }
  }

  // 합리적인 시간이 걸림 - 아마 인간
  await saveToDatabase(email);
  return c.json({ success: true, message: '환영합니다!' });
});

임계값으로 3초(3000밀리초)를 사용합니다. 테스트에서 자동 채우기가 활성화된 빠른 사용자도 최소 4-5초가 걸렸습니다. 제가 잡은 가장 느린 봇은 약 800밀리초였습니다. 3초는 둘 다에 편안한 마진을 제공합니다.

예시 3: 완전한 2단계 시스템

실제로는 두 기술이 함께 작동하기를 원합니다. 숨겨진 필드는 폼을 자동 채우는 봇을 잡습니다. 타이밍 검사는 숨겨진 필드를 건너뛰려고 영리하게 행동하는 봇을 잡습니다. 함께하면 자동화된 제출에 대해 거의 뚫을 수 없는 장벽을 형성합니다.

SEO Friend에서 실행 중인 완전한 구현입니다:

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>대기자 명단 가입</title>
</head>
<body>
  <form id="waitlist-form">
    <div class="form-group">
      <label for="email">이메일 주소</label>
      <input
        type="email"
        id="email"
        name="email"
        required
        placeholder="you@example.com"
      >
    </div>

    <!-- 허니팟 1: 숨겨진 필드 함정 -->
    <!-- 봇은 이것을 자동 채움; 인간은 절대 보지 못함 -->
    <input
      type="text"
      name="website"
      style="position:absolute;left:-9999px"
      tabindex="-1"
      autocomplete="off"
      aria-hidden="true"
    >

    <!-- 허니팟 2: 타이밍 검사 -->
    <!-- 속도 검증을 위해 페이지 로드 시간 기록 -->
    <input type="hidden" name="loadedAt" id="loadedAt">

    <button type="submit">대기자 명단 가입</button>
  </form>

  <script>
    // 페이지 로드 즉시 타임스탬프 설정
    document.getElementById('loadedAt').value = Date.now();

    document.getElementById('waitlist-form').addEventListener('submit', async (e) => {
      e.preventDefault();
      const formData = new FormData(e.target);

      const response = await fetch('/api/signup', {
        method: 'POST',
        body: formData
      });

      const result = await response.json();
      if (result.success) {
        alert('대기자 명단에 가입해 주셔서 감사합니다!');
      } else {
        alert(result.error || '문제가 발생했습니다.');
      }
    });
  </script>
</body>
</html>

결과

이 2단계 허니팟 시스템을 구현한 후, 스팸 가입이 하루 20-50개에서 사실상 0으로 떨어졌습니다. 봇들은 여전히 시도하고 있습니다—서버 로그에서 볼 수 있습니다—하지만 모두 잡혀서 가짜 성공 응답을 받습니다. 그들은 이기고 있다고 생각합니다. 제 데이터베이스는 깨끗하게 유지됩니다.

실제 사용자는 아무것도 눈치채지 못했습니다. 풀어야 할 CAPTCHA도, 가입 흐름에 추가된 마찰도 없습니다. 이메일을 입력하고 제출을 클릭하면 끝입니다. 허니팟 검사는 백그라운드에서 보이지 않게 발생합니다.

구현하는 데 약 30분이 걸렸습니다—대부분 무엇이 차단되는지 모니터링할 수 있도록 로깅을 설정하는 데 사용했습니다. 지속적인 유지 보수도 없고, 스팸 방지 서비스에 대한 월 비용도 없고, 관리할 API 키도 없습니다.

때로는 가장 간단한 해결책이 최고입니다. 허니팟은 작동하기 때문에 수십 년 동안 존재해 왔습니다. 점점 더 정교해지는 AI와 머신 러닝의 시대에, 숨겨진 텍스트 필드와 타임스탬프로 봇을 막는 것에는 만족스러운 무언가가 있습니다.

자주 묻는 질문

허니팟은 모든 봇에 효과가 있나요?

아니요. 허니팟을 우회하도록 특별히 설계된 정교한 봇은 숨겨진 필드를 건너뛰거나 인위적인 지연을 추가할 수 있습니다. 그러나 이것은 드뭅니다. 대부분의 봇은 모든 것을 자동 채우고 즉시 제출하는 단순한 스크레이퍼입니다. 허니팟은 자동화된 스팸의 95% 이상을 잡으며, 이는 대개 스팸 문제를 관리 가능하게 만드는 데 충분합니다.

이것이 접근성을 깨뜨릴까요?

올바르게 구현하면 아닙니다. aria-hidden="true" 속성은 스크린 리더에게 허니팟 필드를 완전히 무시하라고 알려주므로, 시각 장애인 사용자는 상호 작용할 수 없는 필드로 혼란스러워하지 않습니다. tabindex="-1"은 키보드 사용자가 탭으로 들어가는 것을 방지합니다. 보조 기술로 탐색하는 사용자는 의도한 대로 정확히 폼을 경험합니다.

합법적인 사용자가 허니팟을 채우면 어떻게 되나요?

올바른 구현으로는 거의 불가능합니다. 필드는 화면 왼쪽 가장자리에서 9999 픽셀 떨어져 위치합니다—사용자는 문자 그대로 그것을 보거나 클릭할 수 없습니다. 브라우저 자동 채우기는 autocomplete="off"로 비활성화됩니다. 수천 명의 방문자와 2주간의 프로덕션 사용에서 오탐이 전혀 없었습니다.

대신 CAPTCHA를 사용해야 할까요?

CAPTCHA는 마찰을 추가합니다. 연구에 따르면 전환율을 3-8% 줄일 수 있습니다. 사용자는 싫어합니다—저도 싫어합니다. 허니팟은 보이지 않습니다; 사용자는 존재하는지도 모릅니다. 허니팟으로 시작하세요. 적절한 허니팟 보호를 구현한 후에도 여전히 심각한 스팸이 발생하는 경우에만 CAPTCHA를 추가하세요. 대부분의 사이트에서는 그럴 가능성이 낮습니다.

Fred

Fred

AUTHOR

Full-stack developer with 10+ years building production applications. I've built frontends that users actually enjoy using (and that don't break in IE).

Need a developer who gets it?

POC builds, vibe-coded fixes, and real engineering. Let's talk.

Hire Me →