使用Wappalyzer 分析页面

5,390 阅读4分钟

Wappalyzer 分析页面

一个谷歌插件

今天同事推荐了我一个谷歌插件 Wappalyzer,

Wappalyzer 是一款浏览器插件,通过 Wappalyzer 可以识别出网站采用了那种 web 技术。它能够检测出 CMS 和电子商务系统、留言板、javascript 框架,主机面板,分析统计工具和其它的一些 web 系统。The company behind Wappalyzer 还能够收集 web 程序的一些信息用于统计分析,揭示出各种 web 系统的使用率即增长情况。实际 Wappalyzer 就是一个指纹识别工具。

效果图如下 1

那这是什么原理呢?

然后我就很好奇这个插件是怎么实现的,于是我就想看这个插件的源码。

既然是一个 chrome 插件,那么就是安装在本地的

我们浏览器输入

chrome://version

会看到如下截图

2
2

其中我们可以看到个人资料路径,我们进入这个路径

扩展程序就在当前路径下面的 Extensions 文件夹中, 我们可以看到很多乱码一样的文件夹名称,其实这些就是谷歌插件的 id

3
3

于是我们回到谷歌浏览器中输入 [chrome://extensions/](chrome://extensions/)

我们就可以看到 ID:gppongmhjkpfnbhagpmjfkannfbllamg

4
4

再回到刚刚打开的文件夹中,找到对应 id 的文件夹拖入到开发工具中

5
5

通过 manifest.json 文件我们可以知道插件会在后台运行 html/background.html 这个文件

打开此文件发现这个文件只是引入了四个 js

<script src="../node_modules/webextension-polyfill/dist/browser-polyfill.js"></script>
<script src="../js/wappalyzer.js"></script>
<script src="../js/driver.js"></script>
<script src="../js/lib/network.js"></script>

​ 第一个文件是 polyfill,先不看

weppalyzer.js 中我们可以看到他导出了一个类

if (typeof module === 'object') {
  module.exports = Wappalyzer
}

于是我们进去这个类,我通过vscode自带outline可以看出 方法分为analyze*parse*

6
6

我们先看analyze方法 可以看到以下核心的方法,遍历this.apps然后调用内部 this.analyze* 方法,

....省略一些代码
Object.keys(this.apps).forEach((appName) => {
      apps[appName] =
        this.detected[url.canonical] && this.detected[url.canonical][appName]
          ? this.detected[url.canonical][appName]
          : new Application(appName, this.apps[appName])

      const app = apps[appName]

      promises.push(this.analyzeUrl(app, url))

      if (html) {
        promises.push(this.analyzeHtml(app, html))
        promises.push(this.analyzeMeta(app, metaTags))
      }

      if (scripts) {
        promises.push(this.analyzeScripts(app, scripts))
      }

      if (cookies) {
        promises.push(this.analyzeCookies(app, cookies))
      }

      if (headers) {
        promises.push(this.analyzeHeaders(app, headers))
      }
    })
    ....省略一些代码

那么this.apps 是哪里来的呢, 我们可以从 driver.js 中

<script src="../js/driver.js"></script>

// driver.js, 先new了一个Wappalyzer
const wappalyzer = new Wappalyzer()

// 375行左右有一个Init

    const response = await fetch('../apps.json')
    const json = await response.json()

    wappalyzer.apps = json.apps
    wappalyzer.categories = json.categories


我们可以看到是从 apps.json 我们可以看到很多没听过的 app,但是也有很多,

"Ant Design": {
    "cats": [
        12
    ],
    "html": [
        "<[^>]*class=\"ant-(?:btn|col|row|layout|breadcrumb|menu|pagination|steps|select|cascader|checkbox|calendar|form|input-number|input|mention|rate|radio|slider|switch|tree-select|time-picker|transfer|upload|avatar|badge|card|carousel|collapse|list|popover|tooltip|table|tabs|tag|timeline|tree|alert|modal|message|notification|progress|popconfirm|spin|anchor|back-top|divider|drawer)",
        "<i class=\"anticon anticon-"
    ],
    "icon": "Ant Design.svg",
    "js": {
        "antd": ""
    },
    "website": "https://ant.design"
}

比如 Ant Design 的配置,我们可以看到有一个html 里面是通过 html 来匹配的,于是我们看 wapplyzer.js中的 analyzeHtml 方法,发现他是通过正则来匹配的

伪造指纹

我们新建一个空白的 html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>

</body>
</html>

本地启动一个express服务,打开插件可以看到有下面几个

7
7

那如果我们页面上加一个 符合"<i class=\"anticon anticon-"的标签呢

<i class="anticon anticon-1"></i>

我们可以看到多了 Ant Design

8
8

到此文章就告一段落了,其实 Wappalyzer 里面还是有很多有意思的东西的 比如说 apps.json中的 implies 字段,

 "Element UI": {
     "cats": [
         12
     ],
     "icon": "ElementUI.svg",
     "implies": "Vue.js",
     "html": [
         "<(?:div|button) class=\"el-(?:table-column|table-filter|popper|pagination|pager|select-group|form|form-item|color-predefine|color-hue-slider|color-svpanel|color-alpha-slider|color-dropdown|color-picker|badge|tree|tree-node|select|message|dialog|checkbox|checkbox-button|checkbox-group|container|steps|carousel|menu|menu-item|submenu|menu-item-group|button|button-group|card|table|select-dropdown|row|tabs|notification|radio|progress|progress-bar|tag|popover|tooltip|cascader|cascader-menus|cascader-menu|time-spinner|spinner|spinner-inner|transfer|transfer-panel|rate|slider|dropdown|dropdown-menu|textarea|input|input-group|popup-parent|radio-group|main|breadcrumb|time-range-picker|date-range-picker|year-table|date-editor|range-editor|time-spinner|date-picker|time-panel|date-table|month-table|picker-panel|collapse|collapse-item|alert|select-dropdown|select-dropdown__empty|select-dropdown__wrap|select-dropdown__list|scrollbar|switch|carousel|upload|upload-dragger|upload-list|upload-cover|aside|input-number|header|message-box|footer|radio-button|step|autocomplete|autocomplete-suggestion|loading-parent|loading-mask|loading-spinner|)"
     ],
     "website": "https://element.eleme.io/"
 },

​ 比如这一段,意思就是,用了Element UI 一定用了 Vue.js

参考资料

本文使用 mdnice 排版