![[실전 보안 04] 이메일 인증으로 진짜를 거른다](https://img.thenullpage.com/posts/5711/5711_1_ac08e8.webp)
지난 편에서 비밀번호를 안전하게 저장하는 법을 다뤘다. 하지만 아무리 비밀번호를 잘 관리해도, 애초에 그 계정을 만든 쪽이 봇이라면 소용이 없다. 초기 우리 커뮤니티가 겪은 가장 큰 골칫거리 중 하나가 바로 자동 생성된 가짜 계정이었다. 몇 초 만에 수십 개씩 만들어져 광고 글을 쏟아 내는 계정들을 손으로 지우다 지쳤을 무렵, 나는 회원가입 과정에 이메일 인증이라는 관문을 세우기로 했다. 이번 편은 그 관문이 왜 효과적인지, 그리고 어떻게 만들었는지에 대한 기록이다.
1. 가짜 계정이 쉬운 이유
이메일 인증이 없던 시절, 회원가입은 아이디와 비밀번호만 넣으면 끝났다. 문제는 이 과정에 사람만 할 수 있는 관문이 하나도 없었다는 것이다. 봇은 무작위 문자열로 아이디를 만들고 아무 비밀번호나 넣어 가입 요청을 반복하면 그만이었다. 응답이 성공이면 계정 하나가 생기고, 봇은 곧바로 다음 계정을 만든다. 사람이 손으로 하나 만드는 시간에 봇은 수백 개를 만든다.
이렇게 만들어진 계정은 대부분 스팸의 발판이었다. 도박, 불법 상품, 검색 노출을 노린 링크가 담긴 글과 댓글을 뿌리는 데 쓰였다. 신생 커뮤니티는 글이 적어 이런 도배가 특히 눈에 띄고, 방문한 진짜 사용자에게 사이트가 관리되지 않는다는 인상을 준다. 초기 신뢰를 갉아먹는 치명적인 문제였다. 계정을 지워도 새 계정이 곧바로 생기니 밑 빠진 독에 물 붓기였다.
근본 원인은 명확했다. 가입에 아무런 비용도, 검증도 없다는 것. 봇에게 가입은 그냥 요청 한 번이었다. 그래서 나는 가입에 작은 검증 단계를 하나 끼워 넣어, 봇이 넘기 어렵거나 최소한 성가시게 만들기로 했다. 여러 방법을 검토한 끝에 가장 먼저 도입한 것이 이메일 소유 확인이었다. 실제로 받을 수 있는 이메일 주소를 가진 사람만 가입을 완료하게 만드는 방식이다.
2. 이메일 인증이 막아 주는 것
이메일 인증의 원리는 단순하다. 가입 신청을 받으면 곧바로 계정을 활성화하지 않고, 신청자가 적은 이메일 주소로 확인 링크나 코드를 보낸다. 그 링크를 누르거나 코드를 입력해야 비로소 가입이 완료된다. 핵심은 이 과정이 그 이메일 주소를 실제로 통제하는 사람만 통과할 수 있다는 것이다. 남의 주소를 아무리 적어 봐야 그 편지함을 열 수 없으면 인증을 끝낼 수 없다.
이게 왜 봇을 걸러 내느냐면, 봇이 계정마다 진짜로 수신 가능한 이메일 주소를 마련하고 그 편지함을 확인하는 일이 훨씬 번거롭기 때문이다. 물론 자동화된 임시 메일 서비스도 있지만, 최소한 요청 한 번으로 계정을 찍어 내던 흐름은 끊긴다. 봇은 이제 메일을 받고 링크를 파싱해 다시 요청하는 여러 단계를 거쳐야 한다. 이 마찰만으로도 대량 생성의 효율이 크게 떨어진다.
덤으로 얻는 이점도 있었다. 검증된 이메일 주소는 운영에 요긴한 연락 수단이 된다. 비밀번호를 잊었을 때 재설정 링크를 보내거나, 중요한 공지나 알림을 전달할 통로가 생긴다. 앞 편에서 비밀번호는 원래 값을 알려 주지 않고 새로 설정하게 한다고 했는데, 그 재설정 링크를 안전하게 보낼 곳이 바로 이 검증된 이메일이다. 인증은 스팸 방어이자 동시에 정상 사용자와의 신뢰할 수 있는 연결 고리였다.
3. 확인 코드는 어떻게 다뤄야 하나
구현하면서 신경 쓴 부분은 확인 링크나 코드 자체의 안전이었다. 이 코드가 예측 가능하면 봇이 메일을 받지 않고도 코드를 추측해 인증을 통과할 수 있다. 그래서 코드는 충분히 길고 무작위해야 한다. 짧은 숫자 몇 자리라면 봇이 전부 대입해 볼 수 있으니, 추측이 사실상 불가능할 만큼의 임의성을 확보하는 게 중요했다.
또 하나는 유효 기간이었다. 확인 코드는 영원히 살아 있으면 안 된다. 발급 후 일정 시간이 지나면 만료되게 해, 어딘가 새어 나간 옛 코드가 뒤늦게 악용되는 걸 막았다. 그리고 한번 사용한 코드는 즉시 무효화해 재사용을 차단했다. 여기에 짧은 시간에 인증 메일을 반복 요청하지 못하도록 횟수 제한도 걸었다. 그러지 않으면 봇이 남의 이메일 주소로 인증 메일을 무한정 발송시키는 괴롭힘 도구로 악용할 수 있기 때문이다.
메일 발송 자체는 전문 발송 서비스를 통해 처리했다. 직접 메일 서버를 운영하면 스팸으로 분류돼 편지함에 도착조차 못 하는 문제를 겪기 쉽다. 신뢰받는 발송 서비스를 통해야 인증 메일이 안정적으로 전달된다. 이때 발송에 필요한 접근 열쇠는 앞 편에서 강조한 것처럼 코드에 박지 않고 비밀 저장소에 따로 보관했다. 이 부분은 시크릿 관리 편에서 다시 다룬다.
4. 인증도 완벽하지는 않다
이메일 인증을 도입하고 나서 가짜 계정이 눈에 띄게 줄었다. 하지만 이게 만능은 아니라는 것도 곧 알게 됐다. 마음먹은 공격자는 임시 이메일 서비스를 자동으로 활용해 인증을 우회할 수 있다. 그래서 이메일 인증은 봇을 완전히 차단하는 벽이 아니라, 가입의 비용을 올려 대량 생성을 걸러 내는 여러 겹 중 하나로 이해해야 했다. 첫 편에서 말한 심층 방어의 한 층인 셈이다.
그래서 나는 이메일 인증을 다른 장치들과 함께 겹쳐 썼다. 가입 창에는 사람 여부를 조용히 확인하는 위젯을 붙였고, 짧은 시간에 몰리는 가입 요청은 횟수 제한으로 걸렀다. 이메일 인증이 걸러 내지 못한 봇은 다른 층에서 걸리고, 그 층을 넘은 봇은 또 다른 층에서 걸린다. 어느 하나만 믿지 않는 이 태도가 실제로 스팸을 감당 가능한 수준으로 눌러 줬다.
또 정상 사용자의 편의도 함께 챙겨야 했다. 인증 단계가 너무 번거로우면 진짜 사용자마저 가입을 포기한다. 그래서 인증 메일은 즉시 도착하도록 신경 쓰고, 메일이 안 왔을 때 다시 보내는 버튼을 두고, 혹시 스팸함을 확인하라는 안내를 붙였다. 보안과 편의 사이의 균형을 잡는 일은 이 시리즈 내내 반복되는 주제인데, 이메일 인증은 그 균형을 처음 진지하게 고민하게 만든 기능이었다.
5. 신원 확인의 다음 단계
이메일 인증은 가입 시점의 관문이다. 그런데 봇의 공격은 가입에만 있는 게 아니었다. 로그인, 글쓰기, 댓글, 검색 같은 모든 요청에서 자동화된 남용이 벌어진다. 이메일 인증은 가입이라는 한 지점을 지킬 뿐, 나머지 모든 순간을 지키지는 못한다. 사이트 곳곳에서 사람과 봇을 구분해야 하는 더 넓은 과제가 남아 있었다.
이메일 인증을 운영하며 데이터도 조금씩 쌓였다. 인증 완료율이 지나치게 낮으면 메일이 제대로 도착하지 않거나 절차가 너무 번거롭다는 신호였고, 특정 도메인의 임시 메일로 가입이 몰리면 그 도메인을 주시해야 한다는 신호였다. 이렇게 관문 하나를 두면 그 관문을 통과하는 흐름 자체가 사이트 건강을 보여 주는 계기판이 된다. 보안 장치가 방어만 하는 게 아니라 운영 상태를 읽는 창이 되어 준다는 것도 직접 굴려 보고 나서야 알게 된 이점이었다.
여기서 필요한 게 요청 하나하나에서 사람 여부를 판별하는 좀 더 범용적인 장치다. 매번 사용자에게 번거로운 퍼즐을 풀게 하면 경험이 나빠지니, 가능하면 조용히, 사용자가 눈치채지 못하게 사람인지 확인하는 방식이 이상적이다. 다행히 요즘은 그런 방향으로 발전한 봇 방어 도구들이 있다. 전통적인 캡차의 불편함을 크게 줄인 최신 방식들이다.
이메일 인증을 붙이며 배운 또 하나는 검증 상태를 사이트 곳곳에서 존중해야 한다는 것이었다. 인증을 안 마친 계정은 글이나 댓글을 쓰지 못하게 막았다. 인증 관문만 세우고 정작 미인증 계정이 자유롭게 활동하게 두면 관문을 세운 의미가 없기 때문이다. 가입은 받되 인증을 마치기 전까지는 읽기만 가능하고 쓰기는 막는 식으로, 인증 여부에 따라 권한을 단계적으로 부여했다. 이렇게 하니 설령 봇이 미인증 계정을 대량으로 만들어도 실제 스팸으로 이어지지 못했다. 권한을 상태와 엮는 이 발상은 뒤에 다룰 권한 설계 편에서 더 확장된다.
그래서 다음 편에서는 봇 방어의 최전선인 캡차와 그 진화형에 대해 다루려 한다. 우리가 흔히 신호등이나 자전거 사진을 고르던 그 성가신 퍼즐이 왜 생겨났고, 요즘은 어떻게 사용자를 거의 괴롭히지 않으면서 봇을 걸러 내는지, 그리고 내가 우리 사이트의 가입과 로그인 창에 어떤 방식을 붙였는지를 실제 경험으로 이야기하겠다. 사람과 기계를 구분한다는 이 시리즈의 핵심 원리가 가장 직접적으로 드러나는 편이 될 것이다.