某航参数逆向及设备指纹分析

article/2025/6/20 21:02:31

文章目录

  • 1. 写在前面
  • 2. 接口分析
  • 3. 加密分析
  • 4. 算法还原
  • 5. 设备指纹风控分析与绕过

【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章

作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!

1. 写在前面

  今天端午节,祝大家端午安康!最近事情比较多,所以又有很长一段时间没有写文章了(差点忘了这个好习惯~~)。刚好今天过节有时间,把前些天看的一些小案例分享一下。这期分析的是某航空网站的加密参数跟它的设备指纹风控,通过非登录游客身份搜索航班机票查询然后解决风控标记弹行为验证(比较简单适合新手研究


分析网站

aHR0cHM6Ly93d3cuYWlyY2hpbmEuY29tLmNuL2dhdGV3YXkvYXBpL2ZsaWdodC9saXN0

2. 接口分析

随便在搜索入口查询一趟航班信息,可以看到发包请求参数params一段密文,如下所示:

在这里插入图片描述

除了请求参数加密外,请求头也有一个x-device-token参数疑似动态生成的(但不是直接网站的JS代码层面生成的)。如下所示:

在这里插入图片描述

像这种Token请求头的参数一般情况下我们可以先不去管它,先去分析请求参数的加密。头部一般非签名的核心参数,你固定测试一次两次是可以重放的。但是请求参数的加密对什么加密了、明文是什么以及后续能够模拟伪造出请求来测试是必须

初看这个Token的时候我感觉在哪里见过(比较熟悉)。有经验的可能会猜测它是由某个接口动态请求之后服务端下发的(在有效的时间范围可以有效固定去使用)做过某东逆向分析的可以发现它的这个参数开头tak01...跟它们官方的那个设备指纹高度相似(后续验证发现就是用的某东的设备指纹风控)

3. 加密分析

开始定位找到发包加密的位置,这里可以直接通过XHR断点去跟栈就能够溯源到整个发包跟调用加密的位置,如下所示:

在这里插入图片描述

可以看到o.P就是加密方法,直接跳转到对应的JS代码,如下:
在这里插入图片描述

var c = function(e) {try {var t = encodeURIComponent(JSON.stringify(e));return o.sm2.doEncrypt(t, "04064c2a3bcafba2c1ca4f5fb8ecd876b23d70fc4479b78f3c8066c02a8c17749458bca86361bc563d2501b61e2ac93a676a1305893aafcc6be2ea48ecb048672e", a.yV)} catch (n) {return console.log(n),""}}

看上面加密代码的入口,比较明显的可以看到使用了疑似sm2国密算法。像一般我们猜测到了加密算法大致看看是否标准的就可以直接使用自己擅长的语言导个包去进行还原。后面那一串的话看起来就是它的密钥,sm2一般长度256 bit,开头04表示非压缩。后面那64字节(128个十六进制字符),大致如下得出:

04 || <X (64 hex)> || <Y (64 hex)> = 1 + 64 + 64 = 129 hex digits

略微看一下它的那个JS实现,是标准一个sm2。然后要还原这个加密算法的方式可以直接把doEncrypt的JS代码扣出来(webpack)然后导出模块再调用即可。还有就是确定完整个加密算法是标准的还是变异的或者魔改的后用其他语言实现(代码量会大大降低

4. 算法还原

既然是分享,这里扣webpack跟使用纯算还原的方式都说一下。首先跳转到sm2.doEncrypt代码处,开始扣一些JS。扣代码的也是讲究精扣粗扣

这里精扣的话就几百行涉及加密的这几段JS代码就行。新手你全部粗扣下来也无所谓(4W多行-不用在意这些细节-能用就行

在这里插入图片描述

然后再把webpack内部的模块加载函数导出来,模块加载器的结构一般如下所示:

function enc(n) {var f = t[n];if (void 0 !== f) return f.exports;...e[n].call(r.exports, r, r.exports,enc)...return r.exports
}

t[n]: 缓存已加载模块
e[n]: 模块定义函数对象(模块id -> 函数)类似Dict
r.exports: 模块的导出结果

之后找到SM2加密方法的所在模块ID(70686),如下所示:

在这里插入图片描述

那么这个时候我们就可以在JS中直接导出扣下来的webpack模块,重新封装一下加密方法。代码实现如下:

function encrypt(e) {var o = sm2(70686), // 加密模块try {var t = encodeURIComponent(JSON.stringify(e));return 0.sm2.doEncrypt(t, "04064c2a3bcafba2c1ca4f5fb8ecd876b23d70fc4479b78f3c8066c02a8c17749458bca86361bc563d2501b61e2ac93a676a1305893aafcc6be2ea48ecb048672e", 1);} catch (n) {console.log(n);return "";}
}

除了上面说到的扣代码,再就是直接使用导包的方式来还原(前提上面的分析我们已经知道了它是一个标准的加密算法),这里我们直接可以使用NodeJS导出加密模块的方式实现,实现代码如下:

const smCrypto = require('sm-crypto');
const { sm2 } = smCrypto;function encrypt(e) {try {const t = encodeURIComponent(JSON.stringify(e));// 密钥const publicKey = "04064c2a3bcafba2c1ca4f5fb8ecd876b23d70fc4479b78f3c8066c02a8c17749458bca86361bc563d2501b61e2ac93a676a1305893aafcc6be2ea48ecb048672e";return sm2.doEncrypt(t, publicKey, 1);} catch (error) {console.error("加密失败:", error);return "";}
}

这个密钥好像是定期会更新的,然后接下来我们验证一下已经通过逆向分析还原出来的加密算法,对接到单次的请求中是否可以正常拿到接口的响应数据,代码实现如下:

import execjs
import requests
from loguru import logger
from getuseragent import UserAgentdef encrypt_request_data(data):with open("sm2.js", encoding='utf-8') as f:ctx = execjs.compile(f.read()) res = ctx.call("encrypt",data) return resdef send_request(encrypted_data):random_ua_list = ["chrome", "firefox", "safari"]ua = UserAgent(random.choice(random_ua_list))useragent = ua.Random()url = "https://www.xxx.com.cn/gateway/api/flight/list"headers = headers = {"Content-Type": "application/json","Accept": "application/json, text/plain, */*","Sec-Fetch-Site": "same-origin","Accept-Language": "zh-CN,zh-Hans;q=0.9","Accept-Encoding": "gzip, deflate, br","Sec-Fetch-Mode": "cors","Host": "www.xxx.com.cn","Origin": "https://www.xxx.com.cn","User-Agent": useragent,"Referer": "https://www.xxx.com.cn/flight/oneway/pek-ctu/2025-06-04","Content-Length": "846","Connection": "keep-alive","Sec-Fetch-Dest": "empty","X-Locale": "zh-CN","X-Device-Token": "" # 自行获取}data = {'params': encrypted_data,'RequestParameterEncryptionIdentificationBit': True}data = json.dumps(data, separators=(',', ':'))response = requests.post(url, headers=headers, data=data)return response.json()if __name__ == "__main__":# 请求明文参数params = {"Trip": [{"Date": "2025-06-05", "Dep": "PEK", "Arrival": "CTU"}],"Passenger": {"adult": 1, "child": 0, "baby": 0},"notchType": None,"aimPrice": None,"RequestParameterSecurityIdentificationBit": True}encrypted_data = encrypt_request_data(params)logger.info(f'加密参数: {encrypted_data}')result = send_request(encrypted_data)logger.info(f'查询数据: {result}')

运行一下上面封装好的Python请求代码,可以看到是没有问题的。能够正常拿到数据,如下所示:

在这里插入图片描述

5. 设备指纹风控分析与绕过

上面测试的是非登录状态下的情况,其实登不登录都无所谓。它本身是有一个风控参的,整个爬虫的风控也都基于它来开展。就是前面我一开始分析提到的X-Device-Token

这个参数就是设备指纹的信息,用来防护恶意请求的。一个X-Device-Token可以请求的次数在10次以内,浏览器的环境就会被标记再配合风控判定推送行为验证码(点选),如下所示:

在这里插入图片描述

在这里插入图片描述

所以如果需要多次持续的请求查询一些数据的话,要么就是直接再逆向分析点选的协议,拿到验证的Token提交也是可以的。再就是解决这个参数绕过点选。继续进一步分析这个参数发现是使用的某东设备指纹风控(包括这个点选也是某东云的验证码系统)如下所示:

在这里插入图片描述

这个参数收集了【浏览器的指纹CanvasWebGL分辨率、Storage...】以及一些初始化加载行为

在这里插入图片描述

针对这个X-Device-Token参数的对抗方案其实也是有很多种的,第一个就是自己定制魔改,第二个就是使用开源好已经从Chromium源码层修改浏览器指纹信息的方案(外面的指纹浏览器也是可以的

这里我们就简单的用魔改过的方案自己在本地搭一个服务,然后通过注入JS的方式刷票务的接口Hook到最新的这个参数,如下所示:

在这里插入图片描述
经过多次测试,一个设备指纹的新参数在请求风控出现行为验证码的时候,调用测试搭建的刷新设备指纹的服务获取新的X-Device-Token都是可以立即绕过行为验证正常再次请求数据的(因为我们使用的魔改方案、它会认为我们是一个新的设备及浏览器),如下所示:

在这里插入图片描述

它这个不管是自动化浏览器的方案还是接口协议的方案都会遇到行为验证码的风控(它的行为验证不像其他平台是必须要过的、可以规避掉)。然后像这种非登录状态或者游客模式可以访问的爬虫方案,厂商针对的风控一般只会从IP设备指纹开展

最后多说一句(爬虫目前想要持续抓取一些头部平台的数据、除了很普遍的很基本的接口验签逆向外。需要解决的就是风控,风控涉及的细分领域有很多。而其中最常遇见的就是设备指纹风控跟行为验证风控,那么对抗是对抗的什么呢?就是这一系列的风控防护,所以定制或魔改指纹、改机这些都是需要涉及的。以前可能只需要考虑策略,但那已经是S3赛季了...


http://www.hkcw.cn/article/jmBSBRoIrk.shtml

相关文章

电子电器架构 --- OTA测试用例分析(上)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

方案精读:42页华为企业组织活力设计方案【附全文阅读】

该文档聚焦华为企业组织活力设计,核心内容为:在非线性时代,方向未必完全正确时,组织活力是企业成功关键,可通过创新与柔性激发。以熵增、熵减为理论基础,华为活力引擎模型包含宏观(厚积薄发、开放合作对抗熵增)与微观(人力资源管理对抗个人惰怠)。 实践上从三层面激活…

双目相机深度的误差分析(基线长度和相机焦距的选择)

全文基于针孔模型和基线水平放置来讨论 影响双目计算深度的因素&#xff1a; 1、基线长度&#xff1a;两台相机光心之间距离2、相机焦距&#xff08;像素&#xff09;&#xff1a; f x f_x fx​&#xff08;或 f y f_y fy​&#xff09;为焦距 f f f和一个缩放比例的乘积。在…

Namespace 命名空间的使用

名字空间&#xff1a;划分更多的逻辑空间&#xff0c;有效避免名字冲突的问题 1.什么是命名空间 名字命名空间 namespace 名字空间名 {...} // 名字空间 n1 域 namespace n1 {// 全局变量int g_money 0;void save(int money){g_money money;}void pay(int money){g_money - m…

力扣热题100之翻转二叉树

题目 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 代码 方法一&#xff1a;递归 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # …

simulink mask的使用技巧

1.mask界面布局 1.1如何调整控件的位置和控件大小&#xff1f; 反正2020a是调不了&#xff0c; 找了好久&#xff0c;只能是调布局&#xff0c;例如你要调成下面这样&#xff1a; 第一个控件的iTem location属性选择New row 后面跟着的几个和第一个同一行的空间属性选择Cu…

第12讲、Odoo 18 权限控制机制详解

目录 引言权限机制概述权限组&#xff08;Groups&#xff09;访问控制列表&#xff08;ACL&#xff09;记录规则&#xff08;Record Rules&#xff09;字段级权限控制按钮级权限控制菜单级权限控制综合案例&#xff1a;多层级权限控制最佳实践与注意事项总结 引言 Odoo 18 提…

AIGC学习笔记(8)——AI大模型开发工程师

文章目录 AI大模型开发工程师007 LangChain之Model IO模块1 Model IO核心概念2 Model IO代码实战什么是LCEL&#xff1f;ModelModel的分类LLMsChatModel PromptPrompt templatesExample selectorsOutput parsers AI大模型开发工程师 007 LangChain之Model IO模块 1 Model IO核…

Java 文件操作 和 IO(5)-- 综合案例练习 -- 示例一

题目描述&#xff1a;扫描指定目录&#xff0c;并找到名称中包含指定字符的所有普通文件&#xff08;不包含目录&#xff09;&#xff0c;并且后续询问用户是否要删除该文件 文章目录 题目描述&#xff1a;扫描指定目录&#xff0c;并找到名称中包含指定字符的所有普通文件&…

Leetcode 465. 最优账单平衡

1.题目基本信息 1.1.题目描述 给你一个表示交易的数组 transactions &#xff0c;其中 transactions[i] [fromi, toi, amounti] 表示 ID fromi 的人给 ID toi 的人共计 amounti $ 。 请你计算并返回还清所有债务的最小交易笔数。 1.2.题目地址 https://leetcode.cn/pro…

【沉浸式求职学习day51】【发送邮件】【javaweb结尾】

沉浸式求职学习 邮件发送原理及实现1.概述2.简单邮件3.复杂邮件 网站注册发送邮件功能实现 邮件发送原理及实现 1.概述 传输协议 SMTP协议 发送邮件&#xff1a; 我们通常把处理用户smtp请求(邮件发送请求)的服务器称之为SMTP服务器(邮件发送服务器)。POP3协议 接收邮件&#…

标题:2025海外短剧爆发年:APP+H5双端系统开发,解锁全球流量与变现新大陆

描述&#xff1a; 2025年出海新风口&#xff01;深度解析海外短剧系统开发核心&#xff08;APPH5双端&#xff09;&#xff0c;揭秘高效开发策略与商业化路径&#xff0c;助您抢占万亿美元市场&#xff01; 全球娱乐消费模式正在剧变。2025年&#xff0c;海外短剧市场已从蓝海…

uni-app学习笔记十六-vue3页面生命周期(三)

uni-app官方文档页面生命周期部分位于页面 | uni-app官网。 本篇再介绍2个生命周期 1.onUnload&#xff1a;用于监听页面卸载。 当页面被关闭时&#xff0c;即页面的缓存被清掉时触发加载onUnload函数。 例如:在demo6页面点击跳转到demo4&#xff0c;在demo4页面回退不了到d…

钉钉红包性能优化之路

一、业务背景 请客红包、小礼物作为饿了么自研的业务产品&#xff0c;在钉钉的一方化入口中常驻&#xff0c;作为高UV、PV的toB产品&#xff0c;面对不同设备环境的用户&#xff0c;经常会偶尔得到一些用户反馈&#xff0c;如【页面白屏太久了】、【卡住了】等等&#xff0c;本…

鲲鹏Arm+麒麟V10 K8s 离线部署教程

针对鲲鹏 CPU 麒麟 V10 的离线环境&#xff0c;手把手教你从环境准备到应用上线&#xff0c;所有依赖包提前打包好&#xff0c;步骤写成傻瓜式操作指南。 一、环境规划# 准备至少两台机器。 架构OS作用Arm64任意&#xff0c;Mac 也可以下载离线包Arm64麒麟 V10单机部署 K8s…

Redis主从复制详解

概述 Redis 的主从复制&#xff08;Master-Slave Replication&#xff09;是实现数据备份、读写分离和水平扩展的核心机制之一。通过主从复制&#xff0c;一个主节点&#xff08;Master&#xff09;可以将数据同步到多个从节点&#xff08;Slave&#xff09;&#xff0c;从节点…

16.进程间通信(二)

一、命名管道 1.概念 匿名管道解决了具有血缘关系的进程之间的通信&#xff0c;如果两个进程毫不相干&#xff0c;如何进行通信呢&#xff1f;通过文件&#xff0c;管道文件。 对于两个不同进程&#xff0c;打开同一路径下的同一文件&#xff0c;inode和文件内核缓冲区不会加载…

优化的两极:凸优化与非凸优化的理论、应用与挑战

在机器学习、工程设计、经济决策等众多领域&#xff0c;优化问题无处不在。而在优化理论的世界里&#xff0c;凸优化与非凸优化如同两个截然不同的 “王国”&#xff0c;各自有着独特的规则、挑战和应用场景。今天&#xff0c;就让我们深入探索这两个优化领域的核心差异、算法特…

day15 leetcode-hot100-29(链表8)

19. 删除链表的倒数第 N 个结点 - 力扣&#xff08;LeetCode&#xff09; 1.暴力法 思路 &#xff08;1&#xff09;先获取链表的长度L &#xff08;2&#xff09;然后再次遍历链表到L-n的位置&#xff0c;直接让该指针的节点指向下下一个即可。 2.哈希表 思路 &#xff0…

rtpinsertsound:语音注入攻击!全参数详细教程!Kali Linux教程!

简介 2006年8月至9月期间&#xff0c;我们创建了一个用于将音频插入指定音频&#xff08;即RTP&#xff09;流的工具。该工具名为rtpinsertsound。 该工具已在Linux Red Hat Fedora Core 4平台&#xff08;奔腾IV&#xff0c;2.5 GHz&#xff09;上进行了测试&#xff0c;但预…