Mic.Run

Back

IPPure 接口分析页面IPPure 接口分析页面

TL;DR

太长不看?先看结论!
  • 首次直接发送不带 x-k/x-t 的请求;从响应头取出 x-k 作为 HMAC 密钥,取出 x-t 作为 serverTime,同时记录 localTime = 当前毫秒时间。
  • 重试时计算 timestamp = Date.now() - localTime + serverTime,签名原文是“请求方法-完整URL-Body-timestamp”;GET 无 Body,例如“GET-https://api.123169.xyz/api/info/ip-risk/172.67.214.188--1750860000000”。
  • 用 x-k 对签名原文做 HMAC-SHA256 并输出 hex,请求头设为“x-k: <服务端Key>”和“x-t: <timestamp>-<HMAC hex>”。如果响应又返回新 x-k,更新密钥和服务器时间后再重试,建议最多 5 次。

前言#

研究IP风险检测的时候,在IPPure找到了这个接口:

https://api.123169.xyz/api/info/ip-risk/114.5.14
txt

IPPure 网页里面请求是正常的,但是请求中有两个应该是签名的东西 x-kx-t,拿来研究一下玩玩。

x-k 从哪里来#

翻一下调用堆栈,在 security.D_hK1zoa.js 调用的

最开始看到的是这段:

这段并不是生成 Header 的,而是接收服务端返回的 Header。

响应中有 x-k 就存进 localStorage,然后把刚才的请求重新发一遍。loopIndex 超过次数后会直接返回 too many failures,猜测如果服务端一直返回新 Key 就死循环了。

x-t 在这里保存的是服务器时间,同时记录一下本机收到它时的时间:

{
    serverTime: parseInt(s),
    localTime: Date.now()
}
js

所以 x-k 不用自己算。

第一次可以直接请求,服务端返回一个 x-k,保存后再用它重新请求。Key 过期看起来也是返回新的 x-k 走这段代码。

x-t 的计算#

请求之前会从 localStorage 取出 x-k,然后拼接下面这几个值:

请求方式-完整URL-Body-时间戳
txt

使用 x-k 做 HMAC-SHA256,最后的 Header 是:

x-k: 服务端返回的Key
x-t: 时间戳-HMAC结果
txt

时间戳也没有直接用本机时间,而是这样计算,可能是为了防止电脑时间不准:

Date.now() - localTime + serverTime
txt

GET 没有 Body,所以这个 IP 接口实际参与签名的字符串大概长这样:

GET-https://api.123169.xyz/api/info/ip-risk/172.67.214.188--1750860000000
txt

Python实现#

按照这个流程写出来就行,不需要提前准备 x-k。第一次请求会从响应里面拿,第二次才会带上签名。 当然我是让 CodeX 写的实现

这个脚本只处理了 GET,用来请求 IP 信息已经够了。

比较容易看错的是 x-t,服务端响应中的 x-t 是服务器时间,请求中的 x-t 才是 时间戳-HMAC。我最开始把响应处理这段当成生成 Header 的地方,所以一直没看到签名到底在哪里算的…

IpPure 的 x-k/x-t 请求签名分析
https://mic.run/blog/x-k-x-t-request-signing
Author Mic
Published at 2026年7月12日
Comment seems to stuck. Try to refresh?✨