![[실전 보안 05] 캡차 없이 봇을 걸러내는 법](https://img.thenullpage.com/posts/5712/5712_1_4ce6bf.webp)
지난 편에서 이메일 인증이 가입 시점의 관문이라면, 사이트 곳곳의 모든 요청에서 사람과 봇을 구분하는 더 범용적인 장치가 필요하다고 했다. 이번 편의 주인공이 바로 그 장치, 캡차와 그 진화형이다. 신호등이 있는 사진을 모두 고르라거나 일그러진 글자를 읽으라던 그 성가신 퍼즐을 다들 기억할 것이다. 나도 처음엔 그런 퍼즐을 붙일까 고민했지만, 사용자를 괴롭히지 않으면서 봇을 걸러 내는 더 나은 방법을 찾아 우리 커뮤니티의 가입과 로그인에 적용했다. 그 과정을 정리한다.
1. 캡차는 왜 생겨났나
캡차는 사람은 쉽게 풀지만 기계는 풀기 어려운 문제를 내서 사람 여부를 가리려는 발상에서 나왔다. 일그러진 글자 읽기가 대표적이었다. 사람의 눈은 찌그러진 글자도 쉽게 알아보지만, 초기의 자동 프로그램은 그런 왜곡된 이미지를 해독하기 어려웠다. 그래서 회원가입이나 글쓰기 앞에 이 퍼즐을 세워 두면 자동화된 대량 요청을 상당히 걸러 낼 수 있었다.
문제는 두 가지였다. 첫째, 기계가 점점 똑똑해졌다. 이미지 인식 기술이 발전하면서 봇도 왜곡된 글자나 사진 속 사물을 알아보게 됐다. 그러자 캡차는 사람을 걸러 내기 위해 점점 더 어려워졌고, 급기야 사람조차 여러 번 틀리는 지경에 이르렀다. 방어를 강화할수록 정작 진짜 사용자가 고통받는 역설이 생긴 것이다.
둘째, 사용자 경험이 나빴다. 글 하나 쓰려고 자전거가 있는 칸을 열두 개나 고르고 있으면 짜증이 난다. 이 마찰 때문에 가입을 포기하는 사람도 생긴다. 봇을 막으려다 진짜 손님을 내쫓는 셈이다. 게다가 일부 캡차는 사용자의 행동 데이터를 광고 목적으로 수집한다는 비판도 받았다. 그래서 나는 퍼즐을 강요하지 않으면서도 봇을 거를 방법이 없을지 찾기 시작했다.
이 지점에서 중요한 깨달음이 있었다. 사람과 기계를 구분하는 데 꼭 퍼즐을 풀게 할 필요는 없다는 것이다. 사람이 브라우저를 다루는 방식과 자동 프로그램이 접근하는 방식 사이에는 눈에 보이지 않는 수많은 차이가 있다. 그 차이를 조용히 관찰하는 것만으로도 상당한 판별이 가능했다. 퍼즐을 내는 대신 신호를 읽는 방향으로 발상을 바꾸자 길이 열렸다.
2. 조용한 검증이라는 발상
내가 우리 사이트에 붙인 것은 사용자를 거의 괴롭히지 않는 조용한 검증 위젯이었다. 이 방식은 사용자에게 퍼즐을 내미는 대신, 브라우저 환경에서 여러 신호를 자동으로 수집해 사람인지 판단한다. 대부분의 진짜 사용자는 아무것도 하지 않아도, 심지어 검증이 진행되는지도 모른 채 통과한다. 화면에는 잠깐 확인 중이라는 표시만 스쳐 갈 뿐이다.
어떻게 퍼즐 없이 판별할까. 이 위젯은 브라우저가 작은 계산 과제를 수행하게 하거나, 정상적인 브라우저라면 당연히 갖춰야 할 여러 특성을 조용히 확인한다. 자동화 도구는 이런 환경을 완벽히 흉내 내기가 은근히 까다롭다. 사람의 브라우저에서는 자연스러운 신호들이 봇의 접근에서는 어딘가 어긋나기 마련이다. 위젯은 이 미묘한 차이를 종합해 사람일 확률을 판단하고, 애매할 때만 추가 확인을 요구한다.
이 방식의 프라이버시 측면도 마음에 들었다. 내가 고른 도구는 사용자의 행동을 광고 목적으로 수집하지 않고, 판별에 필요한 최소한의 신호만 처리한다고 밝히고 있었다. 봇을 막겠다고 사용자의 사생활을 팔아넘기는 건 본말이 전도된 일이라, 이 점은 도구를 고를 때 중요한 기준이었다. 방어와 프라이버시가 충돌하지 않는 선택지가 있다는 게 반가웠다.
3. 앞단 검증과 뒷단 검증의 짝
여기서 반드시 짚어야 할 부분이 있다. 이런 위젯을 화면에 붙이는 것만으로는 절반짜리 방어라는 점이다. 위젯은 사용자가 검증을 통과하면 일종의 통과 증표를 발급한다. 그런데 이 증표를 서버가 다시 검증하지 않으면 아무 소용이 없다. 봇은 화면의 위젯을 아예 건너뛰고 서버에 직접 요청을 보낼 수 있기 때문이다. 화면은 사람이 보는 것이지 봇이 지켜야 하는 게 아니다.
그래서 나는 반드시 서버 쪽에서 이 증표의 진위를 확인하도록 구현했다. 가입이나 로그인 요청이 들어오면, 서버는 함께 온 통과 증표를 검증 서비스에 다시 물어본다. 유효한 증표가 없거나 위조된 증표라면 요청을 거절한다. 화면의 위젯은 사용자 안내용이고, 진짜 관문은 서버에 있다. 이 앞단과 뒷단의 짝을 맞추지 않으면 봇은 위젯을 비웃으며 그냥 통과한다.
이 원리는 사실 보안 전반에 통하는 교훈이었다. 화면에서 하는 검증은 사용자 편의를 위한 것일 뿐, 신뢰의 최종 판정은 언제나 서버에서 이뤄져야 한다. 화면의 입력 제한, 버튼 비활성화, 자바스크립트 검사 같은 것들은 봇 앞에서 무력하다. 봇은 화면을 거치지 않으니까. 그래서 어떤 규칙이든 서버에서 한 번 더 확인한다는 원칙을 나는 이 편을 만들며 뼈에 새겼다.
4. 실제로 붙여 보니
위젯을 가입과 로그인 창에 붙이고 서버 검증까지 엮자, 자동 생성 계정과 유출 목록 대입 시도가 눈에 띄게 줄었다. 특히 로그인 창에 붙인 효과가 컸다. 앞서 이야기한 유출된 아이디와 비밀번호를 무더기로 대입하는 공격은 초당 수많은 시도를 반복하는데, 매 시도마다 사람 검증을 통과해야 하니 봇 입장에서 효율이 급락한 것이다.
물론 완벽하지는 않았다. 작정하고 사람을 고용하거나 정교한 자동화로 검증을 우회하는 경우까지 막지는 못한다. 하지만 첫 편에서 말했듯 목표는 무적이 아니라 성가심이었다. 우리 사이트를 뚫는 비용을 충분히 올려 두면 대다수 자동 공격자는 더 쉬운 다음 대상으로 옮겨 간다. 위젯은 그 비용을 크게 올려 주는 한 겹이었다.
운영하면서 신경 쓴 건 정상 사용자가 걸리지 않게 하는 것이었다. 아주 드물게 특정 환경의 진짜 사용자가 추가 확인을 요구받는 경우가 있었는데, 그럴 때도 퍼즐이 지나치게 어렵지 않도록, 그리고 검증에 실패했을 때 무엇을 해야 하는지 안내가 분명하도록 다듬었다. 앞 편에서 만난 모바일 환경 특유의 문제처럼, 특정 조건에서만 인증이 막히는 경우도 있어 그런 사례를 하나씩 잡아 나가는 일이 실제 운영의 대부분이었다.
5. 사람 구분 다음의 과제
사람과 봇을 구분하는 장치를 갖추자 한 고비를 넘었다. 하지만 사람으로 판별된 뒤에도 문제가 남는다. 사람이라도 같은 요청을 비정상적으로 빠르게, 많이 보낼 수 있기 때문이다. 실수로 새로고침을 연타하는 경우도 있고, 스크립트로 정상 계정을 조종해 대량 요청을 날리는 경우도 있다. 사람 여부만으로는 이런 폭주를 걸러 내지 못한다.
또 봇 검증 위젯을 모든 요청마다 띄울 수는 없다. 글을 읽을 때마다 검증을 시키면 사이트를 쓸 수가 없다. 그래서 위젯은 가입, 로그인처럼 민감하고 빈도가 낮은 지점에 두고, 나머지 수많은 일상 요청은 다른 방식으로 지켜야 했다. 요청의 빈도 자체를 감시해, 짧은 시간에 지나치게 몰리면 잠시 막는 방식이다.
이 편을 정리하며 든 생각은, 봇 방어의 핵심이 결국 비대칭을 만드는 데 있다는 것이었다. 정상 사용자에게는 거의 부담이 없지만 봇에게는 넘기 힘든 벽, 그 비대칭이 클수록 좋은 방어다. 옛 캡차는 봇에게도 벽이었지만 사람에게도 벽이었던 게 문제였다. 반면 조용한 검증 위젯은 사람에게는 거의 투명하면서 봇에게만 마찰을 준다. 좋은 보안 장치일수록 지켜야 할 사람은 못 느끼고 막아야 할 상대만 걸린다는 이 원칙은, 이후 다른 방어를 설계할 때도 좋은 나침반이 되어 줬다.
또 하나 배운 건 어떤 방어든 도입 후 관찰이 절반이라는 사실이었다. 위젯을 붙이고 끝이 아니라, 정상 사용자가 얼마나 걸리는지, 봇이 실제로 줄었는지, 특정 환경에서 문제가 없는지를 계속 지켜봐야 했다. 방어는 한 번 설정하고 잊는 장치가 아니라, 붙인 뒤에도 데이터를 보며 조율하는 살아 있는 과정이었다. 이 태도 없이 도구만 붙여 두면 정작 진짜 사용자가 막히는데도 모르고 지나칠 수 있었다.
그래서 다음 편에서는 요청 폭주를 막는 장치, 흔히 요청 횟수 제한이라 부르는 방식을 다루려 한다. 같은 사용자나 같은 출처가 정해진 시간 안에 보낼 수 있는 요청의 수를 제한해, 폭주와 무차별 대입을 눌러 내는 방법이다. 봇 검증이 문지기라면 요청 제한은 한 사람이 회전문을 몇 번이나 돌 수 있는지를 세는 장치다. 이 둘이 어떻게 협력하는지를 실제 설정 경험과 함께 이야기하겠다.