声明

本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!

逆向目标

本次的逆向目标是WB的登录,虽然登录的加密参数没有太多,但是登录的流程稍微复杂一点,经历了很多次中转,细分下来大约要经过九次处理才能成功登录。

在登录过程中遇到的加密参数只有一个,即密码加密,加密后的密码在获取 token 的时候会用到,获取 token 是一个 POST 请求,其 Form Data 里的 sp 值就是加密后的密码,类似于:e23c5d62dbf9f8364005f331e487873c70d7ab0e8dd2057c3e66d1ae5d2837ef1dcf86......

登录流程

首先来理清一下登录流程,每一步特殊的参数进都行了说明,没有提及的参数表示是定值,直接复制即可。

大致流程如下:

  1. 预登陆

  2. 获取加密密码

  3. 获取 token

  4. 获取加密后的账号

  5. 发送验证码

  6. 校验验证码

  7. 访问 redirect url

  8. 访问 crossdomain2 url

  9. 通过 passport url 登录

1.预登陆

预登陆为 GET 请求,Query String Parameters 中主要包含两个比较重要的参数:su:用户名经过 base64 编码得到,_: 13 位时间戳,返回的数据包含一个 JSON,可用正则提取出来,JSON 里面包含 retcodeservertimepcidnoncepubkeyrsakvexectime 七个参数值,其中大多数值都是后面的请求当中要用到的,部分值是加密密码要用到的,返回数据数示例:

xxxxSSOController.preloginCallBack({
"retcode": 0,
"servertime": 1627461942,
"pcid": "gz-1cd535198c0efe850b96944c7945e8fd514b",
"nonce": "GWBOCL",
"pubkey": "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245......",
"rsakv": 1330428213,
"exectime": 16
})

2.获取加密后的密码

密码的加密使用的是 RSA 加密,可以通过 Python 或者 JS 来获取加密后的密码,JS 加密的逆向在后面拿出来单独分析。

3.获取 token

这个 token 值在后面的获取加密手机号、发送验证码、校验验证码等步骤中都会用到,获取 token 值为 POST 请求,Query String Parameters 的值是固定的:client: ssologin.js(v1.4.19),Form Data 的值相对来说比较多,但是除了加密的密码以外,其他参数其实都是可以在第1步预登陆返回的数据里找到,主要的参数如下:

  • su:用户名经过 base64 加密得到
  • servertime:通过第1步预登陆返回的 JSON 里面获取
  • nonce:通过第1步预登陆返回的 JSON 里面获取
  • rsakv:通过第1步预登陆返回的 JSON 里面获取
  • sp:加密后的密码
  • prelt:随机值

返回数据为 HTML 源码,可以从里面提取 token 值,类似于:2NGFhARzFAFAIp_QwX70Npj8gw4lgj7RbCnByb3RlY3Rpb24.,如果返回的 token 不是这种,则说明账号或者密码错误。

4.获取加密后的账号

前面我们遇到的 su 是用户名经过 base64 加密得到,这里它对用户名进行了进一步的加密处理,加密后的用户名在发送验证码和校验验证码的时候会用到,GET 请求,Query String Parameters 的参数也比较简单,token 就是第3步获取的 token 值,callback_url 是网站的主页,返回数据是 HTML 源码,可以使用 xpath 语法://input[@name='encrypt_mobile']/@value 来提取加密后的账号,其值类似于:f2de0b5e333a,这里需要注意的是,即便是同一个账号,每次加密的结果也是不一样的。

5.发送验证码

发送验证码是一个 POST 请求,其参数也比较简单,Query String Parameters 里的 token 是第3步获取的 token,Form Data 里的 encrypt_mobile 是第4步获取的加密后的账号,返回的数据是验证码发送的状态,例如:{'retcode': 20000000, 'msg': 'succ', 'data': []}

6.校验验证码

校验验证码是一个 POST 请求,其参数也非常简单,Query String Parameters 里的 token 是第3步获取的 token,Form Data 里的 encrypt_mobile 是第4步获取的加密后的账号,code 是第5步收到的验证码,返回数据是一个 JSON,retcodemsg 代表校验的状态,redirect url 是校验步骤完成后接着要访问的页面,在下一步中要用到,返回的数据示例:

{
"retcode": 20000000,
"msg": "succ",
"data": {
"redirect_url": "https://login.xxxx.com.cn/sso/login.php?entry=xxxxx&returntype=META&crossdomain=1&cdult=3&alt=ALT-NTcxNjMyMTA2OA==-1630292617-yf-78B1DDE6833847576B0DC4B77A6C77C4-1&savestate=30&url=https://xxxxx.com"
}
}

7.访问 redirect url

这一步的请求接口其实就是第6步返回的 redirect url,GET 请求,类似于:https://login.xxxx.com.cn/sso/login.php?entry=xxxxx&returntype=META......

返回的数据是 HTML 源码,我们要从中提取 crossdomain2 的 URL,提取的结果类似于:https://login.xxxx.com.cn/crossdomain2.php?action=login&entry=xxxxx......,同样的,这个 URL 也是接下来需要访问的页面。

8.访问 crossdomain2 url

这一步的请求接口就是第7步提取的 crossdomain2 url,GET 请求,类似于:https://login.xxxx.com.cn/crossdomain2.php?action=login&entry=xxxxx......

返回的数据同样是 HTML 源码,我们要从中提取真正的登录的 URL,提取的结果类似于:https://passport.xxxxx.com/wbsso/login?ssosavestate=1661828618&url=https......,最后一步只需要访问这个真正的登录 URL 就能实现登录操作了。

9.通过 passport url 登录

这是最后一步,也是真正的登录操作,GET 请求,请求接口就是第8步提取的 passport url,类似于:https://passport.xxxxx.com/wbsso/login?ssosavestate=1661828618&url=https......

返回的数据包含了登录结果、用户 ID 和用户名,类似于:

({"result":true,"userinfo":{"uniqueid":"5712321368","displayname":"tomb"}});

自此,WB的完整登录流程已完成,可以直接拿登录成功后的 cookies 进行其他操作了。

加密密码逆向

在登录流程中,第2步是获取加密后的密码,在登录的第3步获取 token 里,请求的 Query String Parameters 包含了一个加密参数 sp,这个就是加密后的密码,接下来我们对密码的加密进行逆向分析。

直接全局搜索 sp 关键字,发现有很多值,这里我们又用到了前面讲过的技巧,尝试搜索 sp=sp: 或者 var sp 等来缩小范围,在本案例中,我们尝试搜索 sp=,可以看到在 index.js 里面只有一个值,埋下断点进行调试,可以看到 sp 其实就是 b 的值:

PS:搜索时要注意,不能在登录成功后的页面进行搜索,此时资源已刷新,重新加载了,加密的 JS 文件已经没有了,需要在登录界面输入错误的账号密码来抓包、搜索、断点。

继续往上追踪这个 b 的值,关键代码有个 if-else 语句,分别埋下断点,经过调试可以看到 b 的值在 if 下面生成:

分析一下两行关键代码:

f.setPublic(me.rsaPubkey, "10001");
b = f.encrypt([me.servertime, me.nonce].join("\t") + "\n" + b)

me.rsaPubkeyme.servertimeme.nonce 都是第1步预登陆返回的数据。

把鼠标移到 f.setPublicf.encrypt,可以看到分别是 brbt 函数:

分别跟进这两个函数,可以看到都在一个匿名函数下面:

直接将整个匿名函数复制下来,去掉最外面的匿名函数,进行本地调试,调试过程中会提示 navigator 未定义,查看复制的源码,里面用到了 navigator.appNamenavigator.appVersion,直接定义即可,或者置空都行。

navigator = {
appName: "Netscape",
appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}

继续调试会发现在 var c = this.doPublic(b); 提示对象不支持此属性或方法,搜索 doPublic 发现有一句 bq.prototype.doPublic = bs;,这里直接将其改为 doPublic = bs; 即可。

分析整个 RSA 加密逻辑,其实也可以通过 Python 来实现,代码示例(pubkey 需要补全):

import rsa
import binascii pre_parameter = {
"retcode": 0,
"servertime": 1627461942,
"pcid": "gz-1cd535198c0efe850b96944c7945e8fd514b",
"nonce": "GWBOCL",
"pubkey": "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245......",
"rsakv": 1330428213,
"exectime": 16
} password = '12345678' public_key = rsa.PublicKey(int(pre_parameter['pubkey'], 16), int('10001', 16))
text = '%s\t%s\n%s' % (pre_parameter['servertime'], pre_parameter['nonce'], password)
encrypted_str = rsa.encrypt(text.encode(), public_key)
encrypted_password = binascii.b2a_hex(encrypted_str).decode() print(encrypted_password)

完整代码

GitHub 关注 K 哥爬虫,持续分享爬虫相关代码!欢迎 star !https://github.com/kgepachong/

以下只演示部分关键代码,不能直接运行!完整代码仓库地址:https://github.com/kgepachong/crawler/

关键 JS 加密代码架构

navigator = {
appName: "Netscape",
appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
} function bt(a) {} function bs(a) {} function br(a, b) {} // 此处省略 N 个函数 bl.prototype.nextBytes = bk;
doPublic = bs;
bq.prototype.setPublic = br;
bq.prototype.encrypt = bt;
this.RSAKey = bq function getEncryptedPassword(me, b) {
br(me.pubkey, "10001");
b = bt([me.servertime, me.nonce].join("\t") + "\n" + b);
return b
} // 测试样例
// var me = {
// "retcode": 0,
// "servertime": 1627283238,
// "pcid": "gz-a9243276722ed6d4671f21310e2665c92ba4",
// "nonce": "N0Y3SZ",
// "pubkey": "EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245A87AC253062882729293E5506350508E7F9AA3BB77F4333231490F915F6D63C55FE2F08A49B353F444AD3993CACC02DB784ABBB8E42A9B1BBFFFB38BE18D78E87A0E41B9B8F73A928EE0CCEE1F6739884B9777E4FE9E88A1BBE495927AC4A799B3181D6442443",
// "rsakv": "1330428213",
// "exectime": 13
// }
// var b = '12312312312' // 密码
// console.log(getEncryptedPassword(me, b))

Python 登录关键代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*- import re
import json
import time
import base64
import binascii import rsa
import execjs
import requests
from lxml import etree # 判断某些请求是否成功的标志
response_success_str = 'succ' pre_login_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler'
get_token_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler'
protection_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler'
send_code_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler'
confirm_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler' headers = {
'Host': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'Referer': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
session = requests.session() def get_pre_parameter(username: str) -> dict:
su = base64.b64encode(username.encode())
time_now = str(int(time.time() * 1000))
params = {
'entry': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'callback': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'su': su,
'rsakt': 'mod',
'checkpin': 1,
'client': 'ssologin.js(v1.4.19)',
'_': time_now,
}
response = session.get(url=pre_login_url, params=params, headers=headers).text
parameter_dict = json.loads(re.findall(r'\((.*)\)', response)[0])
# print('1.【pre parameter】: %s' % parameter_dict)
return parameter_dict def get_encrypted_password(pre_parameter: dict, password: str) -> str:
# 通过 JS 获取加密后的密码
# with open('encrypt.js', 'r', encoding='utf-8') as f:
# js = f.read()
# encrypted_password = execjs.compile(js).call('getEncryptedPassword', pre_parameter, password)
# # print('2.【encrypted password】: %s' % encrypted_password)
# return encrypted_password # 通过 Python 的 rsa 模块和 binascii 模块获取加密后的密码
public_key = rsa.PublicKey(int(pre_parameter['pubkey'], 16), int('10001', 16))
text = '%s\t%s\n%s' % (pre_parameter['servertime'], pre_parameter['nonce'], password)
encrypted_str = rsa.encrypt(text.encode(), public_key)
encrypted_password = binascii.b2a_hex(encrypted_str).decode()
# print('2.【encrypted password】: %s' % encrypted_password)
return encrypted_password def get_token(encrypted_password: str, pre_parameter: dict, username: str) -> str:
su = base64.b64encode(username.encode())
data = {
'entry': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'gateway': 1,
'from': '',
'savestate': 7,
'qrcode_flag': False,
'useticket': 1,
'pagerefer': '',
'vsnf': 1,
'su': su,
'service': 'miniblog',
'servertime': pre_parameter['servertime'],
'nonce': pre_parameter['nonce'],
'pwencode': 'rsa2',
'rsakv': pre_parameter['rsakv'],
'sp': encrypted_password,
'sr': '1920*1080',
'encoding': 'UTF-8',
'prelt': 38,
'url': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler',
'returntype': 'META'
}
response = session.post(url=get_token_url, headers=headers, data=data)
# response.encoding = 'gbk'
ajax_login_url = re.findall(r'replace\("(.*)"\)', response.text)[0]
token = ajax_login_url.split('token%3D')[-1]
if 'weibo' not in token:
# print('3.【token】: %s' % token)
return token
else:
raise Exception('登录失败! 用户名或者密码错误!') def get_encrypted_mobile(token: str) -> str:
params = {
'token': token,
'callback_url': '脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler'
}
response = session.get(url=protection_url, params=params, headers=headers)
tree = etree.HTML(response.text)
encrypted_mobile = tree.xpath("//input[@name='encrypt_mobile']/@value")[0]
# print('4.【encrypted mobile】: %s' % encrypted_mobile)
return encrypted_mobile def send_code(token: str, encrypt_mobile: str) -> str:
params = {'token': token}
data = {'encrypt_mobile': encrypt_mobile}
response = session.post(url=send_code_url, params=params, data=data, headers=headers).json()
if response['msg'] == response_success_str:
code = input('请输入验证码: ')
# print('5.【code】: %s' % code)
return code
else:
# print('5.【failed to send verification code】: %s' % response)
raise Exception('验证码发送失败: %s' % response) def confirm_code(encrypted_mobile: str, code: str, token: str) -> str:
params = {'token': token}
data = {
'encrypt_mobile': encrypted_mobile,
'code': code
}
response = session.post(url=confirm_url, params=params, data=data, headers=headers).json()
if response['msg'] == response_success_str:
redirect_url = response['data']['redirect_url']
# print('6.【redirect url】: %s' % redirect_url)
return redirect_url
else:
# print('6.【验证码校验失败】: %s' % response)
raise Exception('验证码校验失败: %s' % response) def get_cross_domain2_url(redirect_url: str) -> str:
response = session.get(url=redirect_url, headers=headers).text
cross_domain2_url = re.findall(r'replace\("(.*)"\)', response)[0]
# print('7.【cross domain2 url】: %s' % cross_domain2_url)
return cross_domain2_url def get_passport_url(cross_domain2_url: str) -> str:
response = session.get(url=cross_domain2_url, headers=headers).text
passport_url_str = re.findall(r'setCrossDomainUrlList\((.*)\)', response)[0]
passport_url = json.loads(passport_url_str)['arrURL'][0]
# print('8.【passport url】: %s' % passport_url)
return passport_url def login(passport_url: str) -> None:
response = session.get(url=passport_url, headers=headers).text
login_result = json.loads(response.replace('(', '').replace(');', ''))
if login_result['result']:
user_unique_id = login_result['userinfo']['uniqueid']
user_display_name = login_result['userinfo']['displayname']
print('登录成功!用户 ID:%s,用户名:%s' % (user_unique_id, user_display_name))
else:
raise Exception('登录失败:%s' % login_result) def main():
username = input('请输入登录账号: ')
password = input('请输入登录密码: ') # 1.预登陆,获取一个字典参数,包含后面要用的 servertime、nonce、pubkey、rsakv
pre_parameter = get_pre_parameter(username) # 2.通过 JS 或者 Python 获取加密后的密码
encrypted_password = get_encrypted_password(pre_parameter, password) # 3.获取 token
token = get_token(encrypted_password, pre_parameter, username) # 4.通过 protection url 获取加密后的手机号
encrypted_mobile = get_encrypted_mobile(token) # 5.发送手机验证码
code = send_code(token, encrypted_mobile) # 6.校验验证码,校验成功则返回一个重定向的 URL
redirect_url = confirm_code(encrypted_mobile, code, token) # 7.访问重定向的 URL,提取 crossdomain2 URL
cross_domain2_url = get_cross_domain2_url(redirect_url) # 8.访问 crossdomain2 URL,提取 passport URL
passport_url = get_passport_url(cross_domain2_url) # 9.访问 passport URL 进行登录操作
login(passport_url) if __name__ == '__main__':
main()

【JS 逆向百例】复杂的登录过程,最新WB逆向的更多相关文章

  1. Node.js开发入门—使用cookie保持登录

    这次来做一个站点登录的小样例,后面会用到. 这个演示样例会用到Cookie.HTML表单.POST数据体(body)解析. 第一个版本号,我们的用户数据就写死在js文件中. 第二个版本号会引入Mong ...

  2. Java使用正则表达式取网页中的一段内容(以取Js方法为例)

    关于正则表达式: 表1.常用的元字符 代码 说明 . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 \s 匹配任意的空白符 \d 匹配数字 \b 匹配单词的开始或结束 ^ 匹配字符串 ...

  3. 【算法】C语言趣味程序设计编程百例精解

    C语言趣味程序设计编程百例精解 C/C++语言经典.实用.趣味程序设计编程百例精解(1)  https://wenku.baidu.com/view/b9f683c08bd63186bcebbc3c. ...

  4. 【转】SQL Server -- 已成功与服务器建立连接,但是在登录过程中发生错误

    SQL Server -- 已成功与服务器建立连接,但是在登录过程中发生错误 最近在VS2013上连接远程数据库时,突然连接不上,在跑MSTest下跑的时候,QTAgent32 crash.换成IIS ...

  5. 已成功与服务器建立连接,但是在登录过程中发生错误。 (provider: SSL Provider, error: 0 - 接收到的消息异常,或格式不正确。)

    之前做好的asp.net部署后,发现 访问数据库时: 异常:已捕获: "已成功与服务器建立连接,但是在登录过程中发生错误. (provider: SSL Provider, error: 0 ...

  6. SQL Server(解决问题)已成功与服务器建立连接,但是在登录过程中发生错误。(provider: Shared Memory Provider, error:0 - 管道的另一端上无任何进程。

    http://blog.csdn.net/github_35160620/article/details/52676416 如果你在使用新创建的 SQL Server 用户名和密码 对数据库进行连接的 ...

  7. Java设计模式百例 - 观察者模式

    观察者(Observer)模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,主体对象的状态变化会通知所有观察者对象.观察者模式又叫做发布-订阅(Publish/Subscribe ...

  8. flask的session解读及flask_login登录过程研究

    #!/usr/bin/env python # -*- coding: utf-8 -*- from itsdangerous import URLSafeTimedSerializer from f ...

  9. sql server 2008启动时:已成功与服务器建立连接,但是在登录过程中发生错误。(provider:命名管道提供程序,error:0-管道的另一端上无任何进程。)(Microsoft SQL Server,错误:233) 然后再连接:错误:18456

    问题:sql server 2008启动时:已成功与服务器建立连接,但是在登录过程中发生错误.(provider:命名管道提供程序,error:0-管道的另一端上无任何进程.)(Microsoft S ...

  10. 上百例Silverlight网站及演示汇总,供友参考

    毁灭2012 博客园 首页 新闻 新随笔 联系 管理 订阅 随笔- 125  文章- 0  评论- 446  上百例Silverlight网站及演示汇总,供友参考   今天我将发现的Silverlig ...

随机推荐

  1. 有什么好用的C/C++源代码混淆工具?

    开始使用ipaguard 前言 iOS加固保护是直接针对ios ipa二进制文件的保护技术,可以对iOS APP中的可执行文件进行深度混淆.加密.使用任何工具都无法逆向.破解还原源文件.对APP进行完 ...

  2. 创建一个简单的Docker镜像

    1. 创建 Dockerfile 文件.index.html测试页面 [root@localhost docker]# vi Dockerfile FROM nginx:1.17.6 #基于 ngin ...

  3. selenium-web自动化(po模型)

    什么是po模型呢?简单理解就是:把每个页面当成一个对象,给这些页面当成一个类,主要就是完成元素定位和业务操作:把它和测试脚本区分开来,需要什么取这些页面类去调用即可.这样的好处在于页面元素发生变化时, ...

  4. 如何在 Unity 游戏中集成 AI 语音识别?

    简介 语音识别是一项将语音转换为文本的技术,想象一下它如何在游戏中发挥作用?发出命令操纵控制面板或者游戏角色.直接与 NPC 对话.提升交互性等等,都有可能.本文将介绍如何使用 Hugging Fac ...

  5. 【JAVA基础】JAVA基础知识整理

    https://www.weixueyuan.net/java/rumen_1/ JAVA的三大版本 JDK.JRE.JVM JAVA安装与卸载 JDK结构 HelloWorld运行 编译型与解释型 ...

  6. Java基础知识整理,驼峰规则、流程控制、自增自减

    写在开头 本文接着上一篇文章续写哈.[Java基础知识整理,注释.关键字.运算符](https://blog.csdn.net/qq_43506040/article/details/13563332 ...

  7. [NOI2015]荷马史诗 - Huffman树

    题目描述 追逐影子的人,自己就是影子. --荷马 llison 最近迷上了文学.她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静地阅读她爱不释手的<荷马史诗>.但是由<奥德赛&g ...

  8. L2-030 冰岛人 (25 分) (阅读理解)

    补题链接:Here 2018年世界杯,冰岛队因1:1平了强大的阿根廷队而一战成名.好事者发现冰岛人的名字后面似乎都有个"松"(son),于是有网友科普如下: 冰岛人沿用的是维京人古 ...

  9. SpringBoot多模块项目搭建以及搭建基础模板

    多模块项目搭建 目录 多模块项目搭建 1.父项目pom文件编辑 2.创建子模块 1.父项目pom文件编辑 <!--1.父工程 添加pom格式--> <packaging>pom ...

  10. vscode如何优雅的拥抱eslint

    https://www.toutiao.com/a6826129210260587019/?tt_from=weixin&utm_campaign=client_share&wxsha ...