一、概述

今天接触到了一个陌生的名词:函数劫持,查了一些资料记录一下。
函数劫持的意思是在一个函数运行之前把它劫持下来,添加我们想要的功能,然后再调用原来的函数执行。这也是常见的钩子函数的原理之一。
举一个简单的例子:

1
2
3
4
5
var _alert = alert;
window.alert = function(s) {
console.log('alert:', s);
_alert(s);
}

二、应用

  1. XSS攻击经常使用alert测试是否存在跨站,所以可以通过劫持alert函数来监测是否有人在攻击你的网站。在监测的页面劫持alert函数,记录调用情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function report(caller) {
    var img=new Image();
    img.src=`http://www.site.com/getReport.php?caller=${encodeURIComponent(caller)}`;
    }
    var _alert = window.alert
    window.alert = function(s) {
    report(alert.caller)
    _alert(s)
    }
  2. 自定义业务功能。业务代码经常会有新需求的加入,对原代码进行修改匹配,常常比较耗费时间,这个时候就可以使用函数劫持,在不修改原业务逻辑的前提下,增加新功能。

三、反劫持

  1. 判断某个函数是否被劫持?

    1
    2
    3
    function foo() {console.log(foo);}
    alert.toString() // "function alert() { [native code] }"
    foo.toString() // "function foo() {console.log(foo);}"

    对于native代码内置函数如alert,输出打印时会显示为[native code],而自定义的函数会显示函数定义的内容,通过这个方法可以检查函数是否被劫持。
    注意到有一种伪装成native function的方式如下:

    1
    2
    3
    var fakeAlert = (function(){}).bind(null);
    console.log(window.alert.toString()); // function alert() { [native code] }
    console.log(fakeAlert.toString()); // function () { [native code] }

    bind(null)伪装后的函数不带函数名,所以更严格的检查需要检查函数名是否为空。

  2. 如何反劫持?
    通常劫持的都是一些可以向用户反馈信息的API,如alertevalconsole.log等。这些native代码通常难以还原,所以可以通过还原出一个纯净的环境iframe,在iframe中调用相关函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function createIframe(w) {
    var d = w.document;
    var newIframe = d.createElement("iframe");
    newIframe.style.width = 0;
    newIframe.style.height = 0;
    d.body.appendChild(newIframe);
    newIframe.contentWindow.document.write("<html><body></body></html>");
    return newIframe;
    }

    function injectScriptIntoIframe(f, proc) {
    var d = f.contentWindow.document;
    var s = "<script>\n(" + proc.toString() + ")();\n</script>";
    d.write(s);
    }

    对于涉及到反馈信息的API可以封装到函数中,调用方法在iframe中执行:

    1
    2
    3
    4
    5
    6
    // 将信息相关封装到函数中,在iframe中调用
    function payload() {
    // your code goes here
    }
    var f = createIframe(top);
    injectScriptIntoIframe(f, payload);

四、参考

[1] 函数劫持
[2] 谈谈JS中的函数劫持