Web 端请求签名机制分析:以小红书(XiaoHongShu)x-s 参数为例

免责声明: 本文所有分析均基于公开可访问的前端 JS 代码及小红书 Web 端(https://www.xiaohongshu.com)正常访问过程中的网络请求,仅用于安全研究、学习与了解 Web 前端请求保护机制。文中接口地址均来自浏览器正常访问产生的公开流量。请勿将本文技术用于任何未授权的系统,违者后果自负。

一、背景介绍

小红书 Web 端(edith.xiaohongshu.com)对 API 请求采用了一套前端签名保护机制,其中 x-s 是核心请求头,用于标识请求的合法性。本文以**首页信息流接口(homefeed)**为研究对象,分析 x-s 的生成机制。

核心签名相关请求头:

请求头 说明
x-s 请求签名,每次请求动态生成,核心防护参数
x-t 毫秒时间戳,参与签名计算
x-s-common 设备/环境信息的 Base64 编码,固定值
x-b3-traceid 链路追踪 ID,固定值

研究核心难点:

  • 混淆 JS 的还原02_source.js 为高度混淆的字符串数组自解密结构)
  • Node.js 环境补环境 (原 JS 依赖浏览器 windowdocumentnavigator 等对象)
  • x-s 签名算法的定位与提取 (核心函数 mnsv2
  • x-s-common 的结构分析(自定义字母表 Base64 编码的设备指纹)

二、整体流程图

复制代码
准备请求参数(url path + JSON body)
  ↓
计算辅助哈希:
  f = url_path + json_body(字符串拼接)
  c = MD5(f)
  d = MD5(url_path)
  ↓
调用混淆 JS 中的 mnsv2(f, c, d) → 得到签名核心值 x3
  ↓
组装 x-s 结构体:
  {"x0":"4.3.1","x1":"xhs-pc-web","x2":"Windows","x3":<签名>,"x4":"object"}
  ↓
对结构体 JSON 进行自定义字母表 Base64 编码
  ↓
x-s = "XYS_" + Base64结果
  ↓
携带 x-s、x-t、x-s-common 等请求头发起 POST 请求

三、抓包分析

3.1 目标接口

请求:

复制代码
POST https://edith.xiaohongshu.com/api/sns/web/v1/homefeed
Content-Type: application/json;charset=UTF-8

关键请求头:

请求头 来源 说明
x-s 本地签名生成 核心防护参数,见第四节
x-t 本地时间戳 毫秒级时间戳
x-s-common 固定值 设备指纹,Base64 编码的 JSON,详见第五节
x-b3-traceid 固定值 链路追踪 ID
xy-direction 固定值 固定为 57

请求体示例(JSON):

json 复制代码
{
  "cursor_score": "",
  "num": 15,
  "refresh_type": 1,
  "note_index": 15,
  "category": "homefeed.cosmetics_v3",
  "need_num": 8,
  "image_formats": ["jpg", "webp", "avif"],
  "need_filter_image": false
}

响应: 返回首页信息流 note 列表(JSON)。


四、x-s 签名生成算法

4.1 预处理:三个入参的计算

python 复制代码
url_path  = '/api/sns/web/v1/homefeed'
json_body = json.dumps(data, separators=(',', ':'))  # 无空格紧凑格式

f = url_path + json_body          # 路径与请求体的字符串拼接
c = MD5(f)                        # 对拼接结果做 MD5
d = MD5(url_path)                 # 对路径单独做 MD5

4.2 核心签名:mnsv2

混淆 JS(02_source.js)中注册了全局函数 mnsv2,挂载于 window 对象上:

javascript 复制代码
// 混淆后原始形式(伪代码还原逻辑)
window.mnsv2 = function(f, c, d) {
    // 内部通过字符串数组解密表(_0x5ae8)还原真实逻辑
    // 核心:基于 f、c、d 三个输入,结合内置密钥流,生成签名字符串 x3
    return x3  // 签名结果
}

注意mnsv2 的内部实现为字符串数组自解密混淆结构(见 02_source.js 前两行的 _0x5ae8 数组),不建议手动还原,直接在补环境后调用原始 JS 函数即可得到正确结果。

4.3 x-s 的组装与编码

得到 x3 后,按以下步骤组装最终 x-s 值:

Step 1:构造信息结构体

json 复制代码
{
  "x0": "4.3.1",
  "x1": "xhs-pc-web",
  "x2": "Windows",
  "x3": "<mnsv2计算结果>",
  "x4": "object"
}
字段 含义
x0 SDK 版本号
x1 应用标识
x2 操作系统
x3 核心签名值(mnsv2 输出)
x4 固定字符串 "object"

Step 2:自定义字母表 Base64 编码

小红书使用了非标准 Base64 字母表 ,与标准 A-Z a-z 0-9 +/ 不同:

小红书使用了非标准 Base64 字母表,具体字母表字符串需自行从 SDK JS 源码中提取(位于 03_main.js 顶部的常量字符串定义处)。

编码流程:

  1. 对结构体 JSON 字符串进行 encodeURIComponent 转义,提取各字节 UTF-8 编码
  2. 按标准 Base64 分组(每 3 字节 → 4 字符),但使用上述自定义字母表查表输出

Step 3:拼接前缀

复制代码
x-s = "XYS_" + Base64编码结果

五、x-s-common 结构分析

x-s-common 是一个固定的长字符串,同样使用x-s 相同的自定义字母表 Base64 编码,解码后为设备指纹 JSON:

json 复制代码
{
  "s0": <平台标识>,
  "s1": <...>,
  "x0": <SDK版本>,
  ...
}

该值包含浏览器指纹信息,在会话期间相对固定。研究时可从浏览器抓包直接复用,无需重新生成。


六、Node.js 环境补全(补环境)

02_source.js 运行时依赖大量浏览器 API,在 Node.js 中直接执行会报错,需手动补充以下环境(见 01_env.js):

6.1 需补充的对象

对象/属性 补充方式 说明
window window = global 将 Node.js global 作为 window
self, top 同上 同 window 引用
window.addEventListener 空函数 阻止事件绑定报错
window.chrome 空对象 {} 模拟 Chrome 环境
document 自定义 Document 补充 cookie、getElementsByTagName 等
document.cookie 注入 cookie 字符串 部分签名逻辑会读取 cookie 参与计算
navigator.userAgent 注入 UA 字符串 需与请求头中的 UA 保持一致
navigator.webdriver false 规避 webdriver 检测
XMLHttpRequest 空实现 阻止 JS 内部发起请求
MouseEventScreen 空函数 防止构造函数调用报错

6.2 补环境注意事项

  • document.cookie 中注入的 cookie 需与 Python 请求中使用的 cookie 保持一致 ,因为签名函数内部可能读取 a1webId 等 cookie 字段参与计算
  • navigator.userAgent 需与请求头 user-agent 一致
  • 补环境不完整时,建议用 Proxy 对象拦截 get/set 操作,打印实际访问了哪些属性,按需补充

七、Python 调用 Node.js 方案

7.1 调用架构

复制代码
Python (xhs.py)
  └─ subprocess.run(['node', 'xhs/03_main.js', 'get_x_s', json_data])
       └─ 03_main.js (入口)
            ├─ require('./01_env.js')   # 补环境
            └─ require('./02_source.js') # 混淆原始 JS

7.2 关键注意事项

路径问题(常见陷阱)subprocess 调用 node 时的工作目录不一定是脚本所在目录,必须使用绝对路径:

python 复制代码
import os
js_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "03_main.js")
result = subprocess.run(['node', js_path, function_name, arg], ...)

返回值解析03_main.js 通过 console.log 输出结果,Python 端读取 stdout;若 JS 内部有 debug 日志,需按换行符切分取目标行。

编码 :subprocess 调用时指定 encoding='utf-8',避免中文路径或中文字符导致乱码。


八、完整请求流程概述

步骤 动作 说明
1 构造请求体 按接口要求拼装 JSON,使用无空格紧凑格式
2 计算辅助哈希 f = path+body,c = MD5(f),d = MD5(path)
3 调用 mnsv2 通过 Node.js 执行补环境后的混淆 JS,传入 f/c/d
4 组装 x-s 构造结构体 JSON → 自定义字母表 Base64 → 加前缀 "XYS_"
5 准备其他请求头 x-t(时间戳)、x-s-common(设备指纹,可复用抓包值)
6 发起 POST 请求 edith.xiaohongshu.com,携带完整 headers 和 cookies
7 解析响应 JSON 格式,包含 note 列表等业务数据

九、关键知识点总结

知识点 详情
签名核心函数 window.mnsv2(f, c, d),注册在混淆 JS 的全局作用域
入参构造 path+body 拼接 → MD5 两次,得到 f/c/d 三个输入
x-s 结构 "XYS_" + CustomBase64(JSON{x0~x4})
自定义 Base64 字母表需自行从 SDK JS 源码提取,长度 64 字符,非标准 A-Za-z0-9+/
x-s-common 设备指纹,同编码方式,会话内固定,可抓包复用
混淆类型 字符串数组自解密(_0x5ae8 数组 + 自调用立即函数校验和)
补环境关键点 document.cookie 需与请求 cookie 一致,navigator.userAgent 需与 UA 一致
路径陷阱 Python subprocess 调用 node 时需使用 __file__ 构造绝对路径

十、依赖安装

bash 复制代码
pip install requests
node --version  # 需要 Node.js 环境(建议 v16+)

本文技术仅供安全研究与学习,切勿用于任何未授权系统,违者后果自负。

相关推荐
包子源2 小时前
React-PDF 与 Web 预览「像素级」对齐实践
前端·react.js·pdf
jiayong232 小时前
第 25 课:给学习笔记页加上搜索、标签筛选和 URL 同步
开发语言·前端·javascript·vue.js·学习
UXbot2 小时前
如何用 AI 快速生成完整的移动端 UI 界面:从描述到交付的实操教程
前端·ui·交互·ai编程·原型模式
南囝coding2 小时前
零成本打造专业域名邮箱:Cloudflare + Gmail 终极配置保姆级全攻略
前端·后端
jiayong232 小时前
第 12 课:`watch` 和防抖到底该怎么用
前端·javascript·vue.js
鹏程十八少2 小时前
2.2026金三银四 Android Handler 完全指南:28道高频面试题 + 源码解析 + 图解 (一文通关)
android·前端·面试
大连好光景2 小时前
Fiddler、Wireshark、Charles三种抓包工具的对比
前端·fiddler·wireshark
gyx_这个杀手不太冷静2 小时前
大人工智能时代下前端界面全新开发模式的思考(五)
前端·架构·ai编程
qiao若huan喜2 小时前
12、webgl 基本概念 +满天星星眨眼睛
前端·信息可视化·webgl