요즘은 많은 웹 보안 사고가 자동화와 관련이 있습니다. 웹 스크래핑, 비밀번호 재사용, 클릭 사기 공격은 실제 사용자를 모방하려는 공격자에 의해 자행되므로, 마치 브라우저에서 오는 것처럼 보이려고 시도합니다. 웹사이트 소유자는 사용자에게 서비스를 제공하고 싶어하며, 웹 서비스 제공자는 덜 안정적이고 무거운 웹 인터페이스를 통해 스크래핑하는 대신 API를 통해 콘텐츠에 프로그래밍 방식으로 액세스할 수 있기를 원합니다.
cURL과 유사한 방문자에 대한 기본적인 검사가 있다고 가정하면, 다음으로 할 수 있는 합리적인 단계는 방문자가 PhantomJS 및 SlimerJS 와 같은 헤드리스 브라우저가 아닌 실제 UI 기반 브라우저를 사용하고 있는지 확인하는 것입니다.
이 글에서는 PhantomJS를 이용한 방문자 식별을 위한 몇 가지 기술을 설명하겠습니다. 우리는 가장 인기 있는 헤드리스 브라우저 환경인 PhantomJS에 초점을 맞추기로 했지만, 다룰 개념의 대부분은 SlimerJS와 다른 도구에도 적용할 수 있습니다.
메모: 이 문서에서 제시하는 기술은 특별히 언급하지 않는 한 PhantomJS 1.x와 2.x 모두에 적용할 수 있습니다. 첫째, PhantomJS에 대응하지 않고도 이를 감지하는 것이 가능할까요?
아시다시피 PhantomJS는 Qt 프레임워크 기반으로 구축되었습니다. Qt가 HTTP 스택을 구현하는 방식은 다른 최신 브라우저와 차별화됩니다.
먼저, 다음 헤더를 보내는 Chrome을 살펴보겠습니다.
하지만 PhantomJS에서는 동일한 HTTP 요청이 다음과 같습니다.
PhantomJS 헤더는 몇 가지 미묘한 방식으로 Chrome(그리고 결과적으로 다른 모든 최신 브라우저)과 다르다는 것을 알 수 있습니다.
서버에서 이러한 HTTP 헤더 이상을 확인하면 PhantomJS 브라우저를 식별할 수 있습니다.
하지만 이런 가치들을 믿는 것이 안전할까요? 적대자가 프록시를 사용하여 헤드리스 브라우저 앞에서 헤더를 다시 쓰는 경우, 해당 헤더를 수정하여 일반적인 최신 브라우저처럼 보일 수 있습니다.
이 문제를 서버에서만 해결하는 것은 만병통치약이 아닌 듯합니다. 그러면 PhantomJS의 JavaScript 환경을 사용하여 클라이언트에서 무엇을 할 수 있는지 살펴보겠습니다.
HTTP를 통해 전달된 User-Agent 값을 신뢰할 수 없을 수도 있지만 클라이언트에서는 어떨까요?
안타깝게도 PhantomJS에서는 user-agent 헤더와 navigator.userAgent 값을 변경하는 것도 마찬가지로 간단하기 때문에 이것만으로는 충분하지 않을 수 있습니다.
navigator.plugins에는 브라우저 내에 존재하는 플러그인 배열이 포함되어 있습니다. 일반적인 플러그인 값에는 Flash, ActiveX, Java 애플릿 지원, 그리고 이 브라우저가 OS X의 기본 브라우저인지 여부를 나타내는 플러그인인 " 기본 브라우저 도우미 "가 포함됩니다. 저희의 조사에 따르면, 일반적인 브라우저의 대부분 신규 설치에는 모바일에서도 하나 이상의 기본 플러그인이 포함되어 있습니다.
이는 PhantomJS와는 다릅니다. PhantomJS는 어떤 플러그인도 구현하지 않고, 플러그인을 추가하는 방법( PhantomJS API 사용)도 제공하지 않습니다.
그러면 다음과 같은 확인이 유용할 수 있습니다.
반면, 페이지가 로드되기 전에 PhantomJS JavaScript 환경을 수정하여 이 플러그인 배열을 스푸핑하는 것은 매우 간단합니다.
실제로 구현된 플러그인을 사용하여 PhantomJS를 사용자 정의 빌드하는 것을 상상하는 것도 어렵지 않습니다. 이 작업은 PhantomJS가 기반으로 하는 Qt 프레임워크에서 플러그인을 구현하기 위한 기본 API를 제공하기 때문에 생각보다 쉽습니다.
또 다른 관심사항은 PhantomJS가 JavaScript 대화 상자를 억제하는 방식입니다.
여러 번 측정한 결과, 알림 대화 상자가 15밀리초 이내에 사라지면 브라우저가 사람이 제어하고 있지 않을 가능성이 높은 것으로 나타났습니다. 하지만 이 접근 방식을 사용하면 실제 사용자에게 수동으로 닫아야 하는 알림으로 귀찮게 해야 합니다.
PhantomJS 1.x는 글로벌 객체에 두 가지 속성을 노출합니다.
그러나 이러한 속성은 실험적 기능 의 일부이며 앞으로 변경될 수 있습니다.
PhantomJS 1.x 및 2.x는 현재 오래된 WebKit 엔진을 사용하고 있습니다. 즉, PhantomJS에는 없는 최신 브라우저에 존재하는 브라우저 기능이 있다는 의미입니다. 이는 JavaScript 엔진까지 확장되어 PhantomJS에서는 일부 기본 속성과 메서드가 다르거나 없습니다.
그러한 메서드 중 하나는 Function.prototype.bind인데, 이 기능은 PhantomJS 1.x 및 이전 버전에서는 없습니다. 다음 예제에서는 bind가 있는지, 실행 환경에서 스푸핑되지 않았는지 확인합니다.
이 예제에서는 사용자 정의 indexOfString() 함수를 사용합니다. 이는 PhantomJS가 기본 String.prototype.indexOf를 스푸핑하여 항상 음수 결과를 반환할 수 있기 때문에 독자의 연습으로 남겨둡니다.
그러면 PhantomJS 스크립트를 이용해 이 코드를 평가하려면 어떻게 해야 할까요? 한 가지 기술은 호출될 가능성이 높은 일부 자주 사용되는 DOM API 함수를 재정의하는 것입니다. 예를 들어, 아래 코드는 브라우저의 스택 추적을 검사하기 위해 document.querySelectorAll을 재정의합니다.
이 글에서는 PhantomJS를 식별하기 위한 7가지 다른 기술을 살펴보았습니다. 이는 서버에서와 PhantomJS의 클라이언트 JavaScript 환경에서 코드를 실행하는 방식 모두에서 가능합니다. 감지 결과를 강력한 피드백 메커니즘과 결합하면(예: 동적 페이지를 비활성화하거나 현재 세션 쿠키를 무효화) PhantomJS 방문자에게 견고한 장애물을 제공할 수 있습니다. 하지만 이러한 기술이 완벽하지 않다는 점과 정교한 적대 세력은 결국에는 침투할 것이라는 점을 항상 명심하세요.
자세한 내용을 알아보려면 AppSec USA 2014의 프레젠테이션 녹화본( 슬라이드 )을 시청해 보시기 바랍니다. 여기에 제시된 기술의 구현 사례와 가능한 우회 방법을 담은 GitHub 저장소 도 마련했습니다.
읽어주셔서 감사합니다. 즐거운 사냥 되세요.