关注微信公众号:K哥爬虫,持续分享爬虫进阶、JS/安卓逆向等技术干货!

声明

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

逆向目标

  • 目标:智慧树扫码登录,接口使用了 WebSocket 通信协议
  • 主页:aHR0cHM6Ly9wYXNzcG9ydC56aGlodWlzaHUuY29tL2xvZ2luI3FyQ29kZUxvZ2lu

WebSocket 简介

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,WebSocket 使得客户端和服务器之间的数据交换变得更加简单。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket 协议简称为 WS 或者 WSS(WebSocket Secure),其发送请求的 URL 以 ws:// 或者 wss:// 开头,WSS 是 WS 的加密版本,类似于 HTTP 与 HTTPS。

WebSocket 协议的最大特点就是:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。与 HTTP 的对比如下图所示:

抓包分析

来到智慧树的扫码登录页面,抓包选中 WS,用来筛选 WebSocket 请求,如下图所示:

其中有一些比较特别的参数,是 HTTP/ HTTPS 请求中没有的:

  • Upgrade: websocket:表明这是 WebSocket 类型请求;
  • Sec-WebSocket-Version:告诉服务器所使用的 Websocket Draft(协议版本),必须是 13;
  • Sec-WebSocket-Extensions:协议扩展,某类协议可能支持多个扩展,通过它可以实现协议增强;
  • Sec-WebSocket-Key:是 WebSocket 客户端发送的一个 base64 编码的密文,是浏览器随机生成的,要求服务端必须返回一个对应加密的 Sec-WebSocket-Accept 应答,否则客户端会抛出 Error during WebSocket handshake 错误,并关闭连接。

我们先扫码登录一遍,再选择 Messages 选项卡,可以看到有一些数据交互,其中绿色的箭头是客户端发送给服务器的数据,红色箭头是服务器响应返回给客户端的数据,如下图所示:

我们观察一下整个交互过程,当我们打开二维码页面后,也就是二维码加载出来的同时,WebSocket 连接就建立了,每隔8秒左右,客户端就主动发送一串字符串,服务端也返回相同的字符串,只不过是字典格式,当我们扫码成功时,服务端就返回扫码成功的信息,当我们点击登陆时,客户端又会返回扫码结果,如果成功,就有一个一次性密码 oncePassword 和一个 uuid,这两个参数肯定在后续的请求中会用到的。如果长时间不扫码的话,过段时间就会返回二维码已失效的信息,每隔8秒发送一次消息,正是为了保持连接以及获取二维码状态消息。

那么到这里就出现了两个问题:

  1. 在来回交互发送的那串字符串,是怎么得来的?

  2. 在 Python 中应该如何实现 WebSocket 请求?

  3. 如何实现客户端每隔 8 秒发送一次数据的同时,实时接收服务端的信息?(观察请求扫码结果实时返回的,所以不能每隔 8 秒才接收一次)

参数获取

首先解决第一个问题,客户端发送的那串字符串是怎么来的,这里寻找加密字符串的方式和 HTTP/HTTPS 请求是一样的,在本例中,我们可以直接搜索这个字符串,发现是通过一个接口传过来的,其中 img 就是二维码图片的 base64 值,qrToken 就是客户端发送的那串字符串,如下图所示:

这里需要注意的是,并不是所有的 WebSocket 请求都是如此的简单的,有的客户端发送的数据是 Binary Message(二进制数据)、或者更复杂的加密参数,直接搜索无法获取,针对这种情况,我们也有解决方法:

  1. 已知创建 WebSocket 对象的语句为:var Socket = new WebSocket(url, [protocol] );,所以我们可以搜索 new WebSocket 定位到建立请求的位置。

  2. 已知一个 WebSocket 对象有以下相关事件,我们可以搜索对应事件处理程序代码来定位:

事件 事件处理程序 描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发
  1. 已知一个 WebSocket 对象有以下相关方法,我们可以搜索对应方法来定位:
方法 描述
Socket.send() 使用连接发送数据
Socket.close() 关闭连接

Python 实现 WebSocket 请求

接着前面说,第二个问题,在 Python 中应该如何实现 WebSocket 请求?Python 库中用于连接 WebSocket 的有很多,比较常用、稳定的有 websocket-client(非异步)、websockets(异步)、aiowebsocket(异步)。在本案例中使用 websocket-client,这里还要注意第三个问题,对于客户端来说,要每隔 8 秒发送一次数据,对于服务端,我们需要实时接收服务端的信息,可以观察请求,扫码的结果是实时返回的,如果我们也每隔 8 秒才接收一次数据的话,有可能会丢失数据,而且也会使得整个程序的响应也不及时,效率变低。

在 websocket-client 官方文档中给我们提供了一个长连接的 demo,它实现了连续发送三次数据,并实时监听服务端返回的数据,其中的 websocket.enableTrace(True) 表示是否显示连接详细信息:

import websocket
import _thread
import time def on_message(ws, message):
print(message) def on_error(ws, error):
print(error) def on_close(ws, close_status_code, close_msg):
print("### closed ###") def on_open(ws):
def run(*args):
for i in range(3):
time.sleep(1)
ws.send("Hello %d" % i)
time.sleep(1)
ws.close()
print("thread terminating...")
_thread.start_new_thread(run, ()) if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.WebSocketApp(
"ws://echo.websocket.org/", on_open=on_open,
on_message=on_message, on_error=on_error, on_close=on_close
) ws.run_forever()

我们将其适当改造一下,客户端在 run 方法里,依然是每隔 8 秒发送一次 qr_token,实时接收服务端的消息,当“扫码成功”字样出现在消息里时,将得到的 oncePassworduuid 存起来,然后关闭连接,逻辑代码如下所示,后续只要将二维码的获取逻辑接入就行了。(已脱敏处理,不能直接运行)

import json
import time
import _thread
import websocket web_socket_url = "wss://appcomm-user.脱敏处理.com/app-commserv-user/websocket?qrToken=%s"
qr_token = "ca6e6cfb70de4f2f915b968aefcad404"
once_password = ""
uuid = "" def wss_on_message(ws, message):
print("=============== [message] ===============")
message = json.loads(message)
print(message)
if "扫码成功" in message["msg"]:
global once_password, uuid
once_password = message["oncePassword"]
uuid = message["uuid"]
ws.close() def wss_on_error(ws, error):
print("=============== [error] ===============")
print(error)
ws.close() def wss_on_close(ws, close_status_code, close_msg):
print("=============== [closed] ===============")
print(close_status_code)
print(close_msg) def wss_on_open(ws):
def run(*args):
while True:
ws.send(qr_token)
time.sleep(8)
_thread.start_new_thread(run, (qr_token,)) def wss():
# websocket.enableTrace(True) # 是否显示连接详细信息
ws = websocket.WebSocketApp(
web_socket_url % qr_token, on_open=wss_on_open,
on_message=wss_on_message, on_error=wss_on_error,
on_close=wss_on_close
)
ws.run_forever()

实现扫码登录

最重要的 WebSocket 请求部分已经解决了,扫码拿到 oncePassworduuid 后,后续的处理步骤就比较简单了,现在来理一下完整的步骤:

  1. 请求首页,第一次获取 cookie,包含:INGRESSCOOKIE、JSESSIONID、SERVERID、acw_tc;
  2. 请求获取二维码接口,得到二维码的 base64 值和 qrToken;
  3. 建立 WebSocket 连接,扫描二维码,获取一次性密码 oncePassword 和 uuid(好像没什么用);
  4. 请求一个登录接口,302 重定向,需要携带一次性密码,第二次获取 cookie,包含:CASLOGC、CASTGC,同时更新 SERVERID;
  5. 请求第 4 步 302 重定向地址,第三次获取 cookie,包含:SESSION;
  6. 携带完整 cookie,请求用户信息接口,获取真实用户名等信息。

实际上 WebSocket 连接结束后,有很多请求,看起来都比较可以,但是经过 K 哥测试,只有两个重定向比较有用,抓包如下:

完整代码

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

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

Python 登录代码

import time
import json
import base64
import _thread
import requests
import websocket
from PIL import Image web_socket_url = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
get_login_qr_img_url = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
login_url = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
user_info_url = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler" headers = {
"Host": "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler",
"Pragma": "no-cache",
"Referer": "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
} qr_token = ""
once_password = ""
uuid = ""
cookie = {} def get_cookies_first():
response = requests.get(url=login_url, headers=headers)
global cookie
cookie = response.cookies.get_dict() def get_login_qr_img():
response = requests.get(url=get_login_qr_img_url, headers=headers, cookies=cookie).json()
qr_img = response["img"]
global qr_token
qr_token = response["qrToken"]
with open('code.png', 'wb') as f:
f.write(base64.b64decode(qr_img))
image = Image.open('code.png')
image.show()
print("请扫描验证码! ") def wss_on_message(ws, message):
print("=============== [message] ===============")
message = json.loads(message)
print(message)
if "扫码成功" in message["msg"]:
global once_password, uuid
once_password = message["oncePassword"]
uuid = message["uuid"]
ws.close() def wss_on_error(ws, error):
print("=============== [error] ===============")
print(error)
ws.close() def wss_on_close(ws, close_status_code, close_msg):
print("=============== [closed] ===============")
print(close_status_code)
print(close_msg) def wss_on_open(ws):
def run(*args):
while True:
ws.send(qr_token)
time.sleep(8)
_thread.start_new_thread(run, (qr_token,)) def wss():
# websocket.enableTrace(True) # 是否显示连接详细信息
ws = websocket.WebSocketApp(
web_socket_url % qr_token, on_open=wss_on_open,
on_message=wss_on_message, on_error=wss_on_error,
on_close=wss_on_close
)
ws.run_forever() def get_cookie_second():
global cookie
params = {
"pwd": once_password,
"service": "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
}
headers["Host"] = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
headers["Referer"] = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
response = requests.get(url=login_url, params=params, headers=headers, cookies=cookie, allow_redirects=False)
cookie.update(response.cookies.get_dict())
location = response.headers.get("Location")
return location def get_cookie_third(location):
global cookie
headers["Host"] = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
headers["Referer"] = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
response = requests.get(url=location, headers=headers, cookies=cookie, allow_redirects=False)
cookie.update(response.cookies.get_dict())
location = response.headers.get("Location")
return location def get_login_user_info():
headers["Host"] = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
headers["Origin"] = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
headers["Referer"] = "脱敏处理,完整代码关注 GitHub:https://github.com/kgepachong/crawler"
params = {"time": str(int(time.time() * 1000))}
response = requests.get(url=user_info_url, headers=headers, cookies=cookie, params=params)
print(response.text) def main():
# 第一次获取 cookie,包含 INGRESSCOOKIE、JSESSIONID、SERVERID、acw_tc
get_cookies_first()
# 获取二维码
get_login_qr_img()
# websocket 扫码登录,返回一次性密码
wss()
# 第二次获取 cookie,更新 SERVERID、获取 CASLOGC、CASTGC
location1 = get_cookie_second()
# 第三次获取 cookie,获取 SESSION
get_cookie_third(location1)
# 获取登录用户信息
get_login_user_info() if __name__ == '__main__':
main()

【JS 逆向百例】WebSocket 协议爬虫,智慧树扫码登录案例分析的更多相关文章

  1. HTTP协议图--HTTP 响应状态码(重点分析)

    1. 状态码概述 HTTP 状态码负责表示客户端 HTTP 请求的返回结果.标记服务器端的处理是否正常.通知出现的错误等工作. HTTP 状态码如 200 OK ,以 3 位数字和原因短语组成.数字中 ...

  2. node.js Websocket实现扫码二维码登录---GoEasy

    最近在做一个扫码登录功能,为此我还在网上搜了一下关于微信的扫描登录的实现方式.当这个功能完成了后,我决定将整个实现思路整理出来,方便自己以后查看也方便其他有类似需求的程序猿些. 要实现扫码登录我们需要 ...

  3. 淘宝的sign参数js逆向

    前言:现在网站都有很强的反爬机制,都是非常常见的是用js前端加密参数,所以不得不去分析和逆向js混淆后的代码 一. 打开天猫或淘宝,shift+ctrl+F12全局搜索sign参数. 这里发现很多地方 ...

  4. 利用websocket实现手机扫码登陆后,同步登陆信息到web端页面

    新手必看 广播系统 事件系统 准备工作 初始化项目 引入 laravel-websockets 软件包 启动 websocket 监听 主要流程 创建两个页面 建立 socket 连接 手机端扫码登陆 ...

  5. Win10环境前后端分离项目基于Vue.js+Django+Python3实现微信(wechat)扫码支付流程(2021年最新攻略)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_182 之前的一篇文章:mpvue1.0+python3.7+Django2.0.4实现微信小程序的支付功能,主要介绍了微信小程序内 ...

  6. C(++) Websocket实现扫码二维码登录---GoEasy

    最近在做一个扫码登录功能,为此我还在网上搜了一下关于微信的扫描登录的实现方式.当这个功能完成了后,我决定将整个实现思路整理出来,方便自己以后查看也方便其他有类似需求的程序猿些. 要实现扫码登录我们需要 ...

  7. 网络爬虫之记一次js逆向解密经历

    1 引言 数月前写过某网站(请原谅我的掩耳盗铃)的爬虫,这两天需要重新采集一次,用的是scrapy-redis框架,本以为二次爬取可以轻松完成的,可没想到爬虫启动没几秒,出现了大堆的重试提示,心里顿时 ...

  8. 爬虫05 /js加密/js逆向、常用抓包工具、移动端数据爬取

    爬虫05 /js加密/js逆向.常用抓包工具.移动端数据爬取 目录 爬虫05 /js加密/js逆向.常用抓包工具.移动端数据爬取 1. js加密.js逆向:案例1 2. js加密.js逆向:案例2 3 ...

  9. 这个爬虫JS逆向加密任务,你还不来试试?逆向入门级,适合一定爬虫基础的人

    友情提示:在博客园更新比较慢,有兴趣的关注知识图谱与大数据公众号吧.这次选择苏宁易购登录密码加密,如能调试出来代表你具备了一定的JS逆向能力,初学者建议跟着内容调试一波,尽量独自将JS代码抠出来,实在 ...

  10. 我去!爬虫遇到JS逆向AES加密反爬,哭了

    今天准备爬取网页时,遇到『JS逆向AES加密』反爬.比如这样的: 在发送请求获取数据时,需要用到参数params和encSecKey,但是这两个参数经过JS逆向AES加密而来. 既然遇到了这个情况,那 ...

随机推荐

  1. VRAR产业峰会暨第二届华为VR开发应用大赛颁奖典礼在和平区成功举办!

    摘要:近日,由沈阳市和平区人民政府和华为技术有限公司主办的VRAR产业峰会暨第二届华为VR开发应用大赛颁奖典礼在沈阳君悦酒店圆满举办. 12月29日,由沈阳市和平区人民政府和华为技术有限公司主办的VR ...

  2. 无法访问Docker 里的 mysql, redis

    [root@centos-linux jimmy]# firewall-cmd --state not running [root@centos-linux jimmy]# sysctl net.ip ...

  3. WCF 动态调用 动态代理

    关键词:WCF动态调用.动态调用WCF.WCF使用动态代理精简代码架构.使用反射执行WCF接口 代码地址: https://gitee.com/s0611163/DynamicWCF https:// ...

  4. Android 3分钟带你入门开发测试

    作者:Zhu Yifei 作为一名合格的开发人员,基本的开发测试能力必不可少,开发测试分单元测试和UI测试,通过开发测试可以减少开发人员自测时间,提升开发质量.本篇文章可以帮助初级开发人员快速了解开发 ...

  5. ifelse优化方案

    优化 if else:https://blog.csdn.net/FBB360JAVA/article/details/103832405 利用java8特性优化: https://blog.csdn ...

  6. kafka集群五、__consumer_offsets副本数修改

    系列导航 一.kafka搭建-单机版 二.kafka搭建-集群搭建 三.kafka集群增加密码验证 四.kafka集群权限增加ACL 五.kafka集群__consumer_offsets副本数修改 ...

  7. Eureka相关相关接口和代码位置

    1.服务注册:com.netflix.eureka.registry.AbstractInstanceRegistry#register(InstanceInfo registrant, int le ...

  8. ACP 知识点总结

    记录下学习ACP过程不断遇到的且需要记录的知识点: 在阿里云专有网络VPC创建之后,路由器也是随着VPC一起自动创建,所以不需要手动创建,这个时候需要继续创建交换机才能在交换机种创建其他云产品. 7层 ...

  9. nginx 工作原理及特点

    本文为博主原创,未经允许不得转载: nginx 简介:是一个高性能 HTTP 和 反向代理 服务器. Nginx 特点是占有内存少,并发能力强,事实上 Nginx 的并发能力确实在同类型的网页服务器中 ...

  10. 【RTOS】基于RTOS的嵌入式系统看门狗策略

    RTOS - high integrity systems 看门狗策略 Watchdog Strategies for RTOS enabled embedded systems 介绍 看门狗定时器就 ...