요즘에는 많은 웹 보안 사고에 자동화가 개입됩니다. 웹 스크래핑, 비밀번호 재사용, 클릭 사기 공격은 실제 사용자를 흉내 내려는 공격자에 의해 자행되므로 브라우저에서 접속하는 것처럼 위장하려 합니다. 웹사이트 소유자는 실제 사람에게 서비스를 제공하고 있는지 확인하고 싶을 것이며, 웹 서비스 제공자는 프로그램을 통한 콘텐츠 접근이 더 무겁고 안정성이 떨어지는 웹 인터페이스를 스크래핑하는 대신 API를 통해 이루어지기를 원할 것입니다.
cURL과 유사한 방문자를 걸러내는 기본적인 검사를 갖추었다고 가정하면, 그다음으로 합리적인 단계는 방문자가 PhantomJS나 SlimerJS 같은 헤드리스 브라우저가 아니라 실제 UI 기반 브라우저를 사용하고 있는지 확인하는 것입니다.
이 글에서는 PhantomJS를 통한 방문을 식별하는 몇 가지 기법을 소개합니다. PhantomJS가 가장 널리 사용되는 헤드리스 브라우저 환경이기 때문에 이를 중점적으로 다루기로 했지만, 여기서 살펴볼 여러 개념은 SlimerJS와 기타 도구에도 적용할 수 있습니다.
참고: 별도로 명시하지 않는 한, 이 글에서 소개하는 기법은 PhantomJS 1.x와 2.x 모두에 적용됩니다. 먼저 살펴볼 내용은 다음과 같습니다. 요청에 응답하지 않고도 PhantomJS를 탐지할 수 있을까요?
HTTP 스택
아시다시피 PhantomJS는 Qt 프레임워크를 기반으로 구축되었습니다. Qt의 HTTP 스택 구현 방식은 다른 최신 브라우저와 차이가 있습니다.
먼저, 다음과 같은 헤더를 전송하는 Chrome을 살펴보겠습니다:

하지만 PhantomJS에서는 동일한 HTTP 요청이 다음과 같이 표시됩니다:

PhantomJS 헤더는 몇 가지 미묘한 측면에서 Chrome과 다르며, 실제로는 다른 모든 최신 브라우저와도 다르다는 것을 알 수 있습니다:
- Host 헤더가 마지막에 표시됩니다
- Connection 헤더 값에 대문자와 소문자가 혼용되어 있습니다
- 유일한 Accept-Encoding 값은 gzip입니다
- User-Agent에 “PhantomJS”가 포함되어 있음
서버에서 이러한 HTTP 헤더의 이상 징후를 확인하면 PhantomJS 브라우저를 식별할 수 있습니다.
하지만 이러한 값을 그대로 믿어도 안전할까요? 공격자가 헤드리스 브라우저 앞단에서 프록시를 사용해 헤더를 다시 작성한다면, 해당 헤더를 일반적인 최신 브라우저처럼 보이도록 변경할 수 있습니다.
이 문제를 서버에서만 해결하려는 것은 만능 해결책이 아닌 듯합니다. 그렇다면 PhantomJS의 JavaScript 환경을 활용해 클라이언트에서 무엇을 할 수 있는지 살펴보겠습니다.
클라이언트 측 User-Agent 확인
HTTP를 통해 전달되는 User-Agent 값은 신뢰하기 어려울 수 있지만, 클라이언트 측에서는 어떨까요?

안타깝게도 PhantomJS에서는 user-agent 헤더와 navigator.userAgent 값을 변경하는 것도 마찬가지로 매우 간단하므로, 이것만으로는 충분하지 않을 수 있습니다.
플러그인
navigator.plugins에는 브라우저에 설치된 플러그인 배열이 포함되어 있습니다. 일반적인 플러그인 값으로는 Flash, ActiveX, Java 애플릿 지원, 그리고 “기본 브라우저 도우미”가 있습니다. 기본 브라우저 도우미는 이 브라우저가 OS X의 기본 브라우저인지 여부를 나타내는 플러그인입니다. 조사 결과, 일반적인 브라우저를 새로 설치하면 대부분 모바일에서도 기본 플러그인이 하나 이상 포함되어 있었습니다.
이는 플러그인을 전혀 구현하지 않으며 플러그인을 추가할 방법도 제공하지 않는 PhantomJS와는 다릅니다(PhantomJS API를 사용해도).
그렇다면 다음과 같은 검사가 유용할 수 있습니다:

반면, PhantomJS JavaScript 환경을 수정하면 이 플러그인 배열을 상당히 쉽게 위조할 수 있습니다. 수정은 페이지가 로드되기 전에 이루어져야 합니다.
실제로 구현된 플러그인이 포함된 PhantomJS의 커스텀 빌드도 충분히 상상할 수 있습니다. 이는 생각보다 어렵지 않은데, PhantomJS의 기반인 Qt 프레임워크가 플러그인 구현을 위한 네이티브 API를 제공하기 때문입니다.
타이밍
또 한 가지 흥미로운 점은 PhantomJS가 JavaScript 대화 상자를 표시하지 않도록 처리하는 방식입니다:

여러 차례 측정한 결과, 경고 대화 상자가 15밀리초 이내에 닫힌다면 해당 브라우저는 사람이 조작하고 있지 않을 가능성이 높습니다. 하지만 이 방식을 사용하면 실제 사용자에게 직접 닫아야 하는 경고를 표시해 불편을 끼치게 됩니다.
전역 속성
PhantomJS 1.x는 전역 객체에 두 가지 속성을 노출합니다:

하지만 이러한 속성은 실험적 기능의 일부이며 향후 변경될 수 있습니다.
JavaScript 엔진 기능 부족
PhantomJS 1.x 및 2.x는 현재 오래된 WebKit 엔진을 사용하므로, 최신 브라우저에는 있지만 PhantomJS에는 없는 브라우저 기능이 있습니다. 이는 JavaScript 엔진에도 해당하며, 일부 네이티브 속성과 메서드는 PhantomJS에서 다르게 동작하거나 아예 존재하지 않습니다.
그러한 메서드 중 하나가 Function.prototype.bind인데, PhantomJS 1.x 이하 버전에는 이 메서드가 없습니다. 다음 예제에서는 bind가 존재하는지, 그리고 실행 환경에서 위조되지 않았는지 확인합니다.

이 코드는 여기에서 자세히 설명하기에는 다소 복잡하지만, 자세한 내용은 저희 발표 자료에서 확인하실 수 있습니다.
스택 추적
PhantomJS가 evaluate 명령을 통해 실행한 JavaScript 코드에서 발생한 오류에는 고유하게 식별 가능한 스택 추적 정보가 포함되어 있어, 이를 통해 헤드리스 브라우저를 식별할 수 있습니다.
예를 들어 PhantomJS가 다음 코드에 대해 evaluate를 호출한다고 가정해 보겠습니다:

이 예제에서는 사용자 정의 indexOfString() 함수를 사용한다는 점에 유의하세요. 이 함수의 구현은 독자의 연습 과제로 남겨 두었습니다. 네이티브 String.prototype.indexOf는 PhantomJS에 의해 조작되어 항상 음수 결과를 반환할 수 있기 때문입니다.
그렇다면 PhantomJS 스크립트가 이 코드를 평가하도록 하려면 어떻게 해야 할까요? 한 가지 방법은 호출될 가능성이 높은, 자주 사용되는 DOM API 함수 몇 가지를 재정의하는 것입니다. 예를 들어 아래 코드는 브라우저의 스택 추적을 검사하도록 document.querySelectorAll을 재정의합니다:

요약
이 글에서는 서버에서 식별하는 방법과 PhantomJS의 클라이언트 JavaScript 환경에서 코드를 실행하는 방법을 포함해, PhantomJS를 식별하는 7가지 기법을 살펴보았습니다. 탐지 결과를 강력한 대응 메커니즘과 결합하면(예: 동적 페이지를 작동하지 않게 만들거나 현재 세션 쿠키를 무효화하는 방식) PhantomJS 방문자에게 상당한 장벽을 세울 수 있습니다. 다만 이러한 기법이 완벽한 것은 아니며, 정교한 공격자는 결국 이를 우회할 수 있다는 점을 항상 염두에 두어야 합니다.
자세한 내용을 알아보려면 AppSec USA 2014에서 진행한 발표 영상 (슬라이드)을 시청해 보시기 바랍니다. 또한 여기에서 소개한 기법의 구현 예시와 가능한 우회 방법을 담은 GitHub 저장소도 마련했습니다.
읽어주셔서 감사합니다. 즐거운 버그 헌팅 되세요.
기여자
Sergey Shekyan – @sshekyan
Ben Vinegar – @bentlegen
Bei Zhang – @ikarienator
작성자 소개
관련 블로그 게시물
성평등을 향한 여정: 사이버 보안 분야에서 성공적인 커리어를 개척하는 방법에 관한 Jenn Gile과의 Q&A
F5의 ‘성평등을 향한 여정’ 시리즈 네 번째 Q&A에서는 Rachael Shah와 NGINX 제품 마케팅 책임자 Jenn Gile이 사이버 보안 직종에 대한 오해를 바로잡고, 사이버 보안 분야의 성별 격차를 해소하기 위한 조언과 그 이점에 대해 이야기합니다.
TLS 검사에서 피터 파커 원칙 다루기
“큰 힘에는 큰 책임이 따른다.” 볼테르와 처칠도 이와 비슷한 말을 남겼지만, 이 문구는 스파이더맨 코믹스에서 벤 삼촌의 말로 가장 널리 알려져 있습니다. 물론 이 문구가 문화적으로 널리 통용되는 이유 중 하나는 TLS 검사를 비롯해 수많은 상황과 주제에 적용할 수 있기 때문입니다.
Venafi와 F5를 통한 머신 아이덴티티 보호
‘머신 아이덴티티’란 간단히 말해 자동화된 프로세스가 자신의 신원을 식별할 수 있도록 하고, 다른 자동화 시스템과 프로세스가 관련 리소스에 적절한 수준의 접근 권한을 부여하도록 하는 방법을 의미합니다. 이 개념 자체는 새로운 것이 아닙니다. 비교적 새로운 점은 이를 구현해야 하는 규모입니다.
마이너와 미니언: 데이터 유출만이 비용을 초래하는 것은 아닙니다
물론 데이터 유출은 더 큰 비용을 초래할 수 있고(적어도 지금은), 실패 사실을 세상에 드러낼 수밖에 없게 합니다(통지 의무가 있으니까요). 하지만 보안을 단순히 데이터 노출 문제로만 봐서는 안 됩니다. 요즘은 데이터가...
내 클라우드에서 나가!
F5의 Jay Kelley가 전하는 최신 소식에는 Microsoft Office 365용 신규 ID 및 액세스 솔루션을 배포할 때 고려해야 할 사항이 정리되어 있습니다.
Shift(웹 앱) 보안 레터
웹 앱 보안을 개발 초기 단계로 앞당기세요. F5 블로그에서 DevOps 파이프라인 초기에 보안을 통합하여 취약점을 예방하고 시간을 절약하는 방법을 알아보세요.