五百丁登录enPassword参数
难度:★☆☆☆☆ 1星
一、目标
目标网站: https://www.500d.me/login/
登录的时候提交表单里密码字段enPassword是被加密的:
本次目标就是破解这个参数加密。
二、分析
打开登录页:
https://www.500d.me/login/
打开开发者工具,切换到Network,清空掉无关请求,然后页面里输入账号密码尝试登陆,注意账号密码是随便输的,是故意让它登陆失败观察一下流程的:
捕捉到了三个请求,先看下第一个Get请求,链接是:
https://www.500d.me/common/public_key/?_=1605590883257
响应内容:
{
"modulus": "AM+emhhTb5EOH/ZbDg78dHOw79H4aFQkF4pCFCw9yo8oRigsa0p6bIB3UVjK+S5E1v3OSy1+/4WM10Zb+k+qV/7hK0GuoO2w15s+0nYJLjPC4SO8WmFgNC5aQsdHOPXt9hcK6sbJKh4dWR/U/pyfTOlp1IJqx4ZALyAf5sZN25Np",
"exponent": "AQAB"
}
乍一看有点懵逼,其实如果之前搞过类似的话看一眼url里面的public_key和响应内容,大概就知道这应该是就是获取加密密码时使用的公钥了,密码也可能就是rsa加密的,我们借助ModHeader来测试一下这个请求是否对cookie或者referer做了检查,可以看到不带cookie和referer也是可以访问的:
好的继续看第二个Post请求:
https://www.500d.me/login/submit/
这个是实际提交登录参数的,因此提交了一个表单有用户名密码之类的参数:

第三个请求实际上是一个雪碧图,用于在页面上显示图标用的,这里不再详述。
通过观察请求大致捋出来了登录的流程,先是发送一个请求获取公钥,然后再用js加密密码提交登录表单,接下来的重点就在第二个请求发送前的js逻辑,接下就是想办法去定位到那段js代码,复制第二个请求的url,打一个xhr断点:

然后在页面上重新尝试登录,就卡在了断点这里,格式化代码,同时在调用栈里往前回溯寻找相关的栈帧:

在success方法的栈帧里看到了发出登录请求的代码,这个success是前面那个获取公钥的接口成功时的回调方法:

密码字段加密的核心逻辑:
var rsaKey = new RSAKey();
rsaKey.setPublic(b64tohex(data.modulus), b64tohex(data.exponent));
var enPassword = hex2b64(rsaKey.encrypt(form.find("input[name='password']").val()));
然后把鼠标放到RSAKey上悬停一会儿,会弹出弹窗表明出处,单击跟进入:
然后定位到了一个叫做rsa.js的文件,把这个文件整个抠出来新建一个文件encrypt.js放进去:

然后回到加密的方法,如法炮制找到b64tohex和hex2b64的逻辑:


这两个都是base64.js文件中,同样跟进去,然后整个抠出来放到encrypt.js,然后在encrypt.js中尝试写一个加密密码的方法为外部提供调用的接口:
/**
* 向外界暴露加密密码的方法
*
* @param passwd
* @param modulus
* @param exponent
* @returns {string|*}
*/
function encryptPasswd(passwd, modulus, exponent) {
const rsaKey = new RSAKey();
rsaKey.setPublic(b64tohex(modulus), b64tohex(exponent));
return hex2b64(rsaKey.encrypt(passwd));
}
console.log(encryptPasswd("cc11001100", "{\n" +
" \"modulus\": \"AJbFLrvha10BPOdevQ+cuIDirMylI9srBg3MQe/3jG3FovKT3+/hSHPZbJljaOHnLHskJh1+r8ECwpJEU16xA73D+SbCcK83my+vMH2VLdP9w6eRfqkEfo+W/5yn7ZmNAnGTPlTC29I3b8cyVEuUHHO8HQGpgJXJp7FDbTjxgj6R\"\n" +
"}", "AQAB"));
同时node运行测试一下是否OK,然后发现报错了,因为扣的js不全,有些依赖没有放到encrypt.js中,回到登录页面,查看源代码,搜索rsa定位到这里,把红色方框内没有扣的js一股脑儿扣了放到encrypt.js中:
都扣完了还是报错:

然后补环境,把下面的代码插入到encrypt.js的最前面:
// 补环境
const window = {
navigator: {
appName: "Netscape"
}
};
const navigator = window.navigator;
然后再运行一下,能够加密出密码了:
把893行的测试代码注释掉,拷到pycharm里,接下来会在python中调用这个js加密密码。
这里还有一个需要注意的问题,就是在登录的时候提交登录表单的接口必须带上token请求头,如果不带的话就会405(方法不被允许: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/405 ),使用ModHeader手动搞掉token试下:

可以看到405了:

这个token是用jQuery的ajaxSend方法设置的一个hook里设置到请求头上的,jQuery的ajaxSend可以设置一个函数在ajax发送之前运行,通过正则搜索“header.{0,100}token”,表示搜索出现在header附近的token,因为js设置的话有可能就是这样设置上的:

于是就定位到了这个文件:
https://static.500d.me/resources/500d/js/utils.js?v=V7119
文件的这个位置,就是在这里设置的请求头:

这个名为token的cookie是在第一次访问登录页的时候设置的cookie:

访问一次登录页拿到这个token就好。
流程基本捋清楚了,接下来是编码实现。
三、编码实现
#!/usr/bin/env python3
# encoding: utf-8
"""
@author: CC11001100
"""
import functools
import time
import execjs
import requests
session = requests.session()
def login(username, passwd):
token = get_token()
print(f"token = {token}")
public_key = get_public_key()
print(f"public key = {public_key}")
data = {
"username": username,
"enPassword": load_js_context().call("encryptPasswd", passwd, public_key["modulus"], public_key["exponent"]),
"service": "",
"remember": True
}
print(data)
headers = {
# 这个参数是必须要带的,什么鬼情况,是后端的框架要检测还是手动做的检测,好像有些框架需要这个参数知道是个xhr请求
"X-Requested-With": "XMLHttpRequest",
# U-A反倒不是必须的...
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
# token是必须要带的
"token": token,
}
url = "https://www.500d.me/login/submit/"
r = session.post(url, data=data, headers=headers)
print(r.status_code) # 200
print(r.text) # {"type":"success","content":""}
@functools.lru_cache(maxsize=1)
def load_js_context():
with open("./encrypt.js", encoding="UTF-8") as f:
js_code = f.read()
return execjs.compile(js_code)
def get_token():
url = "https://www.500d.me/login/"
return session.get(url).cookies["token"]
def get_public_key():
url = f"https://www.500d.me/common/public_key/?_={int(time.time() * 1000)}"
return session.get(url).json()
if __name__ == "__main__":
# 注册资料:
# 邮箱: korowof384@rvemold.com
# 昵称: cc11001100_test
# 密码: ccIs0KccIs0K
login("korowof384@rvemold.com", "ccIs0KccIs0K")
仓库:
请注意爬虫文章具有时效性,本文写于2020-11-17日。


浙公网安备 33010602011771号