Fiddler拦截
开启Fiddler拦截https(设置拦截https就不详细介绍了)
点击登录
发现一个post参数是username和password,密码部分被加密了。
放行后返回cookies
代码解析
这里使用火狐浏览器查看按钮事件的js代码
点击查看代码
if(!isSliderCaptcha);{
// 帐号登陆提交banding事件
$("#casLoginForm");
casLoginForm.submit(doLogin);
doLogin() {
var username = casLoginForm.find("#username");
var password = casLoginForm.find("#password");
if (!checkRequired(username, "usernameError")) {
username.focus();
return false;
}
if (!checkRequired(password, "passwordError")) {
password.focus();
return false;
}
var captchaResponse = casLoginForm.find("#captchaResponse");
if (!checkRequired(captchaResponse, "cpatchaError")) {
captchaResponse.focus();
return false;
}
_etd2(password.val(), casLoginForm.find("#pwdDefaultEncryptSalt").val());
}
}
加密部分在_etd2(),参数分别为密码明文和密钥,继续看
function _etd2(_p0, _p1) {
try {
var _p2 = encryptAES(_p0, _p1);
("#casLoginForm").find("#passwordEncrypt").val(_p2);
} catch(e) {("#casLoginForm").find("#passwordEncrypt").val(_p0);
}
}
似乎明文传输也能被服务器接受,
加密算法就在_gas函数里,_rds(64)为取长度为64的随机字符串,与密码明文拼接,16位的随机字符串作为初始向量,AES加密算法,CBC模式,pk#7填充。
function encryptAES(data, _p1) {
if (!_p1) {
return data;
}
var encrypted = _gas(_rds(64) + data, _p1, _rds(16));// 64位随机数 + 密码 , 密钥, 16位随机数
return encrypted;
}
function _gas(data, key0, iv0) {// 64位随机数 + 密码 , 密钥, 16位随机数
key0 = key0.replace(/(^\s+)|(\s+$)/g, "");
var key = CryptoJS.enc.Utf8.parse(key0); // 加密密钥
var iv = CryptoJS.enc.Utf8.parse(iv0); // 初始向量
var encrypted = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
附上python加密解密代码(部分代码来自网络)
import urllib
from Crypto.Cipher import AES
import base64
import random
class AesCrypt():
def __init__(self, key, model, iv):
BS = 16
self.pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
self.model = {'ECB': AES.MODE_ECB, 'CBC': AES.MODE_CBC}[model]
#self.key = self.add_16 (key)
self.key = bytes(key,encoding="utf-8")
self.iv = iv.encode ()
if model == 'ECB':
self.aes = AES.new (self.key, self.model) # 创建aes对象
elif model == 'CBC':
self.aes = AES.new (self.key, self.model, self.iv) # 创建aes对象
def aesdecrypt(self, text):
# CBC解密需要重新创建一个aes对象
if self.model == AES.MODE_CBC:
self.aes = AES.new(self.key, self.model, self.iv)
text = base64.decodebytes(text.encode('utf-8'))
self.decrypt_text = self.aes.decrypt(text)
return self.decrypt_text.decode('utf-8').strip('')
def aesencrypt(self, word):
chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
retStr = ''
for i in range(64):
retStr += chars[random.randint(0,len(chars)-1)]
word = retStr + word
word = self.pad(word)
t = self.aes.encrypt(word.encode('utf-8'))
b = base64.encodebytes(t)
#b = t
return str(b,encoding='utf-8')
现在使用的加密算法已经明了,还剩下两个问题:
- 加密的密钥从哪里来?
- 偏移使用随机数服务器怎么解密?
先解决第一个问题,密钥哪里来的?以上代码在_etd2()中传入的第二个参数就是密钥。
_etd2(password.val(), casLoginForm.find("#pwdDefaultEncryptSalt").val());
密钥取自pwdDefaultEncryptSalt,继续抓包,发现密钥直接保存在页面里。
第二个问题,偏移使用随机数那么服务器要怎么解密?
这里先简单了解一下AES CBC模式的加密过程:
CBC模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密从而得到密文片段,最后在将密文片段拼接起来得到完整的密文。
解密过程就与加密流程相反,可以看到在解密时初始向量只用在了第一个密文段中,因此即使使用了错误的初始向量,影响的也只是第一个密文段,这将导致初始向量在某些长字符串AES加密场景下形同虚设。
回到正题,这里的AES加密是64个随机字符加上密码明文,那么在解密的时候即使使用错误的初始向量,也仅仅是前16个字符错误,将整个字符串前64个截取掉,剩下的就是密码明文。
function encryptAES(data, _p1) {
if (!_p1) {
return data;
}
var encrypted = _gas(_rds(64) + data, _p1, _rds(16));// 64位随机数 + 密码 , 密钥, 16位随机数
return encrypted;
}
至此加密解密流程分析完成。
写一下登录函数,简单的通过GET index.htm页面判断是否登录成功
# 判断是否登录成功
def is_login(self):
self.url_index = 'http://x.xxxxxx.edu.cn/EIP/user/index.htm'
r = self.connect.get(self.url_index, headers=self.headers).content.decode()
#print("is_login:\n",r)
r = r.find('请输入用户名')
if(r == -1):
return True
else:
return False
# 登录
def login(self):
if(self.is_login() == False):
print('-----------------登录中...----------------')
headers_1 = self.headers.copy()
headers_1['Sec-Fetch-Site'] = 'same-origin'
headers_1['Sec-Fetch-Mode'] = 'navigate'
headers_1['Sec-Fetch-User'] = '?1'
headers_1['Sec-Fetch-Dest'] = 'document'
headers_1['Content-Length'] = '292'
headers_1['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
headers_1['Referer'] = 'https://ids.xxxxx.edu.cn/authserver/login?service=https%3A%2F%2Fi.xxxxx.edu.cn%2FEIP%2Fcaslogin.jsp'
# 获取登录参数
r = self.connect.get(self.url_login,verify=False);
soup = BeautifulSoup(r.content.decode(), 'html.parser')
self.lt = soup.find('input',{'name':'lt','type':'hidden'}).get('value')
self.dllt = soup.find('input',{'name':'dllt','type':'hidden'}).get('value')
self.execution = soup.find('input',{'name':'execution','type':'hidden'}).get('value')
self._eventId = soup.find('input',{'name':'_eventId','type':'hidden'}).get('value')
self.rmShown = soup.find('input',{'name':'rmShown','type':'hidden'}).get('value')
# 加密密钥
self.pwdDefaultEncryptSalt = soup.find('input',{'id':'pwdDefaultEncryptSalt','type':'hidden'}).get('value')
# 加密密码
se{"type":"inserter","blocks":[{"clientId":"211eef6d-820a-4b3c-a660-692668d54f0f","name":"core/code","isValid":true,"attributes":{},"innerBlocks":[]}]}lf.password = Crypto_Demo.AesCrypt(self.pwdDefaultEncryptSalt).aesencrypt(self.passwd)
data = {
'username':self.username,
'password':self.password,
'lt':self.lt,
'execution':self.execution,
'_eventId':self._eventId,
'rmShown':self.rmShown,
}
# 登录 获取cookies
r = self.connect.post(self.url_login, data=data, headers=headers_1,verify=False)
运行后得到cookies
在线加密网站:https://tool.lmeee.com/jiami/aes
有兴趣的同学可以试试看,AES加密模式:CBC填充:pkcs7padding密钥长度:128位
密钥和偏移随便1{“type”:”block”,”srcClientIds”:[“e5832fb4-51c1-415d-a269-e6812c480f35″],”srcRootClientId”:””}6个字符,给长度大于16的字符串加密,后得到的字符串进行解密,一个个改变偏移的字符看是会有什么变化。
参考:
https://zh.wikipedia.org/wiki/%E9%AB%98%E7%BA%A7%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86
https://zh.wikipedia.org/wiki/%E5%88%9D%E5%A7%8B%E5%90%91%E9%87%8F
https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
https://www.jianshu.com/p/45848dd484a9