Fork me on GitHub

五百丁登录enPassword参数

难度:★☆☆☆☆ 1星

一、目标

目标网站: https://www.500d.me/login/
登录的时候提交表单里密码字段enPassword是被加密的:
0
本次目标就是破解这个参数加密。

二、分析

打开登录页:
https://www.500d.me/login/
打开开发者工具,切换到Network,清空掉无关请求,然后页面里输入账号密码尝试登陆,注意账号密码是随便输的,是故意让它登陆失败观察一下流程的:
1
捕捉到了三个请求,先看下第一个Get请求,链接是:
https://www.500d.me/common/public_key/?_=1605590883257
响应内容:

01
02
03
04
{
    "modulus": "AM+emhhTb5EOH/ZbDg78dHOw79H4aFQkF4pCFCw9yo8oRigsa0p6bIB3UVjK+S5E1v3OSy1+/4WM10Zb+k+qV/7hK0GuoO2w15s+0nYJLjPC4SO8WmFgNC5aQsdHOPXt9hcK6sbJKh4dWR/U/pyfTOlp1IJqx4ZALyAf5sZN25Np",
    "exponent": "AQAB"
}
乍一看有点懵逼,其实如果之前搞过类似的话看一眼url里面的public_key和响应内容,大概就知道这应该是就是获取加密密码时使用的公钥了,密码也可能就是rsa加密的,我们借助ModHeader来测试一下这个请求是否对cookie或者referer做了检查,可以看到不带cookie和referer也是可以访问的:
2
好的继续看第二个Post请求:
https://www.500d.me/login/submit/
这个是实际提交登录参数的,因此提交了一个表单有用户名密码之类的参数:
3
第三个请求实际上是一个雪碧图,用于在页面上显示图标用的,这里不再详述。
通过观察请求大致捋出来了登录的流程,先是发送一个请求获取公钥,然后再用js加密密码提交登录表单,接下来的重点就在第二个请求发送前的js逻辑,接下就是想办法去定位到那段js代码,复制第二个请求的url,打一个xhr断点:
4
然后在页面上重新尝试登录,就卡在了断点这里,格式化代码,同时在调用栈里往前回溯寻找相关的栈帧:
5
在success方法的栈帧里看到了发出登录请求的代码,这个success是前面那个获取公钥的接口成功时的回调方法:
6
密码字段加密的核心逻辑:
01
02
03
var rsaKey = new RSAKey();
rsaKey.setPublic(b64tohex(data.modulus), b64tohex(data.exponent));
var enPassword = hex2b64(rsaKey.encrypt(form.find("input[name='password']").val()));
然后把鼠标放到RSAKey上悬停一会儿,会弹出弹窗表明出处,单击跟进入:
7
然后定位到了一个叫做rsa.js的文件,把这个文件整个抠出来新建一个文件encrypt.js放进去:
8
然后回到加密的方法,如法炮制找到b64tohex和hex2b64的逻辑:
9
10
这两个都是base64.js文件中,同样跟进去,然后整个抠出来放到encrypt.js,然后在encrypt.js中尝试写一个加密密码的方法为外部提供调用的接口:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
/**
 * 向外界暴露加密密码的方法
 *
 * @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中:
11
都扣完了还是报错:
12
然后补环境,把下面的代码插入到encrypt.js的最前面:
01
02
03
04
05
06
07
// 补环境
const window = {
    navigator: {
        appName: "Netscape"
    }
};
const navigator = window.navigator;
然后再运行一下,能够加密出密码了:
13
把893行的测试代码注释掉,拷到pycharm里,接下来会在python中调用这个js加密密码。
这里还有一个需要注意的问题,就是在登录的时候提交登录表单的接口必须带上token请求头,如果不带的话就会405(方法不被允许: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/405 ),使用ModHeader手动搞掉token试下:
14
可以看到405了:
15
这个token是用jQuery的ajaxSend方法设置的一个hook里设置到请求头上的,jQuery的ajaxSend可以设置一个函数在ajax发送之前运行,通过正则搜索“header.{0,100}token”,表示搜索出现在header附近的token,因为js设置的话有可能就是这样设置上的:
16
于是就定位到了这个文件:
https://static.500d.me/resources/500d/js/utils.js?v=V7119
文件的这个位置,就是在这里设置的请求头:
17
这个名为token的cookie是在第一次访问登录页的时候设置的cookie:
18
访问一次登录页拿到这个token就好。
流程基本捋清楚了,接下来是编码实现。

三、编码实现

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/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")


仓库:

https://github.com/CC11001100/misc-crawler-public/tree/master/001-anti-crawler-js-re/01-005-www.500d.me


请注意爬虫文章具有时效性,本文写于2020-11-17日。

posted @   CC11001100  阅读(865)  评论(1编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2019-11-25 flash逆向练习:以逆向的方式通关flash游戏《谈判专家》
点击右上角即可分享
微信分享提示