百度贴吧登录认证与加密流程分析

1 登录安全认证流程

首先,进入百度贴吧首页,点击右上角进入登录/注册界面

请添加图片描述

登陆框图默认为百度贴吧App扫码登录,左下角为注册按钮,右上角则是切换为用户名密码登录。切换为用户名密码登录后,可以看到忘记密码按钮。进一步探索总结,其登录安全认证流程归纳如下:
请添加图片描述

  • 注册:点击注册会跳转到百度的账号注册界面,因此我们可以推测百度贴吧使用的实际上和百度是同一个账号。注册需要提供用户名手机号密码以及验证码。其中,用户名使用中英文都可,但无法重复,且一旦注册,无法修改。一个手机号只能注册一个账户。密码要求为:长度为8~14个字符;字母/数字以及标点符号至少包含2种;不允许有空格、中文。按要求正确输入上述四个表项,点击阅读并接受协议及声明即可成功注册。
  • 扫码登录:按照提示使用最新版百度App扫码,必须已经在手机上登录百度贴吧账号,扫码后需要确认登录,点击取消二维码失效。实测使用百度App扫码也可以成功登录。同时,在没有扫码的情况下,太长时间无操作,二维码也会失效。
  • 用户名密码登录:用户名处可填写手机号/用户名/邮箱,正确输入该项与密码后,点击登录完成安全验证(将图片旋转到正确位置)即可成功登录。
  • 忘记密码:首先输入手机号,完成安全验证(将图片旋转到正确位置)后,请求验证码并输入,即可进入修改密码界面。新密码填写要求与注册时相同,需要两次输入,且不可与旧密码相同。若更换手机(无法收到验证码),可点击手机不再使用,这时,可以使用百度旗下任何App扫码申诉,百度会将结果反馈到新的手机号(需要验证码确认)上。

2 密码加密流程

我们着重分析用户名密码登录过程中的加密流程。

2.1 信息收集

  1. 打开浏览器检查功能,选择网络项,输入账号,点击密码输入框,发现浏览器发送了getpublickey请求,多次测试,发现只要点击输入框,浏览器都会发送这个请求,并且每次收到的反馈都不一样,根据行为及请求名推测这是后面对密码加密要使用的公钥

    请添加图片描述

    其中连续两次的请求反馈信息如下:

    //第一次
    bd__cbs__57hpuc({
        "errno": '0',
        "msg": '',
        "pubkey": '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAPIrts2wEf8+Palf0mtPuTsQ+\nO6vNOCqf+2oSMtTSrlwlCV2jScqPdhcyyzkB8m4siuKpY1MbnBNqu0wtWwW6dyV+\nDaeSKqASvpzIDhBuua6y4qKd981vDLrugZ\/QiAoRwxvSCIofJEKqyzP6vvGxgFos\naOcO+GqtNtnmZNUMdwIDAQAB\n-----END PUBLIC KEY-----\n',
        "key": 'yyyNfqoUl1Wu89XNCg8yG99FQ7VfnQaA',
        "traceid": ""
    })
    //第二次
    bd__cbs__i98412({
        "errno": '0',
        "msg": '',
        "pubkey": '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD1KK0XMEsRi3euQlOsPzk7IvJ5\nw1pteqB5hZwhJhhymz6DEOdQ2oqoldopD4lfuk\/RcvAOvVF5rHJNfN1lrT9\/xN48\nspi2bszoTpGMW070bJu4YrzV\/ZJr0noMM3yYQeZwE0PSjSDwYvPXLDubQBjWAZEl\nYM0AwlK9eS98FSbQkwIDAQAB\n-----END PUBLIC KEY-----\n',
        "key": 'sLI9Ptz4hw9sK2Y82bxin94BONMbXRn5',
        "traceid": "CFF7E601"
    })
    
  2. 输入一个不正确的密码(1234),点击登录,发现浏览器发送了api/?login请求,其负载部分信息如下:

    请添加图片描述

    可以看到,其中包含了usernamepassword以及rsakey,并且rsakey的值与之前getpublickey请求中返回的key值相同,可能是用户凭证。通过之后的分析可以看到,加密并没有用到rsakey。很明显,这里的password已经被加密过了。由此,可以猜测这里的password采用的是RSA加密算法

2.2 JS分析

  1. 在所有js文件中搜索“password” ,结果如下:

    请添加图片描述

    一共有12个文件中出现过“password”,根据文件名以及匹配条数,首先怀疑loginv4_tangram_6f2170e.js文件。

  2. 在该文件中进一步检索,发现涉及password赋值操作的只有两行代码,包含上下文分别是:

    var a = baidu.form.json(n.getElement("smsForm"));
    a.password = n._SBCtoDBC(a.password),
    a.username = n._SBCtoDBC(a.username),
    a.FP_UID = n._getCookie("FP_UID") || "",
    a.FP_INFO = window.PP_FP_INFO || "",
    

    以及

    if (e.RSA && e.rsakey) {
        var o = i;
        o.length < 128 && !e.config.safeFlag && (s.password = baidu.url.escapeSymbol(e.RSA.encrypt(o)),
        s.rsakey = e.rsakey,
        s.crypttype = 12)
    }
    

    很明显,前者将来自 smsForm 表单的数据转换为 JSON 格式并将其存储在变量a中,而后者包含**e.RSA.encrypt()**函数,应该就是我们要找的加密函数。

    为了验证猜想,我们在后者的第三行打上断点,再次点击登录按钮,逐步调试:

    • 执行e.RSA.encrypt()前:
      请添加图片描述

    • 执行e.RSA.encrypt()后:
      请添加图片描述

    可以发现,oi的值都是**“1234“,并且在执行e.RSA.encrypt()前后s.password的值分别为”1234“”a6t%2BzVbhIKPwZv5ydME2OfI6yWCmnjS2srGFEA6zzDG3FwO1mKAcG1x5W0Fga7vJkJyfe8R4V%2BX%2B9shLIkWvBAXaJwNR9cCucovIYmsDzepGYGcFRvqYyY0CtudJJi0RdnceJTzjVgGYbO9e0SQQeAmvrGJ8wqCnr8wrJr00AQ%3D%3D“。后者应为一串URL编码的字符串,使用js对其
    进行解码,以下是
    解码代码结果**:

    请添加图片描述

    退出调试模式,查看新一轮api/?login请求负载,结果如下:

    请添加图片描述

    发现这与我们解码得到的字符串完全相同说明加密函数正是e.RSA.encrypt()

  3. 下面我们探索RSA加密实例化的过程,亦即**e.RSA.encrypt()**函数的具体内容。

    经过之前的分析可知,公钥不同,加密结果也不同,既然RSA加密与公钥相关,那么我们不妨就在文件中搜索**“pubkey”(这个参数是之前getpublickey**中返回的),结果就只有一个地方出现了这个变量:

    请添加图片描述

    在这里设置断点,执行调试,发现运行过程并没有在这里停止,而是直接发送了api/?login请求。结合之前发现在每次点击密码输入框时都会发送getpublickey请求,推测该公钥设置函数是在点击输入框之后执行的。点击输入框进行验证,函数在断点处停止,并且此时的公钥值与getpublickey请求返回相同,t中存储的就是getpublickey的返回,猜想得到验证:

    请添加图片描述

    请添加图片描述

    截取相关初始化代码并给出注释:

    var n = new passport.lib.RSA;  //初始化加密函数
    //设置公钥
    //在提取代码时,可以只保留n,不需要用到e,因为加密只用到了e.RSA,而e.RSA实际上就是n
    n.setKey(t.pubkey),  
        e && e({
        RSA: n,
        rsakey: t.key
    })
    
  4. 继续查找passport.lib.RSA的定义:

    请添加图片描述

    根据上图定位到f(t)函数,截取与passport有关上下文(function(t)内部为具体RSA加密有关细节,不做展示):

    请添加图片描述

  5. 查找**baidu.url.escapeSymbol()**函数的定义,将其保存至本地:

    请添加图片描述

    分析可知,baidu.url.escapeSymbol()函数是对加密结果的一个重新编码,用于将字符串中的特殊字符转义为URL安全的形式

2.3 验证

结合加密函数初始化、加密部分的代码,编写加密js脚本password.js,解决一些诸如window、navigator未定义的问题,成功运行,输出为172位密文。但是,每次运行的结果都不同,有两种可能得结果,其一是前述分析出错,其二是在加密过程中用到了随机数操作

请添加图片描述

再次进入登录页面,发现每次清除缓存后,即使没有再次发送公钥请求,新一轮的密文也会发生变化。因此可以判断之前的分析没有出错,推测应该是RSA算法中使用了随机填充,而直接点击登录按钮时,只有第一次执行js代码,后面的提交使用的是浏览器的缓存。

请添加图片描述
请添加图片描述

2.4 总结

用户以及浏览器行为来总结百度贴吧的加密流程:

  1. 用户点击用户名输入框,输入用户名。
  2. 用户点击密码输入框(此时浏览器发送请求获取公钥),输入密码。
  3. 用户点击登录按钮,浏览器使用公钥对密码进行RSA加密并发送登录请求,请求包含用户名、密文、用户凭证、浏览器指纹等等。

3 代码应用

了解了网站的登录加密算法,我们可以编写脚本或程序来模拟用户的登录操作,这可以为后续的爬虫或者其他自动化数据采集方法做铺垫。在经过比较长时间的尝试后,发现除了密文以及密钥信息,登录还需要dv、fuid等信息,而这些信息都是动态变化的。经过初步分析,这写信息与浏览器指纹等有关,其分析与获取难度不亚于密文加密分析。再加上输入密码后还有人机验证这对于爬虫等操作来说是不划算的,远不如直接使用cookie登录来得简单。所以这里仅仅对自动化获取密钥并加密做尝试。

简单的思路为:

  1. 向服务器发送获取公钥请求。
  2. 将返回解析为JSON格式并提取出密钥。
  3. 调用JS脚本对明文加密并输出。

以下是运行结果(相关代码见附录):

请添加图片描述

输出结果包含获取的公钥、密文以及密文长度

pubkey:
 -----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNpwG1oZlm1o9worIguGRBDc2R
E2sMRYFEdCqvYTwiVtEy2XycScDnsgQMpH9p2JPonK27AcedstGxk5eyPTuUR4zC
CxlhfsXffr0PSgxY5oo4aCTX8WYJDXcStNhlJdx5i5ZaRcv160nRpE/baZXpXTAq
xKMmIA9Ty6SMM/U76QIDAQAB
-----END PUBLIC KEY-----

密文:
DivUrGUZTVW1PPRzUKWqVlkdi5MdVqpmDMzEin2dgvyPWUb9L4jJL8Atz5RqA80tOZsTVOUqtDFXp37yS0hBkLQcK5TizSfemnNKipEjCcM9pg2uJCBx+aWiLNQM/lB3kf7DhLyADerE1R3uBLl/E4MMtnPU2VctgZ6mPk8vMIk=

密文长度:
172

附录

import subprocess
import requests
import sys
import json

# 设置标准输出编码为UTF-8,否则提取JS输出会解码出错
sys.stdout = open(1, 'w', encoding='utf-8', closefd=False)

# 创建会话对象
session = requests.Session()

# 发送GET请求获取登录页
login_url = "https://passport.baidu.com/v2/getpublickey?token=9a4286a899d8903381c1a5e289a09a7c&tpl=tb&subpro=&apiver=v3&tt=1699406747230&gid=2341881-3220-4D98-BA80-05D2371B960E&loginversion=v4&traceid=13696501&time=1699406747&alg=v3&sig=VEhSUHE4eWtLTjRRZE1VWG9kTXExSXZOOHEybXlSeUxyM2ErVjRJbnM2UGlrbll4bmd2dkZ5dXBuU1lxM2FJeA%3D%3D&elapsed=9&shaOne=00c97d3bd2b1a4d30cbca01cd015078d236b1c20&rinfo=%7B%22fuid%22%3A%226b76bffbaa9fc6850ad92648b8494615%22%7D&callback=bd__cbs__vmtq5a"
response = session.get(login_url)

# 提取返回中的JSON部分
json_text = response.text.split('(', 1)[1].rsplit(')', 1)[0]
json_text = json_text.replace("'", "\"")

# 解析JSON数据
data = json.loads(json_text)

# 提取pubkey和key
pubkey = data.get("pubkey")
key = data.get("key")
print("pubkey:\n", pubkey)

# 密码明文
password = "1234"
# 执行外部JavaScript文件并传递参数
result = subprocess.run(["node", "password.js", password, pubkey], capture_output=True, text=True)
password = result.stdout.strip()  # 去除字符串两端的空白字符

print("密文:\n",password)
print("密文长度:\n",len(password))
  • 38
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值