Front-End Tracker로 DOM/Storage 분석하기

ZAP 팀에서 관리하는 도구들 중에 유독 관심이 가던 도구가 하나 있었습니다. 오늘은 드디어 정리를 마무리해서 블로그 글로 공유드리면서 어떻게 사용하는지, 이걸 통해 무엇을 할 수 있는지 이야기드리려고 합니다.

바로 Front-End Tracker입니다.

Set-up

Basic

Front-End Tracker는 Javascript 기반의 도구로 브라우저에서 동작합니다. 결국 브라우저 내부에서 js 코드를 로드하고 사용한다면 ZAP, Burp, Firefox, Chrome 등 환경을 가리지 않고 여러 구간에서 사용할 수 있습니다.

<script src="https://unpkg.com/@zaproxy/front-end-tracker@latest/dist/front-end-tracker.js" crossorigin="anonymous"></script>

위와 같이 unpkg를 통해 cdn으로도 제공되기 때문에 그냥 분석할 페이지에 저 스크립트만 로드되면 준비는 끝납니다.

Using ZAP

ZAP에는 HTTP Sender Script라고 모든 Req/Res에 개입할 수 있는 스크립트 타입이 있습니다. 이를 이용하면 테스트하는 모든 페이지, 또는 Scope에 해당하는 모든 페이지에 강제로 Front-End Tracker JS를 주입시킬 수 있고, 편리하게 테스트할 수 있습니다.

Build

먼저 Front-End Tracker를 빌드해줍시다.

# Clone 후 빌드합니다.
git clone https://github.com/zaproxy/front-end-tracker
cd front-end-tracker
npm install
npm run build

# dist/ 하위에 빌드된 결과가 나타나며, 사용하기 편리한 장소로 이동시켜주세요.
cp dist/front-end-tracker.js <YOUR-PATH>

Write sender script

ZAP의 Scripts로 사용할 script를 작성합니다. 아까 만든 파일을 모든 Response의 head 영역에 주입하는 코드입니다. HTTP Sender 예시는 Community script에도 있습니다. 참고하셔서 필요한 부분만 수정해주시면 됩니다.

FILE = '<YOUR-PATH>'
// e.g /test/dist/front-end-tracker.js

function loadScriptFromFile(file) {
    var Files = Java.type('java.nio.file.Files');
    var Paths = Java.type('java.nio.file.Paths');
    var String = Java.type('java.lang.String');

    var filePath = Paths.get(file);
    return new String(Files.readAllBytes(filePath), 'UTF-8');
}

function sendingRequest(msg, initiator, helper) {}

function responseReceived(msg, initiator, helper) {
    if (initiator != 1) { return; }

    var body = msg.getResponseBody();
    var bodyAsStr = body.toString();
    var header = msg.getResponseHeader();

    var xRequestedWith = msg.getRequestHeader().getHeader('X-Requested-With');
    var contentType = header.getHeader('Content-Type');
    var contentTypeRegex = new RegExp(/text\/html/g);
    var indexOfHead = bodyAsStr.indexOf('<head>');

    if (!contentTypeRegex.test(contentType)
        || xRequestedWith == 'XMLHttpRequest'
        || indexOfHead == -1) {
        return;
    }

    var SCRIPT = '<script>' + loadScriptFromFile(FILE) + '</script>';
    var index = indexOfHead + '<head>'.length();

    var newBody = bodyAsStr.slice(0, index) + SCRIPT + bodyAsStr.slice(index);

    msg.setResponseBody(newBody);
    header.setContentLength(msg.getResponseBody().length());
}

Enable script

해당 스크립트 => 우클릭(Context menu) => Enable Script로 활성화시킵니다.

이제 모든 Response에는 Front-End Tracker가 로드됩니다.

How to use

DOM-Events

mailbox object를 통해 dom-events를 구독합니다. subscribe 함수 내부에서 동작을 정의할 수도 있습니다.

const topic = 'dom-events';
mailbox.subscribe(topic, (_, data) => {
  console.log(data);
});

개발자 도구에서 위 코드를 실행하고, 이후 DOM에 이벤트가 발생하면 이렇게 Object로 console.log가 찍히게 됩니다.

Storage-Events

DOM-Events와 동일하게 mailbox로 구독합니다. 이 때 topic을 storage로 명시하면 됩니다. 이 경우 LocalStorage 등에 변경이 있는 경우 우리가 정의한 동작(console.log)로 인해 기록됩니다.

const topic = 'storage';
mailbox.subscribe(topic, (_, data) => {
  console.log(data);
});

예시로 제가 localStorage.setItem으로 임의로 item을 저장했을 때 storage 이벤트를 수신하고, 아래와 같이 console.log로 남겨줍니다.

Autoloading Hook

물론 매번 topic을 구독하는건 매우 번거로운 일입니다. 그래서 개인적으론 ZAP의 Sender Script에서 topic 구독 부분까지 추가하여 자동 로드하도록 지정하고, 발생하는 이벤트는 console.log 또는 API로 전송해서 별도의 페이지에서 확인하는게 좋아보입니다.

var HOOK = `
<script>
    const topic = 'dom-events';
    mailbox.subscribe(topic, (_, data) => {
    console.log(data);
    });
</script>
`
var SCRIPT = '<script>' + loadScriptFromFile(FILE) + '</script>' + HOOK;
var index = indexOfHead + '<head>'.length();

var newBody = bodyAsStr.slice(0, index) + SCRIPT + bodyAsStr.slice(index);

PubSubJS

Front-End Tracker는 PubSubJS라는 JS 라이브러리를 통해 만들어졌습니다.

https://github.com/mroderick/PubSubJS

해당 라이브러리는 의존성 없이 JS 내부에서 publish, subscribe을 지원해주는 라이브러리로 Front-End Tracker와 비슷한 또는 좀 더 다른 기능들을 수행하는 코드르 쉽게 만들 수 있습니다. 참고하시면 좋을 것 같아요.

Eval Villain

DOM을 추적한다는 액션 자체가 Eval villain이랑 굉장히 비슷합니다. 다만 Eval villain는 DOM 관련 공격에 맞춰져 있어 외부 입력 값이 내부에 어떤 영향을 줄 수 있는지 체크할 수 있는 도구라면, Front-End Tracker는 서비스의 기능이 어떻게 동작하는지 쉽게 파악할 수 있는 도구로 보시면 될 것 같습니다.

결과적으론 둘 다 쓰는게 가장 좋아보입니다 😀 (다만 충돌의 여지가 있긴해서, 잘 분기하며 사용해야할 것 같네요)

Eval Villain이 궁금하다면?

References