
JavaScript拦截器
更新: 2025/2/24 字数: 0 字 时长: 0 分钟
拦截器简介
JavaScript 拦截器是在 request 请求、response 响应或异步操作中插入自定义逻辑的一种模式。
定位流程
有的反爬网站在请求的时候,会返回加密的数据:
再看 Initiator 启动器,会发现是异步的加载,那么数据解密操作就很有可能是在拦截器里面进行的:
这里我们就可以全局搜索一下 interceptors
拦截器关键字,选择第一处进入,可以看到 a.interceptors.request.use
和 a.interceptors.response.use
就是请求和响应拦截器,它不会直接拦截异步加载的过程,但可以在发送请求之前或在接收到响应之后执行自定义逻辑。
网站实践
湘易办
难度:简单
地址:https://mobile.zwfw.hunan.gov.cn:8088/h5webs/#/tgm?cityCode=431322000000
“湘易办”是湖南全力打造的全省统一的企业、群众、公务人员“掌上办事”的总入口、优化营商环境的总平台、建设数字政府的总引擎,由中国建设银行、中国电子集团共同建设。首先我们打开开发者工具,点击左上角手机按钮,让网页以移动端的形式展示,然后乱填一些信息,登录进行抓包:
请求头中没有加密字段,查看表单是一串密文,查看相应内容发现也是一串密文:
面对这种情况,我们就可以全局搜索一下拦截器 interceptor
关键词,总共有两处文件中存在该关键词,我们点击第一个文件,里面搜索 interceptor
关键词,可以看到 u.interceptors.request.use
和 u.interceptors.response.use
关于请求和响应拦截器,我们在响应处打上断点:
我们再次点击登录,断点断在第 1209 行,打印 e
变量,里面就是响应的密文,再打印一下 s["a"](e)
输出的就是明文,这就说明解密的逻辑一定是在 s["a"]()
函数里面:
我们进入 s["a"]()
函数里面一步步运行调试,发现解密的部分是在第 1512 至 1515 行,最后输出了 a
赋值给了 e.data
属性,我们输出一下 a
确认就是我们要的明文信息:
现在我们来解读这几行代码,具体分析如下:
- 第一行
window.atob(e.data)
获取e.data
属性,即响应的密文进行 Base64 解码; - 第二行、第三行都使用到了
window
中的一个DCSAPPClientAPI
属性中的一些方法对变量值进行了一些处理,最后返回了变量a
; - 第四行使用
JSON.parse(a)
对变量a进行JSON序列化;
产业政策平台
和这个站有点像 http://49.77.204.6:17001/Website/#/compQuery/enterInfoQuery
难度:中等
产业政策平台是一个提供各行各业公共产业政策的大数据平台。首先要注意一点,不能先启动开发者工具再进入该网站,必须是先进入该网站再启动开发者工具,然后发现该网站禁用了按 F12
启动Chrome开发者工具,那我们就通过Chrome菜单中的选项来启动开发者:
开发者工具一启动就进到了 VM 虚拟环境的“无限debugger”当中,我们就用前面学习的无限 debugger 绕过方式来绕过,右键点 debugger
所在行的左侧,选择 Never pause here
从不在此停留选项:
断点打上以后,我们点击按钮继续运行:
本以为能顺利的过掉无限debugger,结果页面直接卡死,重试几次都是这样:原因就在于,前面的这种绕过方式只针对小部分网站,而绝大部分网站在 JS 代码中存留了一个写入的功能,它会有一个无限循环的变量来写入到我们的内存当中,从而导致整个页面处于一个卡死状态,如果运行时间过长,甚至会导致我们的电脑处于一个卡死状态,这就是”内存爆破“。
其实绕过的方式也非常简单,通过注入Hook代码,修改debugger条件,就能绕过去了。我们在 Sources
选项卡中选择 Snippet
片段,选择 New Snippet
新建片段,命名为 hook_debugger
,写入下面Hook代码,写入后可以看到左侧的文件名中带有 *
号,这是没有保存修改的状态,我们 Ctrl+S
保存后,点击下面的运行来注入Hook代码:
AAA = Function.prototype.constructor;
Function.prototype.constructor = function (a) {
if(a === 'debugger') {
return function () {};
}
return AAA(a);
};
注入成功后,在控制台会有输出,然后我们点击按钮继续运行,浏览器就不会卡死了:
在网站的”政策类型“中选择”政策文件“一项,在右侧就出现了数据请求,里面的响应正是页面加载的数据:
观察请求的数据接口和请求头,都没有特殊的加密字段,然而POST发送的数据是一串加密数据:
现在我们拷贝请求的路径,打上 XHR 断点,再次点击网站中的”政策文件“选项,断点卡住:
接着我们点击按钮向上执行,会来到下图这里,可以看到一个完整的请求对象:
继续点击按钮向上执行,会来到下图这里,可以看到上面执行一个函数,我们将其中的关键字进行输出,会发现其中一个关键词 interceptors
就是拦截器的意思:
axiosInstance['interceptors']['response']['use']
现在我们只在控制台输出 axiosInstance['interceptors']
拦截器的这一部分,可以看到只有“request请求”和“response响应”两部分,展开“request请求”部分,点击进入函数地址:
跳转之后,我们来到了如下图这里,可以看到这也是一个函数:
来到函数末尾处打上断点,再次点击网站中的”政策文件“选项,函数末尾断点卡住,我们在右侧边栏观察断点处 o
变量中的 data
属性,展开后是一个数组,点击右侧的小方块,下方就出现了我们前面请求的密文参数,可以看到这种形式其实就是Octet-stream进制流,它是一种通用的二进制数据表示方法,可以表示任何类型的数据,包括图像、声音、视频、文本等等:
那接着我们就要去逆向 o['data']
中的函数逻辑,刚好在函数中就有其生成逻辑,我们在此处打上断点,再次点击网站中的”政策文件“选项,断点卡住,可以看到,在执行逻辑之前 o['data']
是明文的请求参数,在执行逻辑之后 o['data']
就变为了密文的请求参数::
我们将 o['data']
中的关键参数进行还原,整体如下:
data = {
"policyType": "4",
"province": "",
"city": "",
"downtown": "",
"garden": "",
"centralId": "",
"sort": 0,
"homePageFlag": 1,
"pageNum": 1,
"pageSize": 7
}
// o['data']变量
o_data = f['encode'](data)['finish']()['slice']()
我们选中 f['encode']
方法,进入到该函数的地址中,发现整个函数只有29行,我们将其中的函数拷贝到js文件当中去,注意将函数名中的 $
替换为 _
以避免歧义产生:
当我们运行代码后,会提示 Writer is not defined
未定义:
如果我们去浏览器中定位这行代码函数位置,就会发现跳转到了另一个虚拟机文件中,而且定位不了具体的函数位置,跟不了栈,因为这个是框架代码:
现在有一点可以确定的是,Writer
一定是在 VM5
这个文件当中的,我们可以在文件中搜索一下 Writer.create()
发现有一处,可以看到这段字符串其实就是上面的那段代码,观察函数还发现前面有一个 13
的编号,这个就有点像Webpack打包的形式:
我们沿着函数编号往上寻找,发现这就是一个将内部函数模块化的一个大函数,我们将其全部扣下来,拷贝到我们的文件当中,进行格式化,发现就是一个Webpack打包的形式:
由于该函数是一个自执行函数,运行一下,发现 commonjsGlobal
未定义,我们去到浏览器中进行断点,发现断不住,就直接在 o['data']
断点处进行控制台输出,发现是一个 Window
对象,由于平常我们补环境是 Window = global;
写法,那我们现在就可以直接在上面添加一个 commonjsGlobal = global;
即可:
现在我们还无法直接调用这个函数,因为这个函数是一个整体,在外部是是调用不了内部的模块的,参考Webpack的处理方式,在外部定义一个 cz
全局变量,来接收函数内部的加载器 i
对象,再通过 cz
加载器来调用 Writer.create()
方法,再一运行我们想要的二进制结果就出来了:
接下来,我们就定义一个函数将 o['data']
的字符形式设为的返回值,在Python进行调用:
最后,我们将其添加到爬虫当中测试,效果如下: