在某些 XSS 场景中我们需要劫持页面中的一些请求,前端页面中主要使用 XHR 来上报/获取数据。但是在一些对兼容性要求很高的 PC 端页面,网络请求可能是通过 JSONP 的方式发送的,如果是 XHR 请求就很简单,直接第三方页面上的 XHR 对象就可以拿到请求或响应的数据,如果是 JSONP,就不能直接拦截了,但是可以通过一些巧妙的方式来实现。

拦截 JSONP 首先我们得知道他是如何工作的,简而言之就是直接在 head 标签动态插入一个 script 标签,然后在 url 上带上需要执行的回调函数名称,script 标签发起一个不受同源策略限制的 GET 请求,服务端将返回的数据塞给回调函数作为参数,然后页面上直接就执行对应的回调函数。所以要劫持 JSONP 请求我们需要做到:

  • 捕获请求发送的时机以及获得请求的参数;
  • 重写页面上已经定义的回调函数,步获传入的参数;
  • 重新执行页面上正确的回调函数,保证之前的逻辑正常。

MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。

通过这个 API 我们就有办法来步获 JSONP 发起的请求。大部分 JSONP 都是直接在 head 标签进行 script 插入的,所以直接监听 head 的 DOM 变化,匹配目标 DOM 和 URL 看是否和 JSONP 请求一致即可解析出 callback 名称以及对应的参数,demo 实现如下:

var MutationObserver = window.MutationObserver || window.WebKitMutationObserver,
  callbackName,
  observer,
  $head,
  regx;

$head = document.querySelector('head');
// 匹配 callback 名称
regx = /callback=(callbackName)/;

observer = new MutationObserver(function (mutations) {
  mutations.forEach(function (mutation) {
    mutation.addedNodes.forEach(function (node) {
      if (
        node.src &&
        node.src.indexOf('callback') != -1 &&
        regx.test(node.src)
      ) {
        callbackName = node.src.match(regx)[1];
        hack = callbackName + '__hack__';
        window[hack] = window[callbackName];
        window[callbackName] = function () {
          // 通过 arguments 即可拿到回调数据
          // do something
          window[hack].apply(window, arguments);
          delete window[hack];
        };
      }
    });
  });
});