프로그래밍

그누보드5 스팸봇차단 – 구형 이미지 캡차 허점 해결하기

그누보드5 스팸봇차단 – 구형 이미지 캡차 허점 해결하기

웹사이트 문의 폼을 운영하다 보면 어느 순간부터 의미를 알 수 없는 영문·숫자 조합의 스팸 문의가 쌓이기 시작합니다. 사람이 쓴 글이 아니라 자동화된 봇이 폼에 값을 채워 넣고 전송하는 것입니다. 이번 글에서는 캡차(자동등록방지)만으로 막히지 않는 스팸봇을, 자바스크립트 실행 여부를 검증하는 방법으로 걸러 낸 과정을 정리합니다.

문제 상황

캡차가 폼에 정상 표시되고, 토큰·허니팟·전송 속도 제한까지 모두 걸어 두었는데도 스팸 문의가 게시판에 저장되었습니다. 검증 코드는 정상인데 봇이 통과한 것입니다.

캡차만으로는 왜 부족한가

문의 폼에는 이미 여러 겹의 방어가 있었습니다. 요청 위조 방지 토큰, 사람 눈에 안 보이는 허니팟 필드, 최소 작성 시간(3초) 검사, 연속 전송 제한, 그리고 이미지 캡차까지 적용되어 있었습니다. 그런데도 스팸이 들어왔습니다.

서버 처리 코드를 한 줄씩 확인한 결과, 코드 자체에는 문제가 없었습니다. 원인은 다른 데 있었습니다. 구형 이미지 캡차는 문자 인식(OCR)이나 캡차 해독 서비스로 자동으로 뚫립니다. 즉 봇이 폼 페이지를 실제로 열고, 캡차를 풀고, 정상적인 흐름처럼 전송하면 기존 방어를 모두 통과할 수 있습니다.

핵심 아이디어

캡차를 더 강하게 만드는 대신, 봇이 흉내 내기 어려운 다른 관문을 추가합니다. 그 관문이 바로 “자바스크립트를 실제로 실행했는가”입니다. 대부분의 스팸봇은 폼으로 값만 전송할 뿐 브라우저처럼 자바스크립트를 실행하지 않습니다.

동작 원리

구조는 단순합니다. 서버가 폼을 내려 줄 때 임의의 숫자(시드)를 세션에 저장하고, 그 시드를 자바스크립트로 전달합니다. 브라우저는 이 시드로 정해진 계산을 수행해 히든 필드에 결과값을 채웁니다. 폼이 전송되면 서버는 그 결과값이 자신이 계산한 값과 일치하는지 확인합니다.

실제 방문자와 스팸봇의 처리 흐름 비교

실제 방문자는 브라우저가 자동으로 계산을 처리하므로 아무것도 느끼지 못합니다. 반면 자바스크립트를 실행하지 않는 봇은 이 값을 비워 둔 채 전송하게 되고, 서버 검증에서 걸러집니다.

1단계 — 폼 페이지에 시드와 계산 심기

폼을 출력하는 페이지에서, 세션에 임의의 시드를 저장합니다. 그리고 값을 담을 히든 필드와, 그 값을 계산해 채우는 짧은 스크립트를 넣습니다.

<?php
// 세션에 검증용 시드 저장
$_SESSION['js_seed'] = mt_rand(100000, 999999);
?>

<form method="post" action="/contact_send.php">
    ... 이름 · 이메일 · 내용 등 입력칸 ...

    <!-- 값을 담을 히든 필드 (비어 있음) -->
    <input type="hidden" name="js_answer" id="js_answer" value="">

    <button type="submit">문의 보내기</button>
</form>

<script>
(function () {
    var seed = <?php echo (int) $_SESSION['js_seed']; ?>;
    function fill() {
        var el = document.getElementById('js_answer');
        if (el) { el.value = (seed * 3 + 17); }   // 정해진 계산식
    }
    if (document.readyState !== 'loading') { setTimeout(fill, 400); }
    else { document.addEventListener('DOMContentLoaded', function () { setTimeout(fill, 400); }); }
})();
</script>
계산식은 노출돼도 됩니다

계산식(seed × 3 + 17)이 소스에 보여도 괜찮습니다. 핵심은 “그 계산을 실제로 실행했는가”입니다. 단순히 소스를 긁어 가는 봇은 계산 결과를 만들지 못합니다. 다만 계산식은 사이트마다 다르게 두는 편이 안전합니다.

2단계 — 서버에서 값 검증하기

폼을 처리하는 서버 코드에서, 전송된 값이 세션 시드로 계산한 값과 일치하는지 확인합니다. 일치하지 않으면 처리하지 않고 중단합니다.

<?php
// 세션 시작 (이미 시작돼 있으면 생략)
if (session_status() !== PHP_SESSION_ACTIVE) session_start();

// 자바스크립트 실행 검증
$answer = isset($_POST['js_answer']) ? (int) $_POST['js_answer'] : 0;
$seed   = isset($_SESSION['js_seed']) ? (int) $_SESSION['js_seed'] : 0;

if ($seed === 0 || $answer !== ($seed * 3 + 17)) {
    exit('비정상적인 접근입니다.');   // 봇 차단
}

// 재사용 방지 — 한 번 쓴 시드는 폐기
unset($_SESSION['js_seed']);

// ... 이하 정상 저장 처리 ...
실제 사용자에게는 영향이 없습니다

값은 페이지가 열리고 약 0.4초 뒤에 자동으로 채워집니다. 사람이 폼을 작성해 전송하기까지는 그보다 훨씬 오래 걸리므로, 전송 시점에는 항상 값이 준비돼 있습니다. 폼의 외관·입력칸·버튼은 그대로이며 방문자는 아무런 차이를 느끼지 못합니다.

적용 결과와 한계

이 방식을 적용한 뒤, 값만 전송하던 방식의 스팸봇은 검증 단계에서 그대로 차단되었습니다. 기존 캡차·허니팟·속도 제한은 그대로 두고 그 위에 한 겹을 더한 것이므로, 방어가 약해지는 부분도 없습니다.

다만 이 방법이 만능은 아닙니다. 헤드리스 브라우저처럼 자바스크립트를 실제로 실행하는 고급 봇은 이 관문도 통과할 수 있습니다. 그 단계까지 대응해야 한다면 서버 측 점수 기반 캡차(예: reCAPTCHA v3) 도입을 검토하게 됩니다. 하지만 실무에서 유입되는 스팸의 상당수는 값만 전송하는 단순 봇이므로, 이 한 겹만으로도 체감되는 스팸량은 크게 줄어듭니다.

정리

캡차는 “사람인지”를 묻고, 이 방법은 “브라우저인지”를 묻습니다. 성격이 다른 두 관문을 겹쳐 두면, 한쪽을 뚫는 봇이 다른 쪽에서 걸립니다. 비용이 거의 들지 않으면서 효과가 좋은, 가성비 높은 방어입니다.

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *로 표시됩니다.