欢迎光临
我们一直在努力

小红书x-s签名逆向:从mnsv2函数到自定义Base64的完整链路

免责声明: 本文所有分析均基于公开可访问的前端 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 依赖浏览器 window、document、navigator 等对象)
  • 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):

{
"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 预处理:三个入参的计算

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 对象上:

// 混淆后原始形式(伪代码还原逻辑)
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:构造信息结构体

{
"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 顶部的常量字符串定义处)。

编码流程:

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

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


    五、x-s-common 结构分析

    x-s-common 是一个固定的长字符串,同样使用与 x-s 相同的自定义字母表 Base64 编码,解码后为设备指纹 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 内部发起请求
    MouseEvent、Screen 等 空函数 防止构造函数调用报错

    6.2 补环境注意事项

    • document.cookie 中注入的 cookie 需与 Python 请求中使用的 cookie 保持一致,因为签名函数内部可能读取 a1、webId 等 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 时的工作目录不一定是脚本所在目录,必须使用绝对路径:

    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__ 构造绝对路径

    十、依赖安装

    pip install requests
    node –version # 需要 Node.js 环境(建议 v16+)


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

    赞(0)
    未经允许不得转载:171主机测评 » 小红书x-s签名逆向:从mnsv2函数到自定义Base64的完整链路
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址