前端无秘密:看我如何策反JS为我所用(上)

  • A+
所属分类:安全文章

前端无秘密:看我如何策反JS为我所用(上)

近日,参加金融行业某私测项目,随意选择某个业务办理,需要向客户发送短信验证码:

 

前端无秘密:看我如何策反JS为我所用(上)

 

响应报文中包含大段加密数据:

 

前端无秘密:看我如何策反JS为我所用(上)

 

全站并非全参数加密,加密必可疑!尝试篡改密文页面提示“实名认证异常”:

 

前端无秘密:看我如何策反JS为我所用(上)

 

猜测该密文涉及用户信息,且通过前端 JS 解密,验证之。

 

手工分析

 

要分析前端,自然得打开浏览器的开发者工具。习惯性按下 F12 键,无反应,显然页面禁用 F12,没有调试环境,所有后续工作均无法开展,我得创造环境。

1.1 创造前端调试环境

虽然禁用 F12,但未禁用 shift-F5,它也能启用开发者工具,或者,页面也未禁用右键,我从元素审查菜单项同样进入开发者工具。前端采用 webpack 打包 通过开发者工具的 {} 简单美化:

 

前端无秘密:看我如何策反JS为我所用(上)

 

 

刚执行前端代码,页面卡死,应该存在反调试逻辑:

 

前端无秘密:看我如何策反JS为我所用(上)

 

多次单步执行,发现 setTimeout(e, 100) 语句:

 

前端无秘密:看我如何策反JS为我所用(上)

业务上几乎不可能用到 100 毫秒的定时器,猜测用于反调试。全局搜索(ctrl-shift-f)该语句,发现只存在于 businessReservation.js 中:

 

前端无秘密:看我如何策反JS为我所用(上)

 

尝试删除该语句,看能否禁掉反调试。由于 JS 已被浏览器加载至内存,即便通过开发者工具删掉该语句,浏览器照样会执行它,所以,必须得在浏览器拿到 JS 前就将其删除,也就是说,得让服务端重新下发该 JS,拦截响应包,删除该语句后放行至浏览器。具体而言,JS 作为静态资源,优先从本地缓存中获取而非服务端,必须强制刷新页面(ctrl-shift-r),拦截返回 businessReservation.js 的响应包搜索关键字 setTimeout(e, 100),无果:

 

前端无秘密:看我如何策反JS为我所用(上)

 

考虑到关键字 setTimeout(e, 100) 是经浏览器美化(代码格式化)之后的,尝试去掉逗号后面的空格,用关键字 setTimeout(e,100) 再次搜索,命中:

 

前端无秘密:看我如何策反JS为我所用(上)

 

将 setTimeout(e, 100) 改为空语句 ;:

 

前端无秘密:看我如何策反JS为我所用(上)

 

放行修改后的 JS 至客户端,之后,前端代码可正常执行,也可随意下断点:

 

前端无秘密:看我如何策反JS为我所用(上)

 

这下算是创建出了前端调试的环境。

 

1.2 解密 Data

 

现在我得想法找出解密函数。与 Data 同时返回至客户端的字段还有 ResultCode:

 

前端无秘密:看我如何策反JS为我所用(上)

 

全局搜索 ResultCode,发现只出现在 businessReservation.js 中:

 

前端无秘密:看我如何策反JS为我所用(上)

 

但有二十来处,得排除无关项。我猜测前端大致逻辑,先检查 ResultCode 是否为表示查询成功的状态码 1000,若是则解密 Data,若不是则报错,伪码类似:

 

前端无秘密:看我如何策反JS为我所用(上)

 

那么,代码中一定是先出现 ResultCode,与之同行或后续行(如,四行,-a4)出现 Data,按此思路验证。

首先,我将该 JS 另存至本地,大小写敏感查找关键字 .ResultCode,将其所在行及其后续四行作为输入,再次查找关键字 .Data,出现了九处:

 

前端无秘密:看我如何策反JS为我所用(上)

 

然后,考虑到要对 Data 解密,一定是作为实参传给某个函数,上面候选的九行中,只有 1414 和 1767 两行在传递实参调用函数:

te.CBC(i.Data, 'Dxxxxxxx1')

其中,第一个参数为待解密的密文,第二个参数像是密钥,另外,联想到 DES 加密算法的分组模式之一就有 CBC,大概率是它。

接着,我全局搜索关键字 CBC,多个文件存在匹配项,大部分为函数调用唯有 businessReservation.js 中的 1477 和 1498 行是函数定义:

 

前端无秘密:看我如何策反JS为我所用(上)

其中,1477 行的函数定义如下:

前端无秘密:看我如何策反JS为我所用(上)

 

1498 行的函数定义如下:

 

前端无秘密:看我如何策反JS为我所用(上)

 

回想前面看到的是对象 te 在调用成员函数 CBC(),毋庸置疑,1498 行的 CBC() 就是我要找的解密函数。

最后,1499 行下断点方便晓得是否进入解密函数,1507 行下断点可从栈中查看解密后的明文,多步执行后,在该函数返回值中看到解密结果:

 

前端无秘密:看我如何策反JS为我所用(上)

 

为更清晰查看明文内容,我在 console 中直接调用 CBC() 解密并对结果 JSON 格式化:

var ciphertext = 'a91cbf8fe19a8238……………………..fde7523032d166ba710c2a6aa501';
JSON.parse(te.CBC(ciphertext, 'Dxxxxxxx1'));

发现单位和家庭地址、姓名等敏感信息,另外,ID_ICCID 字段疑似某种编码后的身份证号码:

 

前端无秘密:看我如何策反JS为我所用(上)

 

1.3 Data 解密脚本化

为便于后续自动化,我尝试将 CBC() 转为 python 脚本。从源码可知,采用 DES 解密算法、分组模式为 CBC、填充方式 pkcs7,单步调试可知,密钥为 Dxxxxxxx1,剩下偏移量暂不清晰,只是知道基于密钥加工而来,python 无法成功解密:

 

前端无秘密:看我如何策反JS为我所用(上)

 

怀疑是偏移量没生成对。

换思路,考虑直接运行 JS 脚本。源码可知,CBC() 调用 crypto-js 库中 DES 实现,在获取解密关键要素后,我参照写了个可读性更强的版本 decrypt_data.js:

 

前端无秘密:看我如何策反JS为我所用(上)

 

通过 nodejs 命令行运行 decrypt_data.js,成功解密,获取用户信息:

 

前端无秘密:看我如何策反JS为我所用(上)

 

为便于其他 python 脚本调用我打算对 decrypt_data.js 简单 python 封装。

个人偏好用 js2py 库实现 python 中执行 JS,于是我迅速地将 decrypt_data.js 进行 python 化,编写脚本 decrypt_data_by_js2py.py:

 

前端无秘密:看我如何策反JS为我所用(上)

 

由于 js2py 需在执行期进行代码翻译,导致运行效率不理想:

 

前端无秘密:看我如何策反JS为我所用(上)

 

回想先前 nodejs 版的 decrypt_data.js 运行效率不错,考虑用 python 调用 nodejs 命令行。先得优化 decrypt_data.js,让其支持命令行选项:

 

前端无秘密:看我如何策反JS为我所用(上)

 

借助 subprocess 库调用命令行 nodejs decrypt_data.js {cipher},编写 decrypt_data_by_nodejs.py 脚本:

 

前端无秘密:看我如何策反JS为我所用(上)

 

效率显著提升:

 

前端无秘密:看我如何策反JS为我所用(上)

 

(题外话,某些站点的登录密码通过前端加密后在提交至服务端,很多小朋友觉得无法跑字典暴破,你看这儿是不是就给你了一个样例:找出对密码加密的 JS 函数,将其提取另存为 JS 文件,加工 JS 文件使其支持命令行参数,用 nodejs 带明文密码参数运行 JS 文件,用 python 读取明文密码字典作为命令行参数调用 nodejs 命令生成密文密码文件,把密文密码文件喂给 burp 的 intruder 暴破即可。)

 

现在,还剩 ID_ICCID 暂未搞定。它看起来神似 base64 编码,就是最开头多了两个 ++,像是某种标志,尝试去掉后成功解码:

 

前端无秘密:看我如何策反JS为我所用(上)

 

验证的确为身份证号码:

 

前端无秘密:看我如何策反JS为我所用(上)

 

此,确认 /xx/api/xxxx/h5/xx/sChkBlPhone 接口泄漏了用户信息三元素

 

前端无秘密:看我如何策反JS为我所用(上)

本文始发于微信公众号(疯猫网络):前端无秘密:看我如何策反JS为我所用(上)

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: