写在前面
在这次之前我是没有接触过前端JS调试,师父临时接了一个项目,说是几个人大家一起看一下,调一下前端JS。因为项目需求嘛,这就开始边学边做,然后还有个同事之前有做过这个,刚好也可以请教一下。
从这里开始基本上就可以踏上前端JS调试之路了,在我这没有一点JS代码基础的前提下开始,路远且长,脑壳大,不过也算是一个全新的内容了,之后的渗透测试应该也能够做的更加全面了。
发现加密
在测试过程中使用BurpSuite
进行抓包,发现抓到的包为如下类型:
网站POST地址都是为网站的一级路由,真实的路径都在某些字段中,加密内容为__xml
的参数值,在网站的开发者工具中也可以找到这个。
可以看到网站前端的JS文件进过了混淆,对于解密工作还是有一定的难度,就是看起来比较烦。
AES解密
那么下面就是解密工作,首先找找网站是怎么加密的。一般加密算法都是写在JS文件中,翻翻JS文件。
直接用开发者工具打开时没有格式化的JS
代码,这样的代码阅读起来十分困难,首先要格式化代码以便阅读,下面是几种格式化代码方式。
- 谷歌Chrome浏览器开发者工具自带格式化功能
- Sublime Text工具——html-css-js prettify,需要注意的是需要node.js的支持
- note pad++,安装JSTool插件进行支持
- 其他代码格式化工具,比如"鬼鬼JS"等等
格式完代码之后,全局搜索"encrypt"找寻加密方式,这个搜索内容不一定就是这个字段,要根据网站内容去设置,碰巧我这边就是"encrypt"这个字段。这里发现网站是使用的AES.CBC
的加密方式,因为前端JS进行了函数名的一定的代码混淆,分析后确定AESKeyStr
是变量t
,偏移量iv
是变量r
。
我这次调的网站相对来说会比较简单一些,AESKeyStr
和iv
是通过标准的JS库生成的,我这里理解的有点类似于SSH登录中通过一段字符串生成AES密钥这个过程,因为生成的字符串是固定的,所以这里就是固定的密钥和偏移量。因此这个网站的key
和iv
在后面的调试过程中就使用crypto-js
自动生成即可。
找完密钥和偏移量之后就是对接口进行调试,调试哪个接口这个就随意了。我这里选择了登录的接口进行调试。
通过接口地址在JS文件中找到相应的内容。
这里就可以看出this.$refs[e].validate(function(e)
控制这个接口的数据包,而在这上面一段就是一些变量的加密处理方式。
打断点进行登录操作也可以看到,上图的这部分就是负责变量内容的加密。
这里就可以放弃这个断点重新执行,将断点重新设置在3510
行,然后在重新执行,其实不看执行也可以,这一块的代码还比较好理解。
handleSubmit: function(e) {
this.loading = !0;
var t = this
, r = this.formInline.user
, n = CryptoJS.MD5(this.formInline.password).toString()
, i = this.formInline.vaildcode
, a = new this.HashMap;
a.put("loginName", r),
a.put("password", n),
a.put("vaildcode", i);
var o = JSON.stringify(a)
, s = this.encrypt(o)
, c = {
__xml: s,
__type: "extTrans"
};
代码 | 对应内容 | 含义 |
---|---|---|
r = this.formInline.user |
username | 格式化username并赋值给变量r |
n = CryptoJS.MD5(this.formInline.password).toString() |
password | 格式化password并进行MD5加密,转换成字符串类型后赋值给变量n |
i = this.formInline.vaildcode |
vaildcode | 验证码 |
a = new this.HashMap |
list | 等同于其他代码中的字典 |
然后通过put()
函数将变量r
、n
、i
以键值对的形式传入a
中,然后通过JSON.stringify()
将a
按照JSON
格式进行格式化,最后对变量o
进行加密处理,这就是整体的逻辑。知道这个逻辑就可以将这部分以自己理解的代码重新复写出来。
//脚本编写自Muyu
function test(){
var CryptoJS = require("crypto-js");
var username = "188888888888@163.com";
var password = CryptoJS.MD5("123456").toString();
var vaildcode = "zzzz";
var a = {"loginName":username,"password":password,"vaildcode":vaildcode};
var o = JSON.stringify(a);
// Encrypt
var key = CryptoJS.enc.Utf8.parse("[keystring]");
//console.log(key);
var iv = CryptoJS.enc.Utf8.parse("[keystring]");
var n = CryptoJS.enc.Utf8.parse(o);
var ciphertext = CryptoJS.AES.encrypt(n, key, { iv: iv, mode: CryptoJS.mode.CBC}).toString();
console.log(ciphertext);
// Decrypt
var bytes = CryptoJS.AES.decrypt(ciphertext, key, { iv: iv, mode: CryptoJS.mode.CBC});
var originalText = bytes.toString(CryptoJS.enc.Utf8);
//console.log(originalText); // 'my message'
}
test();
这个脚本仅仅只是针对于登录的这个接口,后面没有验证适用性,不过一般来说一个网站中的接口应该都是同一套加密算法,然后再根据不同的需求更改参数和参数值应该就OK了。
其他内容
有一点要明确一下,这次调试的算是一个静态加密的网站,难度相对来说比较低,部分网站会使用动态密钥来进行加密,所以这部分网站需要单独去再进行研究,本文中的方法并不能涵盖到所有的站点。
除了动态密钥加密的网站,还有部分网站会将前端JS进行混淆,不单单是上文中的网站仅对部分函数、变量名进行混淆,在遇到这种网站时,我是不确定还能不能继续做下去,有待研究。
关于是否要进行动态调试的问题,因为我做的这个网站最后确定是静态加密,并且文章中并没有过多的说动态调试的过程,但是在询问了师父之后,他给出的意见是——不论对方是否是动态加密,都应该进行动调,这样能够保证你自己写的解密加密的脚本的正确性,以及网站加密算法理解的准确性。
改包
改包这个部分就直接使用上面复写的代码进行针对性更改,然后运行就可以了,这里就不再进行记录了。
写在最后
之前其实有遇到过前端、数据包内容加密的渗透测试场景,并没有学习前端调试的我就没有办法进行这样的测试,也没能通过这样的项目学习到这部分内容,也主要是之前并没有想到可以这样做。这次也算是把这部分内容学习到了,后面测试内容也可以更全面了。
Comments | NOTHING