爬虫 JavaScript 篇[Web 漏洞扫描器]

0x00 前言

上一篇主要讲了如何通过修改 Chromium 代码为 Web 漏洞扫描器的爬虫打造一个稳定可靠的 headless 浏览器。这一篇我们从浏览器底层走到上层,从 C++ 切换到 JavaScript,讲一下如何通过向浏览器页面注入 JavaScript 代码来尽可能地获取页面上的链接信息。

0x01 注入 JavaScript 的时间点

首先我们要解决的第一个问题是:在什么时间点向浏览器页面注入 JavaScript 代码?

答案非常简单, 在页面加载前,我们希望能够注入一段 JavaScript 代码以便于能够 Hook、备份各种未被污染的函数, 在页面加载后,我们希望能够注入一段 JavaScript 代码以便于能够进行遍历各个元素、触发各种事件、获取链接信息等操作。

那么下一个问题又来了:怎么定义页面加载前、页面加载后?

页面加载前的定义非常简单,只要能在用户代码执行前执行我们注入的 JavaScript 代码即可,也就是在页面创建之后、用户代码执行之前的时间段对于我们来说都算是页面加载前,CDP 刚好提供了这么一个 API Page.addScriptToEvaluateOnNewDocument 能够让我们在页面加载前注入 JavaScript 代码。

接下来考虑一下该如何定义页面加载后。最简单的方法就是不管三七二一,每个页面都加载 30s (即便是空白的页面),随后再注入我们的代码,但很明显这会浪费很多资源,我们需要根据每个页面的复杂度来控制加载时间。可能会有同学说我们可以监听 load 事件,等待页面加载结束之后再注入代码,那我们考虑一个比较常见的场景,在某个页面上刚好有那么一两个图片字体资源加载速度特别慢,导致 load 迟迟未被触发(甚至不触发),但这些资源其实我们并不在乎,完全可以直接注入我们代码,所以只等待 load 事件也并不是一个特别好的选择。

我们先看一下加载一个页面的过程,除了会触发 load 事件之外还会触发什么事件:

<html>
<!-- 在外部 script 之前的 css 会阻塞 DOM 的构建 -->
<link rel="stylesheet" href="http://httpbin.org/delay/3?id=1">
<script src="http://httpbin.org/delay/1?id=2"></script>
<!-- 后面两个 css 并不会阻塞 DOM 的构建 -->
<link rel="stylesheet" href="http://httpbin.org/delay/6?id=3">
<link rel="stylesheet" href="http://httpbin.org/delay/6?id=4">
</html>

import pychrome
import pychrome.exceptions

def lifecycleEvent(**kwargs):
    print("{}: {}".format(kwargs['timestamp'], kwargs['name']))

browser = pychrome.Browser()
tab = browser.new_tab()

tab.Page.lifecycleEvent = lifecycleEvent

tab.start()
tab.Page.enable()

try:
    tab.Page.setLifecycleEventsEnabled(enabled=True)
except pychrome.exceptions.CallMethodException:
    pass

tab.Page.navigate(url="http://localhost/load_event.html")
tab.wait(60)

在这里插入图片描述
下面我们简单地介绍一下上面几个我们会用到的事件
| 事件| 解释 |
|DOMContentLoaded|一般表示 DOM 和 CSSOM 均准备就绪的时间点|
| networkAlmostIdle| 当前网络连接数少于 2 后触发|
|networkIdle| 当前没有网络连接后触发|
|load |网页所有资源载入后触发,浏览器上加载转环停止旋转 | |


之前解释过 load 事件可能对我们来说太晚了,但是现在 DOMContentLoaded 事件对我们来说又太早了,因为用户代码也可能会绑定这个事件然后操作 DOM,我们肯定是希望能够在页面稳定之后再注入我们的代码,所以在 load 和 DOMContentLoaded 之间某个时间点对我们来说比较合适,可惜并没有这样一个特别的事件存在,所以我个人觉得比较好的方案是将上面各个事件结合一起使用。

我们先说一下这几个事件的触发顺序,首先这几个事件触发顺序不一定,例如触发时间 load 事件不一定比 DOMContentLoaded 晚,load 也不一定比 networkAlmostIdle 晚。唯一能确定的就是 networkAlmostIdle 一定比 networkIdle 晚。在一般的情况下时间顺序是 DOMContentLoaded -> networkAlmostIdle -> networkIdle -> load。

所以一般的解决方案:

  • 等待 load,同时设定等待超时时间,load 超时直接注入代码,同时等待 DOMContentLoaded 事件
  • DOMContentLoaded 事件触发,接着等待 networkAlmostIdle,同时设定等待超时时间,超时直接注入代码
  • networkAlmostIdle 事件触发,接着等待 networkIdle 同时设定等待超时时间,超时直接注入代码
    如果 load 事件在其他事件前触发,那就直接注入代码。

0x02 DOM 构建前

解决了在什么时候注入 JavaScript 代码的问题,接下来我们该开始考虑第一阶段该注入什么代码了。

由于在第一阶段的时间点,DOM 树还未构建,所以我们所注入的代码均不能操作 DOM,能干的事情也就只有 Hook、备份 BOM 中的函数。

basic

我们先把一些会导致页面阻塞、关闭的函数给 Hook 了,例如:

window.alert = function () { return false; };
window.prompt = function (msg, input) { return input; };
window.confirm = function () { return true; };
window.close = function () { return false; };

同时也需要在 CDP 中处理 Page.javascriptDialogOpening 事件,因为还有类似 onbeforeunload 这样的弹窗。

location

还记得我们上一篇通过修改 Chromium 代码将 location 变成可伪造的事情了吗?就是为了能够在这里对 location 直接 Hook,直接看代码:

var oldLocation = window.location;
var fakeLocation = Object();
fakeLocation.replace = fakeLocation.assign = function (value) {
    console.log("new link: " + value);
};
fakeLocation.reload = function () {};
fakeLocation.toString = function () {
    return oldLocation.toString();
};
Object.defineProperties(fakeLocation, {
    'href': {
        'get': function () { return oldLocation.href; },
        'set': function (value) { console.log("new link: " + value); }
    },
    // hash, host, hostname ...
});
var replaceLocation = function (obj) {
    Object.defineProp
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值