0x00 序、前言

前几周在网上冲浪,无意间在 RSS 里看到这么一篇文章《欺骗Wappalyzer插件指纹识别》。里面详细的介绍了 Wappalyzer 这款浏览器拓展的工作原理以及如何欺骗其结果。当时扫了一眼觉得这篇文章很有意思,可以拿来改造蜜罐,就在此基础上做了进一步的研究。

0x01 什么是Wappalyzer?

Wappalyzer 是一个浏览器拓展,这款拓展可以通过分析服务器返回响应数据(如Header,Cookie,Dom,Script等)来识别出网站采用了哪些Web框架或技术。

0x02 Wappalyzer 的识别

Wappalyzer 主要用来识别 Web 指纹,很明显,正经人谁会装这个拓展啊?

既然你用 Wappalyzer 识别我的 Web 服务器指纹,那我能不能通过一种方法,来识别你是不是在我的 Web 站点上启用了 Wappalyzer,如果启用了 Wappalyzer,那我就把你重定向到蜜罐里去采集你的浏览器指纹,以其人之道还治其人之身岂不美滋滋?

当然是可以的啦,不然我写这段干什么。

Wappalyzer 在识别前端使用的技术时,会在页面中注入一个 js 文件并执行(吐槽:浏览器拓展经典 content_scripts 了,我现在按 F12,页面下面一堆插件注入的 js……),在文章开头引用的博客中,作者通过Hook removeEventListener函数来向 Wappalyzer 发送伪造的指纹信息,我们也可以通过此方法在页面中加入检测代码,当 Wappalyzer 试图移除 Listener的时候进行一些特殊的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!doctype html>

<head>
    <title>Wappalyzer 测试页</title>
    <meta charset="utf-8">
    <meta content="width=device-width, initial-scale=1" name="viewport">

    <script>
        let rel = removeEventListener;
        removeEventListener = (name, func, opt) => {
            if (
                name === "message" &&
                func &&
                func.toString().includes("wappalyzer.technologies") !== -1 &&
                func.toString().includes("removeEventListener") !== -1 &&
                func.toString().includes("__UNDEFINED__") !== -1 &&
                func.toString().includes("postMessage") !== -1
            ) {
                detector();
                rel(name, func, opt);
            } else {
                rel(name, func, opt);
            }
        };

        const detector = () => {
            alert("哦豁,你居然开着 Wappalyzer !");
        };
    </script>
</head>

<body>
    <p>Hello world!</p>
</body>
</html>

0x03 诱骗Wappalyzer 识别结果

上一章也提到了,Wappalyzer 在识别js 指纹的时候,会注入一个 js 至页面中执行,我们可以在页面的 js 里,最终将识别完成的结果通过 postMessage 函数发送给 Wappalyzer。这就使得我们可以随意的修改 Wappalyzer 识别出的前端技术结果。

不过有意思的是,文章开头引用的博客作者在完成博客后,在GitHub的Wappalyzer项目中提了一个 Issue 来反馈所发现的问题,然而其中一名贡献者在感谢其发现后,提了一嘴:“欺骗不需要这么复杂”。然后给了一个更加简单的 Poc(虽然我懂大概意思但试了没成功,还是用大佬的思路)。

总之我们可以根据 Wappalyzer 的指纹库,把需要展示的结果丢给 Wappalyzer 好了。

例如我希望让 Wappalyzer 显示我这个什么都没使用的页面使用使用了一大堆的技术,只要照着指纹库一个个填上去就好,简直和狂欢一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<!doctype html>

<head>
    <title>Wappalyzer 测试页</title>
    <meta charset="utf-8">
    <meta content="width=device-width, initial-scale=1" name="viewport">

    <script>
        let rel = removeEventListener;
        removeEventListener = (name, func, opt) => {
            if (
                name === "message" &&
                func &&
                func.toString().includes("wappalyzer.technologies") !== -1 &&
                func.toString().includes("removeEventListener") !== -1 &&
                func.toString().includes("__UNDEFINED__") !== -1 &&
                func.toString().includes("postMessage") !== -1
            ) {
                poc();
                rel(name, func, opt);
            } else {
                rel(name, func, opt);
            }
        };

        const poc = () => {
            postMessage({
                wappalyzer: {
                    js: [{
                            name: "2B Advice",
                            chain: "BBCookieControler",
                            value: true,
                        },
                        {
                            name: "33Across",
                            chain: "Tynt",
                            value: true,
                        },
                        {
                            name: "4-Tell",
                            chain: "_4TellBoost",
                            value: true,
                        },
                        {
                            name: "@sulu/web",
                            chain: "web.startComponents",
                            value: true,
                        },
                        {
                            name: "A-Frame",
                            chain: "AFRAME.version",
                            value: true,
                        },
                        {
                            name: "A8.net",
                            chain: "A8salesCookieRepository",
                            value: true,
                        },
                        {
                            name: "A8.net",
                            chain: "a8sales",
                            value: true,
                        },
                        {
                            name: "A8.net",
                            chain: "map_A8",
                            value: true,
                        }
                    ]
                }
            });
        };
    </script>
</head>
<body>
    <p>Hello world!</p>
</body>
</html>

0x04 反制 Wappalyzer

最后的最后,原文作者还发现了一个 Wappalyzer 的 XSS 漏洞,但只可惜是一个 self XSS,没有什么利用价值。

当时看到的时候心里还激动了一下,说是能不能用这个 XSS 加上 Wappalyzer 自己申请的权限,去调用 Chrome 的特权API,结果试了一下确实不行,因为 Chrome 在这一块做了隔离,就算是是 Extension DOM 调用的函数,在 Webpage DOM 里也是没有办法访问特权API的;不过这个 XSS 也在最新的版本中修复了.

0x05 总结

本次主要从一篇大佬的博客开始,通过对 Wappalyzer 的工作原理入手,对 Wappalyzer 进行识别和干扰。

虽然很可惜,这次的 Wappalyzer XSS 漏洞并不能干出什么坏事,但我们也了解到是因为 Chrome 的沙盒机制,将我们的脚本与 Wappalyzer 的脚本做了隔离。如果后续有什么浏览器,可以通过前台 postMessage 的方式,将注入代码提交到拓展的脚本中执行,那这样的 XSS 漏洞就可以利用拓展在 manifest.json 中所声明的所有特权 API了,就会严重的多。

0x06 参考