로그인 스팸 대응 하기

[ 이글은 2018년 06월 13일에 최종 수정되었습니다. ]
§

[Part 2 : http://hackya.com/kr/로그인-스팸-대응-하기-2/]

Korbuddy 님께서

“로그인 스펨에 공격받는 바람에 홈페이지를 날려먹었었거든요”

라고 댓글을 남기신걸 보고, 오래전 부터 한번 써야지 하고 생각만 하고 있던 내용을 이 튜토리얼을 통해 공유합니다.

배경설명을 짧게 하자면, Korbuddy 님은 스패머가 아닌 관리자 계정을 얻어내려는 해커들이 보낸 악성봇에 공격을 받으신 겁니다. brute force attack (뚫릴때까지 계속 무작위 id 와 비번을 입력하는 방식) 으로 관리자 계정으로 로그인 하려는 bot 들의 주 목적은 2가지 입니다,

a. 공격대상 사이트에 악성스크립트 깔아놓기.
b. 금전적 값어치가 있는 정보 훔쳐내기

그리고 워드프레스 사이트는 인터넷의 25% 를 차지하니, 워드프레스 사이트를 주 공격대상을 삼습니다. 어느 사이트나 공격대상 리스트에 올려도 bot 이 워드프레스 사이트용으로 제작되었다면 공격시 1/4 비율로 성공하니까 워드프레스용 bot 제작하는게 가장 현명/효율적 입니다.

How To Defend Against Malicious Bots?
bad_bots

1. 사전조사

요즘 crawler/bot 들의 기술수준이 어느정도에 까지 와 있는지 먼저 research 를 할 필요가 있습니다.

http://stackoverflow.com/questions/5834808/designing-a-web-crawler

http://stackoverflow.com/questions/21006940/how-to-load-all-entries-in-an-infinite-scroll-at-once-to-parse-the-html-in-pytho/21008335#21008335

https://github.com/corywalker/selenium-crawler (스팸봇 오픈소스)

(javascript 으로 스크롤 해야 컨텐츠가 로딩되도록 구조가 짜인 문서도 crawler 로 긁어 오는 걸 시현해서 보여주는 동영상 입니다.)

2. Research 결과

javascript 으로 scroll 이나 click 시 element 가 load 되도록 해서 스팸봇의 접근을 막는 기법이 이제 더이상 먹히지 않음을 알 수 있습니다.

scroll 까지 emulate 하는 bot 이 출시된 상태에서, 사실상 스팸대응은, 이제 개인 개발자 혼자서 대응할 수 있는 수준을 넘어섰다고 봐야 하지 않을까 생각 합니다.

구체적으로는 이런식으로 로딩지연된 문서내용/부분을 확인하고 있네요.

One way to identify that could be to check the value of “window.onscroll”. If that contains a handler, then it could mean that there is infinite scrolling.

암튼, 기존 honeypot, infinite loops + redirect + multiply link 기법으로 로 스팸봇, 악성 crawler 를 엿먹이는 방법은 더이상 통하지 않음을 알수 있습니다.

아쉽습니다. 스패머들/공격자들은 다순히 차단하는거로 그쳐서는 안되고, 반대로 역공을 가서 그 스패머의 서버를 초토화 시키고 금전적으로 엄청난 피해를 가해 줘야 통쾌한건데, infinite loops + redirect 기법을 다 피해가는 bot 들이 제작되고 있느니, 간단하게 이 counter-attack (역공) 할 수 있는 script 을 짜기는 불가능 해보입니다.

crawler trap- 칼랜더 사용하기 이런 기법들 다 무용지물.

3. 대응방법

조금 구체적으로 소스코드를 공유해 가며 설명드리겠습니다.

이 튜토리얼 글을 쓰기 위해 사이트를 하나 생성했습니다.

http://hackya.com/buddy/

위에서 언급했듯이 스팸봇, 로그인봇 들은 워드프레스 사이트를 주 공격 대상으로 제작됩니다.
Korbuddy님 케이스 처럼 로그인 페이지 (http://hackya.com/buddy/wp-login.php) 가 공격받는 이유는, crawler/bot 제작자가 로그인 페이지 주소를 미리 알고 있으니 공격할 수 있는 겁니다.

어느 워드프레스 사이트나 로그인 페이지는 /wp-login.php 에 존재하니까요.

그러니 bot 을 제작할때 일단 저 페이지 (url)로 공격봇을 보내는걸로 script 이 시작됩니다.

스팸봇을 honeypot 기법으로 막는 방식의 가장 큰 문제는, 공격은 막아내지만 Korbuddy 님이 언급하신 것 처럼 그 공격을 막아내는 과정에서 트래픽이 어마어마하게 발생합니다.

스팸봇은 CPU 를 사용하지 않고 GPU 를 씁니다. AI 도 GPU 를 씁니다. 프로세싱 속도 때문에 GPU 를 씁니다. 보통 로그인 공격 시도 log 를 보면, 60초에 100번에서 140번 까지 로그인 시도를 반복 합니다. 이걸 지속하면 사이트가 다운될 정도로 DDoS 공격에 맞먹는 트래픽이 발생하기 때문에 사이트 운영자에게 엄청난 금전적 손해를 가져옵니다.

공격자가 짧게 짧게 공격을 나눠서 하는 이유는, 공격대상 사이트가 다운될까봐. 공격해서 숙주로 삼아야 할 사이트가 죽어버리면 안되죠. 공격은 하되 죽이지는 말아야 하니까, 로그인 공격을 짧게 짧게 나눠서 합니다.

이제 각자 본인의 로그인 페이지로 가보세요.

로그인 페이지

아직 아무런 scripting 도 하지 않았기 때문에 이런 모습일겁니다.
login_page1

로그인폼이 담겨있는 div 를 선택해 보십시오.

login_page2

작업해야 할 element 이름이 #login, 즉 <div id=”login”> 이란걸 알수 있습니다. 이 div 안에 로그인폼이 담겨 있습니다.

로그인 시도시 사람이 아닌 bot임을 확인해 막는거는 위에서 말씀드렸듯이 엄청난 트래픽 증가로 이어질 수 있습니다. 그래서 일단 이걸 원천 봉쇄 해야 합니다.

아, 여기서 잠깐. 원천봉쇄를 한다고 로그인 페이지 접근 자체를 막아버리면 that can defeat the whole purpose of what we are trying to do.

왜냐하면 다음 상황을 보시죠. crawler 가 index.php 로 들어옵니다. 스크립트 명령에 의해 wp-login.php 페이지로 이동합니다. 그런데 로그인 페이지에서 튕겨나가면 다시 index.php 로 돌아오게 됩니다. 스크립트 명령에 의해 wp-login.php 페이지로 이동합니다. -무한반복.

이게 무한loop 입니다. 이것 역시 엄청난 트래픽을 유발하게 됩니다.

그래서 일단 공격봇이 wp-login.php 페이지에 들어오는 것 까지는 허용을 해줘야 합니다.

들어와서 스크립트 명령에 의해 로그인 페이지를 parse 해서 로그인 폼 작성을 시작하게 되는데, 바로 이 과정을 막아야 트래픽 추가발생 없이 방어를 할 수 있게 됩니다.

404 페이지를 보여줘도 되는데/ 그런식의 플러그인도 존재하는데, 똑똑한/뚜렷한 목적이 있는 bot 에게는 이건 또다른 힌트를 제공할 뿐 입니다. “만약 404 페이지가 보이면 로그인 페이지를 찾아라.” 라는 명령을 전달받은 bot 일수도 있거든요.

반면 404 메세지도 뜨지 않고 wp-login.php 페이지에 로그인폼이 존재하지 않으면, 공격봇은 그냥 멍때리고 있게 됩니다. 로그인 폼에 로그인 시도를 하라는 명령을 하달받았는데, 로그인 폼이 없으니 멍때리고 있을 수 밖에요.

로그인폼을 문서에서 사라지게 하는거는 여러가지 방법이 존재하겠지만, 저는 jQuery 의 .remove 함수로 div#login 을 삭제시킬 생각입니다.

여기서 의문하나가 생기실 수 있는데,

css 의 display:none 으로 해도 사라지지 않느냐구요? 그렇게 생각하실 수 있는데, css 는 문서의 DOM 을 더하거나 삭제할 수 없습니다. 단지 사람의 눈에 보이지 않게 가릴 뿐 입니다. display:none 해도 bot 은 문서의 html 을 parse 하는거지, css 는 해석도 하지 않습니다.

이제 이 작업을 하기 위해서 처음 해야 할 일은?

일단 child 테마를 하나 만들어야 겠죠.

본 테마의 이름이 hueman 이니 저는 child 테마의 이름을 heman-child 로 정했습니다.

theme_name

child 테마는 style.css 파일 하나로도 만들어 질 수 있습니다. 이렇게.


/*
Theme Name: Hueman Child
Description: Child theme for hueman theme
Author: Matthew Park
Template: hueman
*/

@import url("../hueman/style.css");

/* custom css */

물론 @import 명령어는 로딩속도에 악영향을 끼치게 되고 + 저는 어짜피 로그인 페이지를 손봐야 하기 때문에 functions.php 파일을 하나 만들어서 functions.php 에서 부모 테마의 css 를 불러오도록 했습니다.

이제 jQuery 를 짜야겠죠.

jQuery( "#login" ).remove();

개간단하죠? (예제에 항상 jQuery 싸인 대신 $ 싸인이 표시되는데, 이 $ 싸인이 jQuery 의 alias 입니다. 가끔 jQuery 충돌이 날때는 이 $ 싸인을 “jQuery” 로 바꾸기만 해도 충돌나는게 해결됩니다. 애초부터 이런 충돌을 방지하려면 $ 싸인 대신 처음부터 “jQuery” 로 표시하는게 좋겠죠?)

저 jQuery 스크립을 이제 child 테마의 functions.php 파일에서 추가해 주기만 하면 됩니다.

명령어 추가는? add_action 으로 합니다. 이렇게

add_action('login_head', 'hackya_custom_login');

함수 자체는

wp_enqueue_script( 'custom-login', get_template_directory_uri() . '/login/custom-login.js', array( 'jquery' ), null, true );
}
add_action( 'login_head', 'remove_login' ); 

이런식으로 하면 될 것 같습니다. 일단 실행해 보겠습니다.

fails_to_include

엥? 뭥미? js 파일이 포함되지 않습니다.

http://hackya.com/buddy/wp-content/themes/hueman/login/custom-login.js

훔… 분명 child 테마의 functions.php 에서 js 를 포함시키는데도, 부모테마의 폴더에서 js 파일을 포함시키려고 하고 있네요.. 제가 잘못한건 하나도 없는데…

별수 없죠. 이런경우 구글링 해봐야 합니다. 분명 저와 동일한 문제를 겪고 있는 사람이 질문글을 어디엔가는 올렸을 겁니다.

https://teamtreehouse.com/community/wordpress-how-to-enqueue-script-to-child-theme

get_template_directory_uri() 대신에 get_stylesheet_directory_uri()

함수를 사용하니 문제가 해결되었다고 합니다. 오케이.

다시 try 해보죠.

잘 됩니다.

.remove 에 대한 documentation 을 상세하게 읽어 보았는데, DOM 에서 완전하게 삭제되는 함수기 때문에 bot 이 삭제된 DOM 을 parse 할수 없다고 나옵니다.

그렇지만, 공격봇이 아주 primitive (원시적/고전적) 인 bot 이라서 javascript 을 해석 못한다면?

로그인 부분이 삭제되지 않고 그냥 보여질 가능성도 있습니다.

이런경우 PHP 함수 preg_replace 를 사용해서 문서가 로딩되기 전 원천적으로 로그인 부분이 아예 출력되지 않게 하는 방법도 있네요. 이렇게.

$your_content = ob_get_contents();
  $your_content = preg_replace( '/\
Tags: , , , , ,

카테고리:

Ω

7 Comments

  • korbuddy.com says:

    역시 메튜님…ㅠ
    좋은 글 잘 읽었습니다. 지식이 부족해서 한번에 이해하기 어려웠어요.
    두고두고 읽어야 할 것 같습니다.

    • Matthew says:

      다음주에 글 마치고 소스코드 정리해서 보내드릴께요. 어떤 분들은 글로 설명을 읽는 것 보다 그냥 소스코드 보는게 더 이해가 잘 되시는 분들이 계세요.

      그나저나 일요일 새벽부터 왜 눈이 떠진건지 모르겠네요. 부활절이라 일찍 일어나진건지… 지금 새벽 6시가 조금 넘은 시각입니다.

      성경을 펼치니 이런 말씀이 나옵니다.

      “폭력으로 옳은 일을 하려고 하는 자는 내시가 처녀를 범하려는 것과 같다.”

      “감추어진 지혜와 숨겨둔 보물, 둘 다 무슨 소용인가?” – 좋은게 있으면 나눠가져라, 이런뜻이겠죠?

      집회서 20장에 나오는 말씀들 입니다. ㅎㅎㅎ

      • korbuddy.com says:

        이미 나눔을 실천하고 계신것 같아요 ㅎㅎ 전 이미 많이 나눠받았거든요 ㅎㅎ

        • Matthew says:

          사전양해를 구했어야 하는데.. 오래전부터 작성해야지 해야지 하면서 안하던 튜토리얼글이 하나 있었는데, 그 글을 아까 아침에 급하게 작성하면서 Korbuddy 님께 미리 양해를 구하지 못하고 작성하신 글을 하나 가져다 썼습니다. (예제가 필요해서요.)

          (튜토리얼 글)
          http://hackya.com/kr/미래지향적-개발-flexbox-모델/

          (예제)
          http://hackya.com/lab/css/flexbox/hueman_theme.html

          실제 어떤 모습으로 이 레이아웃이 보여지는지를 보여드리고 싶어서 Lorem Ipsum 텍스트가 아닌 Korbuddy 님이 작성한 글을 가져다 붙여넣었습니다.

          출처는 밝혔는데 혹시 기분언짢으시면 다른 text 로 대체하겠습니다.

          • korbuddy.com says:

            이런식으로라도 도움이 되신다면 저는 괜찮습니다 ㅎㅎ
            받은것에비해 드리는 것은 보잘것 없는 글뿐이라 부끄럽기까지 하네요ㅎㅎ
            항상 좋은 글 잘 읽고 있습니다!

  • Veteran says:

    고생하셨습니다.

    현재 글을 클래식 봇까지 배려(?)한다는 가정으로 정리하면, “기본 로그인 페이지를 빈 문서로 만들고, 기본 로그인 페이지 접근 시 커스텀 로그인 페이지로 리디렉트한다는 것”으로 요약할 수 있겠네요.

    감사합니다.

    • Matthew says:

      “기본 로그인 페이지를 빈 문서로 만들고, 기본 로그인 페이지 접근 시 커스텀 로그인 페이지로 리디렉트한다” 라기 보다는,

      Method 1. 기본 로그인 페이지를 빈 문서로 만들고, 기본 로그인 페이지 접근 시 crawler 가 뭘해야 할지를 모르게 만든다.

      Method 2. 기본 로그인 페이지를 빈 문서로 만들고, 기본 로그인 페이지 접근 시, 가짜 로그인 페이지로 리드렉트 한다.

      이렇게 두가지 approach 를 제안하고 있는 것 입니다.

      2번째 방식은, 아래 로그인 페이지를 방문하시면 눈으로 보실 수 있으십니다.

      http://hackya.com/buddy/wp-admin/

      2번째 방식의 설명과 소스코드는 이번 주말에 작성할 예정 입니다.

      제가 혼동이 왔던 부분은 (사실 아주 오래전부터 잘 알고 있었던 내용인데 왜 혼동이 왔었는지 모르겠습니다.)

      crawler 는 ajax 된 컨텐츠를 읽지 못한다 라는 사실만 기억을 하고,

      the browser can execute JavaScript and produce content on the fly – the crawler cannot.

      https://developers.google.com/webmasters/ajax-crawling/docs/learn-more#what-the-user-sees-what-the-crawler-sees

      하지만 crawler 에게 클릭할 수 있는 능력을 부여할 수 있다는 사실을 어처구니 없게도 망각해 버렸던거죠.

      http://hackerbot.net/tutorials/86-how-to-create-bot-click-key-mouse-macro-script

      구글봇 처럼 단순히 문서를 parse 하고 index 하려는 목적을 가진 bot 은 ajax 로 로딩되는 부분을 읽어내지 못하지만, 공격을 목적으로 하는 crawler 는 당연히 click 해서 문서가 로딩되도록 하는 기능을 갖추는게 정상인데, 이부분을 잠시나마 착각 했었습니다. ㅎㅎㅎㅎ

      그래서 제가 구상했던거는, #1,#2 의 방식으로 본래 로그인 페이지를 불능화 시킨후 header.php 나 다른 문서에 ajax 로 로딩되는 로그인 창 이었는데, 로그인 창을 ajax 로 불러온다고 해도, crawler 로 부터 완벽하게 감춰지는게 아니라서, 다른 어떤 방법이 있을지 계속 생각 중 입니다.

Leave a Reply to korbuddy.com Cancel reply

Your email address will not be published. Required fields are marked *