
大概两年前我接手一个Web端安全评估的项目重点之一就是某OTA平台的风控token。第一次在接口响应里看到code: 1002, msg: token invalid的时候说实话我愣了一下。网上的说法五花八门有人说1002是token过期有人说是签名被篡改有人说是设备环境异常。这些说法其实都沾点边但都不完整。后来我把整个分析过程跑完回头再看这个1002才意识到一个关键问题对一个风控token做算法分析最重要的不是猜它用什么哈希而是先搞清楚它在请求链路里到底扮演什么角色。这篇文章就以携程token1002 算法分析为切入点聊一聊我是怎么拆解这类前端风控token的。不会给你一个直接复制就能跑的脚本——那既不负责也不安全——而是分享一套可以复用到其他平台的分析方法论怎么确认1002的真实身份怎么拆token字段怎么推导签名算法怎么验证你还原的算法是对的。适合正在做安全测试、前端功能开发或者对风控对抗机制感兴趣的工程师参考。1. 先搞清楚 token1002 到底是什么1.1 它可能是错误码也可能是参数标识我见过太多人一上来就奔着算法去了结果连目标都没搞清。1002这个数字在携程这类平台的请求体系里至少有两种身份。第一种是错误码。接口返回的JSON里出现code: 1002msg里面写着token相关文案这代表token已经生成并且提交上去了但服务端校验没通过。这种情况下算法本身可能没毛病问题出在校验环节时间窗口超了、参数被改过、或者设备和cookie对不上。第二种是参数标识。某些内部接口会把场景ID或版本号塞进token相关参数里1002可能是某个风控场景编号比如搜索场景或下单场景。判断方法很简单打开DevTools的Network面板找到报错的那条请求分别看请求参数和响应体。如果请求参数里有token相关字段而响应体里返回1002那它多半是错误码如果token字段本身带着1002这种可辨识的子串那更可能是场景标识。这一步不做后面全是空中楼阁。1.2 风控token在请求链路中的位置搞清楚1002的身份之后第二步是定位token在整个请求链路里的位置。前端风控token的生成时机通常有两种页面初始化就生成或者某个关键事件触发后才生成。搜索框聚焦、滑块校验、提交订单都是常见的触发点。以OTA平台的典型链路来说大概是这样一个流程页面加载时前端SDK先采集设备信息包括UA、屏幕分辨率、Canvas指纹、时区、语言环境然后SDK会在后台持续采集用户行为比如鼠标移动轨迹、点击坐标、停留时长等到要发核心请求时把这些信息连同时间戳一起交给一个token生成函数产出最终token塞进请求头或请求体。服务端拿到token后先做签名校验确认参数没被篡改再看时间戳确认请求在有效窗口内最后看设备指纹和风控策略决定放行还是返回1002。这个链路理解透了你就知道分析的重点应该放在哪个环节。如果1002出现在所有请求上问题大概率在基础指纹采集如果只出现在特定接口那问题就在那个接口的专用逻辑里。提示分析任何站点前先确认自己是否有授权。合规是底线也是保护自己的方式。下面的方法都默认你在做授权范围内的安全评估或功能调试。1.3 分析前的材料准备动手之前把工具和材料备齐能省下一半的返工时间。浏览器DevTools抓请求快照、打断点、搜索JS源码都用它。抓包工具Charles或Fiddler都行用来做更细粒度的请求对比。Node.js环境后期要把前端JS函数拉出来单独跑没有Node寸步难行。Python环境写脚本做样本对比、字段统计、自动化验证。材料方面至少记录三样东西第一报错发生时的完整请求URL和请求头注意里面有没有token相关字段第二响应体原文1002的具体msg文案越长越好第三时间点精确到秒因为后面做时间窗口分析时要用。很多人在这一步随手截个图就完事了我强烈建议你保存成文本文件按时间命名。后面做样本对比时一份干净的请求日志比任何逆向工具都值钱。2. 拆解token的组成先看再猜后验2.1 第一梯队肉眼能识别的字段拿到真实token样本后先别急着上工具用肉眼做一轮初筛。这一步能过滤掉一半以上的无意义猜测。先看长度和字符集。如果token是30位左右的十六进制字符串里面只有0-9a-f大概率是某种哈希值如果出现/、、这类字符大概率是Base64编码过的数据如果能看到点号、下划线、横杠之类的分隔符那基本是多个字段的拼接体分隔符就是天然的分段边界。举个例子我教学时经常用这样一个构造样本注意这不是真实平台的token只是用来演示特征a3f8c1e2.1700000000.9b2f4d17e8c3a1b6.5f7a2c9d四段之间用点号隔开。第一段a3f8c1e2是8位hex短小可能是一个固定算法的前8位或设备指纹的摘要前缀第二段1700000000是标准的10位Unix时间戳肉眼就能认出来第三段9b2f4d17e8c3a1b6是16位hex像某个哈希值第四段5f7a2c9d又是8位hex。有了这个初步判断后面的分析就有了方向时间戳部分可以直接验证哈希部分去定位算法短前缀部分去追踪设备指纹。2.2 第二梯队需要还原的字段肉眼能看出来的只是皮毛。风控token里真正有信息量的是那些看起来像乱码的部分。它们通常承担三种职责。第一种是签名。签名的作用是防篡改服务端会用同样的算法对请求参数重新计算一遍比对不上就返回1002。签名的特征是长度固定、对输入极其敏感你改一个字节输出会变得面目全非。第二种是设备指纹。这类字段通常是把UA、Canvas指纹、屏幕参数等信息经过哈希或编码后的结果短时间内在同一设备上保持不变换设备或换浏览器就会变。第三种是行为特征鼠标轨迹、点击热区、键盘输入节奏都被编码成紧凑的字节序列这个最难还原因为它依赖一套完整的行为采集SDK。这三类字段往往混在同一个token里需要通过控制变量法把它们一个个剥离开。2.3 用分组对比法确定字段边界控制变量法是拆字段最靠谱的手段比读几千行混淆代码高效得多。操作流程是这样的固定账号、固定设备、固定浏览器在同一个页面URL下连续发起十次请求记录每次的token。然后只改一个变量比如换一个网络环境再发十次再换一个浏览器指纹再发十次。把这三组样本按位置对齐用表格列出来样本组第1段第2段(时间)第3段第4段组A-1a3f8c1e217000000009b2f4d17e8c3a1b65f7a2c9d组A-2a3f8c1e217000000019b2f4d17e8c3a1b65f7a2c9d组B-1b7c29d1817000000106e1a90b3c4d5e7f85f7a2c9d组C-1f3a5e18217000000208d2c4b6a1f9e7d3c5f7a2c9d看这张表规律很清楚第1段在换设备后变了说明和指纹相关第2段每次都按时间递增是时间戳没跑第3段跟着变既受时间影响也受指纹影响极大概率是参与签名的哈希第4段雷打不动可能是固定盐值或其他常量。这样一轮下来字段边界和职责基本就清晰了。3. 从已知到未知签名算法的推导路径3.1 参数排序与拼接服务端是怎么验签的签名是整个token的核心也是算法分析这四个字最集中的体现。要推导签名算法得先理解服务端的验签逻辑。大多数验签流程都遵循一个朴素原则将要校验的参数按固定顺序拼接成字符串串上盐值做一次哈希得到签名再和客户端传上来的签名比对。这个固定顺序通常是参数名的字典序也就是按key排序后逐个拼接。我用Python写一个最典型的示意结构仅演示通用逻辑不是任何平台的真实代码import hashlib import time def generate_sign(params: dict, salt: str) - str: # 按key排序并拼接 sorted_keys sorted(params.keys()) raw .join(f{k}{params[k]} for k in sorted_keys) raw raw salt return hashlib.md5(raw.encode()).hexdigest()理解这个结构之后你在前端JS里找对应逻辑时就有了搜索方向找排序函数、找字符串拼接模板、找hash调用点。如果平台用的是HMAC而不是普通哈希结构会略有不同通常是先构造好消息串再以某个密钥做HMAC计算但分析思路完全一致。3.2 哈希算法的指纹识别怎么判断对方用的到底是MD5还是SHA系列三个特征就够了。第一是输出长度。32位hex基本就是MD5或其变种40位hex是SHA164位hex是SHA256。第二是字符集。纯hex输出说明经过了hexdigest处理如果是Base64格式的定长字符串比如28个字符的Base64很有可能对应20字节的SHA1或HMAC-SHA1输出。第三是输入构造。如果你已经能控制输入的部分参数可以通过稍微改动输入观察输出的雪崩效应强度侧面推断哈希函数的类型。这里有个容易踩的坑很多平台不会直接用标准哈希而是先对字符串做一次性编码比如以Base64作为中间层再做哈希或者在哈希之前先压缩、反转、截断。所以我一直强调哈希算法指纹判断只是一个方向最终确认还得靠自己拼串算一遍看能不能对得上。3.3 盐值从哪里来三种常见来源签名里的盐值是整个算法还原里最容易卡住的一环。它一般有三个来源。第一种是硬编码在前端JS里。这种情况下盐值就在某个JS文件的字符串常量里格式化代码后直接搜索可疑字符串就能找到。特点是对所有用户都一样。第二种是服务端动态下发。页面初始化时会额外发一个接口返回一个盐值或密钥前端用它参与签名。这种设计比硬编码安全一个档次因为盐值可以定期轮换。第三种是绑定设备指纹。盐值本身是设备指纹的某一个派生值每台设备不同这会让拿到代码的人也未必能算出有效的token。判断盐值来源有点小技巧在控制变量法那一步如果第4段固定不变的部分在不同账号、不同设备上都一样那它很可能是硬编码如果换个会话就变那基本是动态下发你得把获取盐值的那个接口也纳入分析范围。4. 还原token生成的四个实操阶段4.1 定位调用链从触发点到生成函数前面做的都是静态分析这一步开始进入动态调试。目标是找到token生成函数在JS代码里的确切位置。我最常用的方法是调用栈回溯。在DevTools里找到发请求的XHR/fetch调用在Network面板右键那条请求选择Break on fetch/XHR然后刷新页面或重新触发请求。请求发出去之前会断下来这时候看调用栈一层层往下点通常就能看到一个函数的名字比较可疑比如generateToken、buildSign、getSecurityToken这类的。如果断点没断到就用关键词搜索。把JS源码格式化之后在Sources面板里全局搜索token、sign、1002这些关键词定位所有出现的位置再逐个跳到上下文去看。压缩混淆过的代码可读性很差但变量名再怎么混淆字符串常量往往变不了原始字段名还是会出现在字符串里。4.2 断点与Hook让算法自己开口说话找到生成函数后直接读代码有时候很难受因为一屏放不下逻辑跳来跳去。更高效的办法是Hook关键的原生函数让算法在我们眼皮底下运行。以JavaScript为例Date.now()和Math.random()是token生成里最常被用到的两个函数。我在调试环境下会先挂上这样的Hookconst _now Date.now; Date.now function () { const ts _now(); console.log([Date.now], ts); return ts; }; const _random Math.random; Math.random function () { const r _random(); console.log([Math.random], r); return r; };这样token每生成一次时间戳和随机数就会原样打印在控制台里配合你抓包拿到的token样本能迅速确认哪一段对应时间戳、哪一段是随机数参与的结果。同理你还可以HookJSON.stringify、crypto.subtle.digest、window.btoa观察字符串拼接前的对象结构。提示Hook代码要放在页面加载之前才有效。用DevTools的Overrides功能或者在Sources面板里用Script snippet在页面初始化阶段注入都是可行的办法。4.3 最小复现环境与补环境前端JS函数通常依赖浏览器环境里的window、document、navigator、localStorage等一系列对象。要想在Node.js里单独运行这个函数就得做环境补齐。我的建议是先拉到Node里跑一下看它报什么错缺什么补什么。报window is not defined就定义一个global.window报navigator.userAgent不存在就给navigator补上UA字段。不用追求100%还原浏览器只需要补到函数能正常走完就行。这一步能大幅提升分析效率因为你可以像调普通Node脚本一样反复执行token生成函数批量制造样本。一个典型的补环境脚本长这样global.window global; global.navigator { userAgent: Mozilla/5.0 ..., language: zh-CN, platform: Win32 }; global.document { cookie: , referrer: }; const { generateToken } require(./extracted_token.js);补环境的技术含量不高但很耗耐心。我的经验是先从报错最密集的地方补起比如canvas相关操作通常需要mock一个HTMLCanvasElement.prototype.getContext返回固定数据否则在不同环境下生成的指纹会不一致。4.4 验证还原结果时间偏移与参数篡改算法还原到什么程度算成功标准只有一个你独立生成的token能在授权范围内通过服务端的基本校验。验证方法有三类。第一类是时间偏移测试。把系统时间往前调30秒、1分钟、5分钟分别生成token并发送请求观察哪个时间点开始报1002从而推断token的有效窗口。第二类是参数篡改测试。在请求里改动一个非token字段比如把业务参数从A改成B看token是否失效。如果失效说明该字段参与了签名你的签名算法必须包含它如果没失效说明服务端没有把这个字段纳入验签范围。第三类是重放测试。把同一个token重复发送两次如果第二次被1002拦截说明服务端有防重放机制token是一次性的。这三类验证结果直接决定了你还原出来的算法能不能作为后续工作的基础。5. 分析结果如何验证与落地5.1 用控制变量法给字段语义下结论到这一步你手上应该有了一个可运行的token生成函数。但能跑不等于看懂了。我建议再回头做一轮完整的控制变量实验给每个字段的语义下最终结论。操作方法是固定其他所有条件每次只改一个输入观察生成的token里哪一段会变。整理成一张表以后复盘时一目了然改变的变量受影响字段结论修改URL参数第3段第3段参与签名验签范围包含URL参数修改系统时间第2段、第3段时间戳纳入签名第2段是明文时间戳修改设备指纹第1段、第3段设备指纹参与签名第1段是指纹摘要清空Cookie全部无变化该token与Cookie无关更换账号第4段第4段可能与账号ID绑定需进一步确认这张表做完整个token的算法结构才算真正落到纸面上。5.2 自动化回归用数据说话人工验证几个样本不够我会用脚本批量生成几百个token做三个统计指标。第一个是正确率。把生成的token逐个发送到授权测试环境统计通过率低于95%说明还原不完整还有隐藏参数没纳入签名。第二个是重复率。在相同输入下连续生成100次如果token完全相同说明没有随机因子这可能被服务端的防重放机制直接拦截。第三个是时间戳分布。统计token里时间戳字段的解析结果如果出现大量未来的时间戳或负数说明字段边界判断错了。这三个指标能帮你快速定位问题出在随机数处理、字段拼接还是签名范围上。自动化脚本本身不复杂就是把生成函数循环跑把结果写进CSV用Python做统计。5.3 合规边界与结果的使用方式最后这部分我必须认真说。整个分析流程强调了很多次授权这里再展开一下我个人的做法。分析结果应该用于三类场景一是授权范围内的安全评估把发现的问题写成漏洞报告提交给平台安全团队二是自己团队产品的风控自测理解现有token机制在什么条件下会失效三是纯粹的技术学习把分析过程写成文章或教程。这三类场景都建立在你对该平台拥有合法测试权限的前提上。反过来用这类分析去批量抓取他人数据、绕过风控刷接口、干扰正常用户服务既违反平台规则也可能触及法律红线。我见过同行在这上面栽跟头代价远超收益。分析完一个token之后我通常还会顺手做一件事把整个分析过程整理成一份带时间线的复盘文档记录每个阶段的样本、假设和验证结果。下次再遇到其他平台的token直接套这套流程效率能提升一大截。最后再分享一点个人体会。这类风控token的分析真正难的地方从来不是某个哈希算法本身而是你能不能沉住气把样本收集够、把字段边界切分对。我见过太多人在猜算法上花掉一周回头发现不过是设备指纹不一致导致的全盘皆错。先花半小时把样本和链路摸清楚往往比任何奇技淫巧都管用。