随着等保2.0的实施,传输过程中加密变的必要了,很多APP或者手机浏览器端逐步加密了一些加密的措施来解决这个问题,比如以下这样的数据包。
一串乱码,什么是什么都看不懂别说修改数据了。。那咋办呢? 我们可以使用firefox,chrome之类的,我个人比较喜欢firefox,以下的都是基于firefox来讲解。
打开调试器ctrl+F12然后进入 调试器,刷新页面后会加载全部的js,根据习惯,一般login.js就看起来像是主要登录模块触发的函数的页面。
我们回到页面来触发这个button。前端很都操作都是基于事件绑定的。js就是事件驱动的语言,会有大量的闭包,一旦写不好就浏览器内存++,当然这个是题外话。既然是事件驱动就会有很多function.on(‘click’,callback(){})之类的操作来定义一些检测的逻辑。
我们先对需要出发的那个button,比如“登录”,“发送”之类的button,点击他的右键,然后选择“查看元素”。 Firefox会快速定位到这个元素附近的HTML结构,当然也会因为CSS层叠的问题导致定位不准,不过这个不重要,多用几次就知道如何快速定位到关键元素,我们目前还是在讲解HTML,还没开始讲解JS部分。
然后点击event,我们快速定位到这个button点击后的事件逻辑块代码,比如以下的图。
可以看到这个button绑定了2个click事件,下面那个事件为冒泡事件,即在上面的那个click的同时,下面那个click也会被触发。这个大概的触发代码应该是$(‘#buttion_id’).click(function(){…}) ,其中,红色部分内容应该就是我们打开的这个click事件框框中的代码,我把代码全部贴出来。
以下代码是基于jquery的,要看懂的话需要一些基础,我讲的尽可能简单,让大家都可以快速明白。
1 function() {
2 if (!$(this).prop('disabled')) {
3 var mobilenum = /^(13[0-9]|15[0-9]|16[0-9]|17[013678]|18[0-9]|19[0-9]|14[57])[0-9]{8}$/;
4 var mob = $('.row input').eq(3).val().replace(/\s/g, '')
5 if (!mobilenum.test(mob)) {
6 $('#modal-error').modal('show').children('.error-content').children('.modal-body').html('请输入有效的手机号');
7 return;
8 }
9 if (needImg) {
10 $('#modal-input').modal('show');
11 $('#modal-input .msg-code-img').eq(0).click();
12 var $_input = $('#modal-input input')[0];
13 setTimeout(function() {
14 $_input.focus();
15 }, 500);
16 } else {
17 var params = {
18 'MOBILE': $('.row input').eq(3).val().replace(/\s/g, '')
19 };
20 getPhoneCode(params);
21 }
22 }
23 }
最主要我们看17-20行的代码,构造了一个对象传入到getPhoneCode函数中,params的参数就是手机的参数 并且处理一定逻辑。
接来下我们去搜索getPhoneCode(params)函数。
我们通过全局查找快速定位到这个函数的位置。
1 function getPhoneCode(params) {
2 if (!$('#msg-btn').prop("disabled")) {
3 $('#msg-btn').prop("disabled", true);
4 params['ajaxUrl'] = "/user2/sendSmsCode";
5 params['ajaxCallBack'] = function (data) {
6 if (data.MSG_CODE == 0) {
7 if (data.MESSAGE_CODE) {
8 $('.msg-code-input').val(data.MESSAGE_CODE);
9 checkNull();
10 }
11 /*if (show_input) {
12 $('.tel-code-row').show();
13 }*/
14 $(".msg-code-img:not('#modal-input .msg-code-img'):last").click();
15 time($('#msg-btn'));
16 return;
17 } else if (data.MSG_CODE == '102') {
18 if ($('.tel-code-row').css('display') == 'none') {
19 alerterror('您的失败次数过多<br/>请输入图片验证码');
20 $('.tel-code-row').show();
21 } else {
22 alerterror('图片验证码输入错误');
23 }
24 if (typeof show_input != 'undefined' && !show_input) {
25 show_input = true;
26 }
27 } else {
28 alertfail();
29 }
30 $('#msg-btn').prop("disabled", false);
31 }
32 submitAjax(params);
33 }
34 }
32行之前都是一些逻辑和判断的代码 和我们想要调试的没关系,4-5行是把一些信息包装进了params参数中,最终通过32行的submitAjax函数来执行,我们继续跟踪这个函数 ,方法和上面一样,全局搜索这个方法。
submitAjax函数的定义如下:
1 //提交数据
2 var submitAjax = function (dataObject) {
3 var ajaxUrl = UC_URL + dataObject.ajaxUrl
4 delete dataObject.ajaxUrl
5 var ajaxCallBack = dataObject.ajaxCallBack;
6 delete dataObject.ajaxCallBack;
7 dataObject.CHNLID = dataStore.getItem("CHNLID");
8 dataObject.BACKURL = unescape(Base64.decode(dataStore.getItem("backUrl")));
9 dataObject.VERSION = "1.32";
10 if (null != dataObject.BACKURL) {
11 dataObject.BACKURL = getDomain(dataObject.BACKURL);
12 }
13 if (null == dataObject.CHNLID && null == dataObject.BACKURL) {
14 dataObject.CHNLID = 'SF';
15 dataObject.BACKURL = location.host;
16 }
17 var JsonParams = JSON.stringify(dataObject);
18 var rsaCommit = function (JsonParams) {
19 var RSAParams = RSAUtils.encryptedString(rsaKey, JsonParams);
20 $.ajax({
21 type: "POST",
22 cache: false,
23 dataType: "json",
24 url: ajaxUrl,
25 contentType: 'text/plain',
26 data: RSAParams,
27 ...82 }
去掉了一些不重要的代码,我们主要来看dataObject参数。我们先对这个地方下个断点。
然后我们回到页面,填写好手机号,点击“获取验证码”,让代码跑起来。
我们可以看到dataObject参数就2个属性,然后继续往下跟,在Json.stringfy之前下断点,因为最后加密的函数是第19行,RSAUtils.encryptedString(rsaKey, JsonParams);其实有经验的同学可以直接在这里下断点查看,这里的rasKey是没有定义的,只有这个JsonParams,而 JsonParams就是刚才的dataObject对象的json序列化。
我们可以看到,在变成json格式之前,程序加入了一些其他的参数,这个不重要。接下来我们下断点到 RSAUtils.encryptedString函数。然后来看rsaKey参数是什么,鼠标移上去显示是undefined..
因为代码只var rsaKey,并没赋值任何。。。不知道程序员在想什么,这个类的加密string方法显然是需要一个加密的key的,也就是私钥,其实这个程序有一个密钥,不过不是这个函数里的。
所以其实前端加密来阻止参数修改没意义的。。
最后我们需要理解上面的流程和逻辑
获取需要的参数比如mobile,版本等信息
json序列化
加密
发送至服务端
最后的代码是:
1 var my = {MOBILE:"15*******",CHNLID:"SF",BACKURL:"*****.com.cn",VERSION:"1.32"};
2 var data = JSON.stringify(my);
3 var rsaKey;
4 RSAUtils.encryptedString(rsaKey, data);
最后得出的值是:
我们再和直接用burpsuite抓到的值对比下:
基本上就结束了,接来下我们可以构造任何我们想要的值来替换掉传输,因为至此我们可以构造任何想要的参数来篡改。
其实换个思路,我们可以编写一些插件(其实已经有类似的插件) ,例如早起的Tamper Data 插件之类的,或者自己写脚本引入,在一些关键代码之前反射出对象的全部属性.
从程序员角度来看,我们可以看到他有好几个加密方法,有的传了密钥,有的没有。而且代码都是部分混淆,部分没有,其实提高一下门槛的话,应该把login.js之类的 也混淆了。虽然这种混淆对我这样的安全工程师没什么用,但是waf不也是这样的思路吗?
把全部的js混淆了不仅可以压缩代码的字符数,减少服务器压力和带宽,还可以提高安全的门槛,增加破解难度,在交互中多次传输一些长度很高的token,迷惑安全人员,再使用一些js和浏览器的hack技术,使得门槛可以非常高。 比如这些js的奇怪特性。
*本文作者:NotFound,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。