智能摘要 AI
为了帮助朋友编写一个监测OA办公系统登录功能的小软件,我分析了系统的登录请求,并使用Python实现了一个加密类`SM3Encryptor`,支持自定义初始向量。该类结合MD5和SM3加密算法,模拟登录过程并生成加密后的密码。通过抓取页面中的验证码和密码加密逻辑,成功实现了登录验证。最终,添加了一个简单的UI界面和邮件通知功能,确保在登录出现问题时能及时通知维护人员。测试结果显示,程序可以正常识别验证码、加密密码并成功登录。项目顺利完成。 总结:
1. **需求**:监测OA系统登录功能,出现问题时发送邮件通知。
2. **方法**:分析登录请求,使用Python
0x00 事件起因
一个在“驾校”网络中心的朋友告诉我,OA办工系统的登录功能经常挂掉,上面的负责人让他编写一个监测的小软件,方便在出问题的时候发一封邮件通知站点维护人员让他们及时修复。摸鱼是一定要摸的,最后他让我帮帮他,刚好没啥事情,就接下了这个任务。
0x01 请求分析
这里的要求是监测系统的「登录功能」,由于这个系统使用的JSP,并且没有做前后端分离,我也拿不到他们内部无需登录验证的接口,所以就只能自行分析,并且模拟登录请求。
我最开始的想法是打算使用Python Selenium,也是最简单的一种,但是打包出来的文件可能比较大,所以我这边打算手撕登录验证。
我们看看登录请求包含哪些参数:
我们先交给AI分析一下:
action:明确表示这是一次登录操作,值为login。
password:
该字段是经过加密的密码。
密码看起来可能是由明文密码进行哈希(例如 MD5 或 SHA256)生成的,并可能使用了其他附加数据(如盐值)进行进一步加密。
后半部分 (_ 后面的部分) 可能是动态生成的加密值,与时间戳、会话信息或其他用户相关数据相关。
pwd_state:猜测为密码状态字段,0 通常表示未启用二次验证或其他附加机制。
vsblogintype:登录类型字段,0 可能表示普通用户登录。
user:明确表明用户名字段,值是明文的。
logincode:
可能是验证码字段,也可能是动态令牌或者固定的某种登录标识。
x 和 y:
可能是鼠标点击的坐标(或者用于伪装为用户行为的参数),在某些情况下,前端会捕获登录按钮点击时的坐标,作为登录验证的一部分。很好,那么接下来我们来断点一下登录,看看他的密码参数的加密流程是怎么样的。
相关的JS代码并没有加密混淆,我们很快拿到了相关的参数和代码。
_前面部分是用户名的大写MD5,_后半部分使用了gmssl的国密加密SM3的加密方法,并且自定义的加密的KEY,现在我们只需要拿到这个关键的KEY就行
0x02 代码分析
由于这个系统做的比较抽象,在页面中为了初始化KEY竟然直接使用eval函数,只能说很强。并且KEY是写死的,可以直接拿来用,在控制台直接输出KEY:
[
193*****4191,
*****3241,
*****375,
*****704,
-1452330820,
372324522,
*****237683,
*****724082
]但是Python库的gmssl的SM3算法并不支持自定义KEY,所以我们这里直接交给AI重构代码,得到我们的自定义方法类:
import hashlib
import struct
class SM3Encryptor:
"""
封装 MD5 和 SM3 加密逻辑的类,支持自定义初始向量。
"""
# SM3 默认初始向量 (IV)
DEFAULT_IV = [
0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600,
0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E
]
def __init__(self, key=None):
"""
初始化加密器,如果提供 key,则用于替换默认的初始向量。
:param key: 自定义密钥数组 (8 个整数)
"""
if key:
self.iv = [(x + 2 ** 32) % 2 ** 32 for x in key] # 确保无符号整数
else:
self.iv = self.DEFAULT_IV
@staticmethod
def rol(value, bits):
"""循环左移"""
return ((value << bits) | (value >> (32 - bits))) & 0xFFFFFFFF
@staticmethod
def ffj(x, y, z, j):
return x ^ y ^ z if j < 16 else (x & y) | (x & z) | (y & z)
@staticmethod
def gj(x, y, z, j):
return x ^ y ^ z if j < 16 else (x & y) | (~x & z)
@staticmethod
def p0(x):
return x ^ SM3Encryptor.rol(x, 9) ^ SM3Encryptor.rol(x, 17)
@staticmethod
def p1(x):
return x ^ SM3Encryptor.rol(x, 15) ^ SM3Encryptor.rol(x, 23)
@staticmethod
def sm3_expand(message):
"""消息扩展"""
w = [struct.unpack(">I", message[i:i + 4])[0] for i in range(0, 64, 4)]
for i in range(16, 68):
w.append(
SM3Encryptor.p1(w[i - 16] ^ w[i - 9] ^ SM3Encryptor.rol(w[i - 3], 15)) ^ SM3Encryptor.rol(w[i - 13],
7) ^ w[i - 6])
w_ = [w[j] ^ w[j + 4] for j in range(64)]
return w, w_
def sm3_compress(self, v, message):
"""压缩函数"""
w, w_ = self.sm3_expand(message)
a, b, c, d, e, f, g, h = v
for j in range(64):
ss1 = self.rol((self.rol(a, 12) + e + self.rol(0x79CC4519 if j < 16 else 0x7A879D8A, j % 32)) & 0xFFFFFFFF,
7)
ss2 = ss1 ^ self.rol(a, 12)
tt1 = (self.ffj(a, b, c, j) + d + ss2 + w_[j]) & 0xFFFFFFFF
tt2 = (self.gj(e, f, g, j) + h + ss1 + w[j]) & 0xFFFFFFFF
d, c, b, a = c, self.rol(b, 9), a, tt1
h, g, f, e = g, self.rol(f, 19), e, self.p0(tt2)
return [a ^ v[0], b ^ v[1], c ^ v[2], d ^ v[3], e ^ v[4], f ^ v[5], g ^ v[6], h ^ v[7]]
@staticmethod
def sm3_padding(message):
"""填充消息"""
ml = len(message) * 8
message += b'\x80'
message += b'\x00' * ((56 - len(message) % 64) % 64)
message += struct.pack(">Q", ml)
return message
def sm3_hash(self, message):
"""
SM3 哈希函数,使用初始化时的自定义初始向量。
:param message: 输入消息
:return: 哈希结果(大写的 16 进制字符串)
"""
message = self.sm3_padding(message)
blocks = [message[i:i + 64] for i in range(0, len(message), 64)]
v = self.iv
for block in blocks:
v = self.sm3_compress(v, block)
return ''.join(f'{x:08x}' for x in v).upper()
def encoodesm3passowrd(self, password):
"""
对密码进行 SM3 加密。
:param password: 输入密码明文
:return: SM3 加密后的 16 进制字符串
"""
return self.sm3_hash(password.encode('utf-8'))
@staticmethod
def md5_encrypt(data):
"""
使用 MD5 对输入数据加密,并返回大写的 16 进制字符串。
:param data: 输入字符串
:return: MD5 加密结果
"""
return hashlib.md5(data.encode('utf-8')).hexdigest().upper()
def encrypt_password(self, account, password):
"""
根据账户、密码和密钥生成最终加密结果。
:param account: 用户名
:param password: 密码明文
:return: 加密后的密码字符串
"""
# 使用 MD5 加密密码
md5_result = self.md5_encrypt(password)
# 使用 SM3 加密密码
sm3_result = self.encoodesm3passowrd(password)
# 拼接 MD5 和 SM3 的结果
return f"{md5_result}_{sm3_result}"
其实还有更简单的方法,直接把网站的JS代码引入环境就行,但是原生的用起来效果很差,Node可行,但是不能保证朋友电脑允许安装,所以就直接用的python。
0x03 登录验证
我们再将相关的登录代码补充完整,接着再画一个UI界面,添加一下报警的邮箱通知,运行试试看。
识别的验证码: JTw3
加密后的密码: 4226382B9F79A051869F20C0F15B1C63_25A7568A72270F3EFD5B795E33D4FEEC6B3812FD888EFBC6DC908AE7642381F6
登录成功!很好,完美运行,补一个小软件的界面:
收工!





评论 (0)