前言
继续~ 继续~ 鸽了好久
EZFL
EZFL(PHP 文件包含)
访问靶机
先随便点点,没有发现什么特殊交互和信息,看一下注释 发现两行特别注释
第一条注释确认了后端使用 include($file) 进行文件包含
第二条 Base64 解码后为:flag is in /flag.txt
由于后端 include() 没有对 $file 做路径限制
|
|
直接传入了 /flag.txt,PHP 的 include 会读取并输出文件内容,flag 就被包含在页面中返回了
EZFL_1(PHP 文件包含 + php://filter 伪协议读取源码)
访问靶机
跟第一题没多大差别,没有什么可交互的,还是看一下注释,发现特别注释
Base64 解码后:“The flag is right in flag.php, but you’ll never be able to see it.” 在flag.php中 但是我不能看见它
尝试直接包含flag.php 返回 flag就在这里,不过我把他藏起来了
因为 include(‘flag.php’) 会执行PHP代码,flag 被藏在注释 //flag{…} 中,PHP 不会输出注释内容
使用php://filter 读取源码
|
|
让PHP不执行flag.php 而是Base64编码再输出 这样就能看见源码
然后再转换一下就能看到flag
EZFL_2(PHP 文件包含 + php://filter 伪协议读取源码 + base64过滤绕过)
访问靶机
依旧没有交互 看一下注释 和上一题的注释内容一样
尝试一下上一题的payload 发现不行 返回no way 可能过滤了base64 尝试一下其他的过滤器 convert.iconv.UTF-8.UTF-16LE
原文件开头是 [?php 转成 UTF-16LE 后会变成类似 <00?<00p<00h<00p<00
这样就不再是可执行的 PHP 标签 include 时不会执行,而是把源码内容直接吐出来
|
|
EZFL_3(LFI -> php://input -> RCE)
访问首页 和前面题目一样 首页没有可交互的地方,那就看一下注释
大概意思就是 让我们靠自己 没有提示了
利用 PHP 的 php://filter 伪协议读取 index.php 的源码
$file 直接取自 $_GET[‘file’],没有任何过滤或白名单校验,直接传入 include(),存在无限制的本地文件包含漏洞
使用目录穿越读取系统文件
|
|
单纯的文件包含只能读取文件,要获取 flag 需要执行命令。利用 php://input 伪协议可以将 POST 请求体作为 PHP 代码执行
接着查看flag的位置
|
|
发现有关flag的随机文件 进行访问
|
|
成功获得flag
EZFL_4
访问首页 和前面题目一样 首页没有可交互的地方 直接看注释 注释也一样 让我们自己往下做
文件包含题 直接目录穿越
|
|
成功读到 /etc/passwd,说明这里是一个可利用的 LFI。接着尝试直接读源码,比如 index.php 或 php://filter
都会返回 php not allowed,说明程序对 file 参数里的 php 做了黑名单过滤
继续测试发现 data:// 没被拦,而且能被 include
|
|
既然 include(data://…) 成立,就可以直接注入 PHP 代码拿 RCE
|
|
返回 uid=33(www-data),说明命令执行成功。随后枚举站点目录
|
|
发现根目录下有 flag.php。由于 file 参数里不能出现明文 php,这里用 shell 通配符绕过黑名单,直接读取
|
|
最后flag在源码里
EZFL_5
访问首页 和前面题目一样 首页没有可交互的地方 直接看注释 注释也一样 让我们自己往下做
先尝试常见的伪协议包含
|
|
返回 data not allowed,说明题目对 data:// 做了过滤
既然只过滤了 data,就换用另一个 PHP 伪协议 php://input。php://input 可以读取原始 POST 数据,而 include() 在包含它时会把其中的 PHP 代码当作脚本执行
|
|
获得flag
EZFL_6(PHP 远程文件包含(RFI) -> 代码执行(RCE))
访问首页 页面存在 file 参数,查看源码可以看到注释:
说明后端大概率直接对 file 做了 include(),先用
|
|
成功读到文件,确认存在文件包含漏洞
继续测试后发现,php:// 和 data:// 都被关键词过滤,常规的 php://filter 利用不了。但进一步发现 http:// 可以被包含,也就是说这里不只是 LFI,还存在 RFI(远程文件包含)。更关键的是:远程文件中的 PHP 短标签 [?= … ]? 会被目标服务器解析执行。于是可以在外部托管一个简单 payload,例如
|
|
https://paste.rs/web 这是一个支持公开纯文本和 Raw 直链的粘贴网站
在上面直接输入 [?=implode(",",scandir("/"));]? 会获得一个链接 https://paste.rs/**** 每次都不一样 直接访问即可
|
|
从而枚举根目录,结果在根目录下发现了可疑文件 qingcenctf.txt
直接读取
|
|
EZFL_7
访问首页 直接给了源码
|
|
说明这是任意文件写入,但会强行在前面加 [?php exit();]?,所以不能直接写普通 webshell
用 php://filter/write=convert.base64-decode/resource=chen.php 绕过前缀,在burp里发 POST
|
|
这段 content 解码后就是执行 ls 命令的代码,前面的 A 是为了配合固定前缀做对齐
写入成功 访问 http://docker.qingcen.net:34249/chen.php
可以看到两个文件 c0nq4er1ng.php conquer.php 接下来获取内容就行了
|
|
也就是
|
|
访问readsrc.php即可看到两个文件的源码
EZFU
EZFU
访问首页 发现有文件上传功能
正常上传一个测试文件,服务端返回 uploads/
在源码里看到它只取原始文件名扩展名,然后直接 move_uploaded_file(),没有任何类型校验 直接抓包 改包发送即可
接着直接访问返回的 file_url 验证命令执行
|
|
一般来说 flag会放在环境变量中
|
|
成功获得flag
EZFU_1
和前面一样 那我们直接看源码
只允许上传图片文件 我们上传一个简单的图片绕过前端 在burp中修改文件名和内容即可
修改请求体 中filename 1.png->1.php 和Content-Type image/png->application/x-php
上传成功 我们直接访问并测试 命令是否执行
|
|
命令执行成功 直接读环境变量获得flag
|
|
成功获得flag
EZFU_2
一、初始观察
打开题目页面,是一个很简洁的文件上传表单,只接受 image 字段,上传后返回文件路径。没有登录、没有验证码,看起来就是考上传绕过。
二、试探过滤规则
先拿不同类型的文件试了一圈,摸清楚后端的过滤逻辑。
test.php 直接被拦,返回 File type not allowed,说明有扩展名黑名单。test.php5 也被拦了,test.pHp 大小写混写同样没用,后端会先转小写再匹配。
正常的图片文件 test.jpg 和 test.png 都能上传成功。
试了 test.phtml,居然过了,既没被黑名单拦,也没被内容检测拦。另外 .htaccess 也能传上去。
再试 test.phar,这次是被内容检测拦下来的,返回 File content not allowed。说明后端除了黑名单之外,还会扫描文件内容里有没有 [?php 之类的标记。
到这里就清楚了,后端有两层过滤。第一层是扩展名黑名单,拦了php、php3、php4、php5、phps、pht、jsp、jspx、asp、aspx、sh、py、pl、exe、bat、cmd 这些。第二层是内容检测,如果文件里出现 [?php 或 [?=,只有扩展名是 .phtml 的时候才放行。 所以 .phtml 就是突破口——不在黑名单里,内容检测也给它开了绿灯,而且 Apache 默认配置会把它当 PHP 解析。
三、拿到 Webshell
构造一句话木马,保存为 shell.phtml 上传。服务器返回了路径 uploads/96289496-024a-471d-8ad0-faeb1416d3a3.phtml。
直接浏览器访问这个地址,页面输出了 phpinfo 的内容,确认 .phtml 被当成 PHP 执行了。
四、找 Flag
用 webshell 执行命令找 flag。先翻了 /flag*、/root/、/var/www/html/ 这些常见位置,都没有。
然后直接读环境变量 env,输出里赫然写着 FLAG=flag{4327b3b7-cfac-46f7-8e6f-88810e9a2147}。
Flag 是通过 Docker 的 ENV 指令注入进容器的,不在文件系统里,所以用 find 搜文件名根本搜不到,必须用 env 或 printenv 才能看见。
五、复盘
这题核心考了两个点。
一个是扩展名黑名单不完整。.phtml 是 PHP 的合法扩展名,出题人的黑名单里漏掉了。做文件上传题要对各种web 语言的冷门扩展名心里有数,比如 .phtml、.pht、.phps、.phar 这些,黑名单很容易漏。
另一个是 flag 的藏法。Docker 容器里经常通过环境变量注入 flag,不是文件,用 find 搜不到。养成习惯进容器先 env 看一眼。
EZFU_3
访问首页,文件上传表单,PHP 8.2.30
测试发现这次加了文件头(Magic Bytes)校验。.phtml 不在扩展名黑名单中,但裸 PHP 代码过不了文件头检查
突破口是给 PHP 文件加上合法的图片文件头骗过校验。构造 payload 时在 PHP 代码前添加 GIF89a GIF 文件头,保存为 shell.phtml,上传时 MIME 改为 image/gif,成功绕过两层检测
上传成功后访问返回的文件路径,读取环境变量获得 flag:
|
|
复盘:这题比 EZFU_2 多了一层文件头校验。.phtml 扩展名不在黑名单中,加上 GIF89a 前缀后文件头检测认为是合法 GIF 图片,而 Apache 仍然会把 .phtml 当 PHP 解析执行。flag 藏在 Docker 环境变量中
EZFU_4
访问首页,文件上传表单。这次比上一题多了 PHP 内容检测
测试发现后端只检测了 [?php 这种完整标签,漏掉了 [?= 短标签(PHP short echo tag)。[?= 在 PHP 5.4+ 中默认开启,等价于 [?php echo
构造 payload 时使用 [?= 替代 [?php,加上 GIF89a 文件头,保存为 shell.phtml 上传成功。读取环境变量获得 flag:
|
|
复盘:后端只匹配了 [?php,没有覆盖 [?= 短标签。做内容检测类题目时要注意所有可能的标签写法
EZFU_5
与 EZFU_4 解法一致,环境换成了 PHP 8.2.30。在 PHP 8.x 中 <% 和 <script language="php"> 已被移除,[?= 是唯一能绕过 [?php 检测的短标签写法
使用同样方法获得 flag:
|
|
复盘:注意 payload 中变量要用单引号,双引号在 multipart 上传时可能被转义导致命令无法传入
EZFU_6
在 EZFU_5 基础上加了危险函数检测。常见命令执行函数 等常见命令执行函数全被拦截
但遗漏了 PHP 的反引号运算符。反引号在 PHP 中等价于 命令执行函数,但它是运算符不是函数名,不被关键词黑名单匹配。使用 [?= + 反引号构造 payload 上传成功
读取环境变量获得 flag:
|
|
复盘:防御时不能只靠函数名黑名单,还应禁用反引号,或在 php.ini 中设置 disable_functions 彻底禁用危险函数
EZFU_7
封了 .phtml,但 .php7 和 .phar 可以上传,且文件名保留原始名称。.htaccess 也可以上传
两步配合:先上传 .htaccess 设置 AddType application/x-httpd-php .php7,再上传 webshell 的 .php7 文件。访问 webshell 读取环境变量获得 flag:
|
|
复盘:文件名未随机化是关键漏洞,允许上传 .htaccess 修改 Apache 解析规则
EZFU_8
.htaccess 不生效(Apache AllowOverride None),但 uploads 目录下有 index.php
突破口是 .user.ini。它是 PHP-FPM 的用户级配置文件,不受 Apache AllowOverride 限制。利用 auto_prepend_file 指令让 PHP 在执行任何文件前先包含指定文件
先上传带 PHP 代码的 GIF 文件作为 webshell,再上传 .user.ini 设置 auto_prepend_file 指向该 GIF。访问 index.php 触发包含执行,获得 flag:
|
|
复盘:.htaccess 由 Apache 读取受 AllowOverride 控制,.user.ini 由 PHP-FPM 自身读取不经过 Apache。防御时应禁止上传 .user.ini 和 .htaccess
EZFU_9
综合题,涉及信息泄露 → 登录认证 → 文件上传三个环节
第一步:源码泄露。login.php 的 ?doc= 参数存在目录穿越,用 ../ 读取 PHP 源码
第二步:获取密码。源码中密码藏在 /etc/passwd 的 GECOS 字段(pass=HHEFasNdUZMc),用目录穿越读取
第三步:登录后台。Dashboard 有 .bz2 备份恢复功能,解压后文件名 = 上传文件名去掉 .bz2 后缀
第四步:构造恶意 bz2。用 Python 的 bz2.compress() 创建包含 PHP 代码的 bzip2 文件,命名为 shell.php.bz2 上传,解压后写入 uploads/shell.php
访问 webshell 获得 flag:
|
|
复盘:多漏洞组合利用,文件名处理是关键——去掉 .bz2 后缀作为写入路径
EZFU_10
login.php 弱口令 admin / 123456 直接登录
Dashboard 备份恢复功能使用 include() 直接包含上传的 .bz2 文件。bz2 压缩数据中的 PHP 代码会被解析执行
但因为上传是 POST 请求,$_GET 参数不可用。需要用 文件写入函数 先写入持久化 webshell,再通过 GET 请求触发
用 Python 的 bz2.compress() 创建包含写文件逻辑的 payload,命名为 writer.bz2 上传。服务器 include() 执行后将 webshell 写入 uploads/rce.php,访问获得 flag:
|
|
复盘:弱口令是第一个突破口,include() 直接包含上传文件是第二个,POST 时 $_GET 不可用需要持久化是第三个
EZFU_11
PHP 文件可以直接上传(无扩展名黑名单),但 uploads 目录下 .htaccess 阻止 PHP 执行,且每次上传后自动恢复原始 .htaccess
利用 .htaccess 覆盖机制:先上传自定义 .htaccess(使用 RemoveType 和 RemoveHandler 移除 PHP 解析限制),再立刻上传 PHP webshell,在服务器恢复原始 .htaccess 前访问
读取 /flag 文件获得 flag:
|
|
复盘:这是竞态条件利用——必须在服务器恢复原始 .htaccess 之前快速完成上传和访问。flag 位于 /flag 文件而非环境变量中
EZFU_12
访问首页,文件上传表单,PHP 8.2.30。文件名会被加上 UUID 前缀进行随机化
测试发现 PHP 文件可以直接上传,且上传后代码会立刻执行。但大约 2 秒后文件会被自动删除——这是一个竞态条件漏洞
解题思路:
- 构造一个读取系统信息的 PHP 脚本(用 shell_exec 函数执行 env 或 cat 命令)
- 通过 curl 上传该脚本,服务端返回带 UUID 的文件路径
- 在文件被删除前立刻用 curl 访问该路径获取输出
由于文件存活时间极短,需要在一次请求中完成上传和访问。用 shell 脚本先上传获取 URL,再立刻访问:
|
|
实际操作中,上传读取环境变量的脚本后立刻访问,获得 flag:
|
|
复盘:这题的核心是竞态条件——PHP 文件上传后立刻可执行,但约 2 秒后被清理脚本删除。攻击者必须在这 2 秒窗口内完成上传和访问。防御时应在校验通过后再写入磁盘,或者在上传时就阻止含危险代码的文件,而非事后清理
EZFU_13
访问首页,是一个"在线 ZIP 解压中心",上传 ZIP 文件后自动解压并提供下载链接
页面 HTML 注释中泄露了提示:flag 位于 /C000000quer.txt。但下载接口的 file 参数做了路径穿越过滤,无法直接读取系统文件
突破口是 ZIP 符号链接攻击。ZIP 格式支持存储 Unix 符号链接,解压时如果服务端不做校验,会创建指向任意路径的符号链接。通过下载接口访问该符号链接时,服务端会跟随链接读取目标文件内容
用 Python 构造包含符号链接的 ZIP 文件,将链接目标设为 /C000000quer.txt,链接名称设为 flag_link。上传后解压生成符号链接,通过下载接口访问即可读取 flag:
|
|
输出:
|
|
构造恶意 ZIP 的 Python 代码思路:
|
|
复盘:这题考的是 ZIP 符号链接攻击。ZIP 格式可以存储符号链接,如果解压时不检查链接目标,就会创建指向系统任意文件的符号链接。配合下载接口,可以读取系统上的敏感文件。防御时解压后应检查文件类型,拒绝符号链接或限制链接目标路径
EZFU_14
访问首页,是一个"在线图片检测中心",上传 JPG 图片后自动生成检测报告
页面 HTML 注释泄露提示:源码备份存在。访问 index.php.bak 获取源码
源码中发现关键漏洞——命令注入:
|
|
文件名未经任何过滤直接拼接进 shell 命令。后端只校验扩展名为 .jpg,但文件名中可以包含 shell 元字符
利用分号 ; 作为命令分隔符注入命令。文件名格式为 x.jpg;要执行的命令;#.jpg,其中:
x.jpg满足扩展名校验;分隔出新命令#注释掉后面的.jpg 2>&1
通过注入 ls -la 发现目录下有 pigeon_cat.php 文件,读取该文件获得 flag:
|
|
复盘:这题考的是命令注入。exec() 函数直接拼接用户输入的文件名到 shell 命令中,没有任何过滤。后端只校验了文件扩展名,没有对文件名内容做安全检查。防御时应使用 escapeshellarg() 对用户输入进行转义,或者使用白名单只允许字母数字和点号
EZSQL
EZSQL(字符型 UNION 联合查询注入)
访问靶机,页面是一个学生成绩查询系统,输入学号返回姓名、学号、班级、成绩四个字段
先测试正常查询 id=2026001,返回学生 Zhang Wei 的信息,说明后端有数据且查询正常
测试 id=1' 没有报错,而是返回 not found,说明可能存在字符型注入,单引号被包裹在查询中
尝试 id=1 or 1=1,返回了第一条学生记录,确认存在注入。进一步用 UNION SELECT 确定列数:
|
|
返回了 1、2、3、4 四个值,确认查询有 4 列,且页面回显位置为:Welcome 对应第 2 列,Student ID 对应第 1 列,Class 对应第 3 列,Score 对应第 4 列
接下来获取数据库名和版本:
|
|
数据库名为 user,版本为 MariaDB 10.4.13
然后枚举当前数据库的所有表:
|
|
发现两张表:flag 和 students
查看 flag 表的列名:
|
|
列名为 id 和 secret
最后直接 dump flag 表:
|
|
成功获得 flag:flag{1ce6dd45-6029-4382-b362-cf4c39792992}
EZSQL_1(字符型 SQL 注入 - 万能密码登录绕过)
访问靶机,页面是一个登录表单,使用 GET 方法提交 username 和 password 两个参数
先用 admin / admin 正常登录,返回 Login failed.,说明后端有查询逻辑
测试注入,在 username 后加单引号 admin',仍然返回 Login failed.,没有报错信息泄露,但也没有语法错误提示,说明可能存在字符型注入
经典的万能密码思路——用单引号闭合 username 字段,再用注释符 --+- 把后面的密码检查注释掉:
|
|
SQL 语句变为:
|
|
--+- 注释掉了密码校验部分,直接以 admin 身份登录成功,页面返回绿色提示框,flag 直接显示:
flag{2ee641f8-b9ae-4f3e-8c67-bf22dd448f23}
复盘:后端对用户输入没有任何过滤和转义,直接拼接进 SQL 语句。这是最基础的字符型 SQL 注入,万能密码绕过登录的经典案例。防御时应使用参数化查询(Prepared Statements)
EZSQL_2(字符型 UNION 联合查询注入 - 注入点在 Password 字段)
访问靶机,页面是一个登录表单,GET 方法提交 username 和 password 两个参数
先正常登录 admin / admin,返回 Login failed.
测试注入点,在 username 后加单引号 admin',仍然返回 Login failed,没有报错
尝试经典万能密码 admin'--+-,同样失败,说明 --+- 被过滤或者注释方式不同
换用 # 注释符测试:
|
|
返回 Welcome admin,说明 # 注释有效,密码校验被绕过。但页面只显示欢迎信息,没有直接给出 flag,需要用 UNION 注入从数据库中提取
确定列数,依次测试 2、3、4、5 列:
|
|
4 列时返回 Welcome 2,确认查询有 4 列,页面回显位置为第 2 列
获取数据库名和版本:
|
|
数据库名为 user,版本为 MariaDB 10.4.13
枚举当前数据库的表:
|
|
只有一张表:flag
查看 flag 表的列名:
|
|
四列:id、name、passwd、secret
直接 dump flag 表:
|
|
返回 1:admin:admin123:flag{87193361-15bf-4ce8-affe-9a068f8e6852},成功获得 flag
复盘:这题与 EZSQL_1 的区别在于注释符——--+- 被过滤,需要改用 # 注释。登录成功后不会直接显示 flag,需要通过 UNION 联合查询注入从 flag 表的 secret 列中提取。flag 表同时充当了用户表和 flag 存储的角色
EZSQL_3(字符型 SQL 注入 - 报错回显 + 括号闭合 + UNION 联合查询注入)
访问靶机,页面是一个登录表单,GET 方法提交 username 和 password 两个参数
正常登录 admin / admin,返回 Login failed.,没有直接报错
测试注入,在 username 后加单引号 admin':
|
|
返回 SQL Error: You have an error in your SQL syntax...near '' at line 1,存在报错回显,确认注入点
尝试 admin'--+-,报错 near 'x'),说明查询中使用了括号包裹条件,类似:
|
|
尝试 admin'#,仍然报错 near '',说明 # 后面还有未闭合的括号。正确的闭合方式是先闭合括号再注释:
|
|
返回 Welcome admin,登录成功但没有 flag。与 EZSQL_2 一样,需要用 UNION 注入提取数据
确定列数,测试 1~6 列:
|
|
4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列
获取数据库名和版本:
|
|
数据库名为 user,版本为 MariaDB 10.4.13
枚举当前数据库的表:
|
|
只有一张表:flag
查看 flag 表的列名:
|
|
四列:id、name、passwd、secret
直接 dump flag 表:
|
|
返回 1:admin:admin123:flag{d2556f2f-ccde-40ff-b698-2b2afaec3f94},成功获得 flag
复盘:这题与前两题的核心区别是查询条件使用了括号包裹。admin'# 无法绕过是因为括号未闭合,必须写成 admin')# 先闭合括号再注释。同时页面存在报错回显,可以利用错误信息推断查询结构。做 SQL 注入题时要注意观察报错中的括号、引号等细节来还原后端查询语句
EZSQL_4(字符型 SQL 注入 - 双引号闭合 + UNION 联合查询注入)
访问靶机,页面是一个登录表单,GET 方法提交 username 和 password 两个参数
正常登录 admin / admin,返回 Login failed.
测试单引号注入 admin'#,返回 Login failed,不起作用
测试双引号注入:
|
|
返回 Welcome admin,说明后端使用双引号包裹字符串,# 注释有效
进一步测试 admin")#,报错信息泄露了查询结构:near ')#" AND passwd = "x"',确认:
- 查询使用双引号
"而非单引号' - 密码字段名为
passwd - 没有括号包裹条件 登录成功但没有 flag,需要用 UNION 注入提取数据。使用双引号闭合:
|
|
4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列
获取数据库名和版本:
|
|
数据库名为 user,版本为 MariaDB 10.4.13
枚举当前数据库的表:
|
|
只有一张表:flag
查看 flag 表的列名:
|
|
四列:id、name、passwd、secret
直接 dump flag 表:
|
|
返回 1:admin:admin123:flag{3cd5863c-8dad-4e33-9b8d-8e349dfeb663},成功获得 flag
复盘:这题的核心区别是后端使用双引号 " 而非单引号 ' 包裹 SQL 字符串。常规的单引号闭合全部失效,需要先判断引号类型再构造 payload。做 SQL 注入时如果单引号不生效,一定要尝试双引号
EZSQL_5(字符型 SQL 注入 - 双引号括号闭合 + UNION 联合查询注入)
访问靶机,页面是一个登录表单,GET 方法提交 username 和 password 两个参数
正常登录 admin / admin,返回 Login failed.
先测试单引号 admin'#,不起作用。测试双引号+括号组合:
|
|
返回 Welcome admin,说明后端使用双引号 " 包裹 + 括号 () 包裹条件,查询结构类似:
|
|
登录成功但没有 flag,需要用 UNION 注入提取数据。使用 ") 闭合:
|
|
4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列
枚举当前数据库的表:
|
|
只有一张表:flag
查看 flag 表的列名:
|
|
四列:id、name、passwd、secret
直接 dump flag 表:
|
|
返回 1:admin:admin123:flag{0de95981-a6b8-43de-bf93-7cc4ab16daa3},成功获得 flag
复盘:这题结合了 EZSQL_3(括号闭合)和 EZSQL_4(双引号)两个特征。需要同时用 ") 闭合双引号和括号,再用 # 注释掉后续条件。随着题目递进,闭合方式越来越复杂,做题时要系统地测试引号类型和括号组合
EZSQL_6(字符型 SQL 注入 - WAF 过滤注释符 + 引号平衡绕过 + UNION 联合查询注入)
访问靶机,页面是一个登录表单,GET 方法提交 username 和 password 两个参数
正常登录 admin / admin,返回 Login failed.
测试单引号注入 admin'#,返回 Invalid characters detected.,说明 # 被 WAF 拦截
测试 admin'--+-,同样被拦截。说明 # 和 -- 注释符都被过滤
但 admin' 返回 SQL 报错,确认存在注入点。尝试不用注释符的绕过方式:
|
|
返回 Welcome admin,通过引号平衡绕过登录。原理是让 SQL 语句的引号自然闭合:
|
|
'1'='1' 中最后的 ' 与后续的 AND 形成合法语句
登录成功但没有 flag,需要用 UNION 注入。在 username 字段注入 UNION 会触发 passwd 列名冲突错误,改用 password 字段注入:
|
|
4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列
获取数据库名:
|
|
数据库名为 user
枚举表时,由于 from information_schema.tables where ... 会改变 SQL 结构,引号平衡被打破。使用 where '1'='1 来平衡引号:
|
|
发现 flag 表。查看列名:
|
|
四列:id、name、passwd、secret
提取 secret 列(避免使用 passwd 列导致冲突):
|
|
返回 flag{83d432f6-74d1-4342-8e59-215bdb3bfa5c},成功获得 flag
复盘:这题的核心难点是 WAF 过滤了 # 和 -- 注释符,不能用常规注释截断后续 SQL。解决方法是引号平衡——让注入的 SQL 语句中所有引号自然配对闭合。where '1'='1 中的 '1' 既充当布尔条件,又平衡了后续的引号。做 SQL 注入题遇到注释符被过滤时,引号平衡是关键绕过技巧
EZSQL_7(字符型 SQL 注入 - WAF 严格过滤关键词 + 大于号二分盲注)
访问靶机,页面是一个登录表单,GET 方法提交 username 和 password 两个参数
正常登录 admin / admin,返回 Login failed.
测试 admin'#,返回 Welcome admin,单引号和注释符可用
测试 admin' or '1'='1,返回 Illegal SQL injection.,存在 WAF 关键词过滤
逐一测试发现 WAF 过滤了几乎所有 SQL 关键词:select、union、from、where、and、or、length、ascii、hex、char、ord、regexp、rlike、if、case、sleep、benchmark、between、in、exists,以及 ( 括号
但 = 运算符和 > 运算符未被过滤,且 mid()、substr()、concat() 等函数可用
关键发现:= 因运算符优先级问题无法用于逐字符判断('admin'=mid(secret,N,1)='X' 会被 MySQL 解析为 ('admin'=mid(...))='X',永远返回相同结果)。但 > 运算符配合括号可以用于二分查找:
|
|
当 mid(secret,1,1) 的 ASCII 值大于 E 时返回 Welcome admin,否则返回 Login failed
编写二分查找脚本,对 secret 列逐字符提取。每次用 > 比较中间值,7 次比较即可确定一个字符(128 个 ASCII 值 → log₂128 ≈ 7 次请求/字符)
示例提取过程:
|
|
返回 Welcome admin → 第 1 个字符 > ‘E’
|
|
返回 Login failed → 第 1 个字符 ≤ ‘F’,即为 ‘F’
依次提取全部 42 个字符,得到 flag{a25e3fbc-ffce-4e57-a717-8614f0c01cce}
复盘:这题的 WAF 过滤极其严格,几乎封禁了所有 SQL 关键词和函数。突破口是 > 运算符未被过滤,配合括号实现二分盲注。核心技巧:
=因左结合优先级无法逐字符判断,必须用>做二分- MySQL 默认 collation 下字符串比较是大小写不敏感的,所以 ‘f’ > ‘E’ 和 ‘f’ > ’e’ 都为真
- 每个字符只需 ~7 次请求(二分),42 个字符总共约 300 次请求即可提取完整 flag
EZSQL_8(字符型 SQL 注入 - WAF 过滤关键词 + 报错回显 + 大于等于号二分盲注)
访问靶机,页面是一个登录表单,GET 方法提交 username 和 password 两个参数
测试 admin'#,返回 Welcome admin,单引号和注释符可用
测试 admin')#,页面返回完整的 SQL 报错信息:SELECT * FROM flag WHERE name = 'admin')#' AND passwd = 'x',直接泄露了:
- 表名:
flag - 列名:
name、passwd - 查询结构:无括号包裹,单引号闭合
测试
admin' or '1'='1,返回非法sql,存在 WAF 关键词过滤。逐一测试发现select、union、from、where、and、or、>、<等全部被拦截 但>=运算符未被过滤!利用>=配合括号构造二分盲注。关键发现:MySQL 中=是右结合的,admin'=(expr)=val#会被解析为admin'=(expr=val)#,配合>=构造布尔判断:
|
|
- 如果
mid(passwd,1,1) >= 'a'为真(1),则WHERE name = ('admin'=1)→WHERE name = 0,‘admin’ 转为 0 匹配 → 返回 Welcome admin - 如果为假(0),则
WHERE name = 1,无匹配行 → 返回 Login failed 但直接提取passwd列得到的是admin123(管理员密码),不是 flag。通过枚举发现表中还有secret列:
|
|
返回 Welcome admin,确认 secret 列存在且首字符 >= ‘a’
对 secret 列进行二分盲注提取,每个字符约 7 次请求(32~126 二分),示例:
|
|
返回 Welcome admin → 第 1 个字符 >= ‘A’
|
|
返回 Login failed → 第 1 个字符 < ‘G’,即为 ‘F’
提取全部 42 个字符,得到 flag{a11e2e0c-1cfd-45ad-a5ea-3596e97e1534}
复盘:这题有三个关键点:
- 报错回显泄露了完整的查询结构(表名、列名),大幅降低了信息收集难度
- WAF 过滤了
>、<但遗漏了>=,利用>=实现二分查找 - MySQL
=的右结合特性使得admin'=(expr)#可以构造布尔 oracle,flag 存储在secret列而非passwd列
EZSQL_9(数字型 UNION 联合查询注入 - 无过滤)
访问靶机,页面是一个"Employee Query"员工查询系统,输入 Employee ID 返回 id、name、department、salary 四个字段
正常查询 id=1,返回员工 Alice 的信息,确认后端有数据且查询正常
测试注入,id=1 and 1=1 返回正常数据,id=1 and 1=0 返回"未找到员工信息",确认存在数字型注入(无需引号闭合)
直接使用 UNION SELECT 测试:
|
|
返回 1、2、3、4 四个值,确认查询有 4 列,且四个位置均可回显。本题无任何 WAF 过滤,union、select、from、where 等关键词均可正常使用
获取数据库名和版本:
|
|
数据库名为 user,版本为 MariaDB 10.4.13
枚举所有表:
|
|
两张表:employees 和 flag
查看 flag 表的列名:
|
|
两列:id 和 flag
直接 dump flag 表:
|
|
返回 1:flag{c53dc831-e6b6-4747-bd6d-68e101023c14},成功获得 flag
复盘:这题是最基础的数字型 UNION 注入,没有任何过滤。与前面几题的字符型注入不同,数字型注入不需要引号闭合和注释符,直接用 and 1=1 / and 1=0 确认注入点后即可 UNION 提取数据。作为 EZSQL 系列中少数无 WAF 的题目,主要考察 UNION 注入的基本流程
EZSQL_10(字符型 UNION 联合查询注入 - 单引号闭合 + 无过滤)
访问靶机,页面是一个"Employee Query"员工查询系统,输入 Employee ID 返回 id、name、department、salary 四个字段
正常查询 id=1 返回员工 Alice 的信息
测试数字型注入 id=1 and 1=1 和 id=1 and 1=0,两者都返回 Alice,说明 and 关键词可能被过滤或注入类型不是数字型
测试字符型注入:
|
|
返回 Alice
|
|
返回"未找到员工信息",确认存在字符型注入,单引号闭合
使用 || 运算符验证:
|
|
返回所有员工数据,进一步确认注入点
直接 UNION 注入,使用 # 注释:
|
|
返回 1、2、3、4,确认 4 列且全部可回显。本题无 WAF 过滤 获取数据库名:
|
|
数据库名为 user
枚举所有表:
|
|
两张表:employees 和 flag
查看 flag 表的列名:
|
|
两列:id 和 flag
直接 dump flag 表:
|
|
返回 1:flag{9c876dcf-ce40-48b1-999a-0fddc3267d2f},成功获得 flag
复盘:这题是基础的字符型 UNION 注入。与 EZSQL_9(数字型)的区别在于需要单引号闭合和 # 注释。做题时如果数字型 and 1=1/and 1=0 无效,应及时切换到字符型测试
EZSQL_11(字符型 SQL 注入 - WAF 过滤 select + 堆叠注入 + handler 命令读取数据)
访问靶机,页面是一个"Employee Query"员工查询系统,输入 Employee ID 返回 id、name、department、salary 四个字段
正常查询 id=1 返回员工 Alice 的信息
测试注入类型,id=1' and '1'='1 返回正常,id=1' and '1'='0 返回"未找到",确认字符型注入,单引号闭合
测试 UNION 注入 id=-1' union select 1,2,3,4#,返回 Illegal SQL injection,存在 WAF
逐一测试关键词,发现 select 被过滤,但 union、from、where、and、or、show、describe、handler 均可用
尝试各种 select 绕过(大小写、注释、编码、空白字符)均失败
关键发现:分号 ; 可用,支持堆叠注入(stacked queries)!
使用 show 命令获取表结构:
|
|
返回 employees 和 flag 两张表
|
|
返回 flag 表结构:id(int)、flag(varchar(255))
由于 select 被完全过滤,无法使用 UNION 或子查询读取数据。改用 MySQL 的 handler 命令——它是 select 的替代方案,可以直接读取表数据:
|
|
返回 1 和 flag{586be8f6-1f8b-432c-a6a6-7fe1b7a2af2d},成功获得 flag
复盘:这题的核心是 select 关键词被严格过滤,常规绕过全部失效。突破口是堆叠注入 + handler 命令:
handler是 MySQL/MariaDB 特有的表读取命令,语法类似handler 表名 open; handler 表名 read first/next/last- WAF 只拦截了
select,遗漏了handler等替代命令 - 堆叠注入(
;分隔多条语句)使得可以先show tables探测结构,再handler读取数据
EZSQL_12(字符型 SQL 注入 - 报错回显 + extractvalue 错误注入)
访问靶机,页面是一个登录表单,GET 方法提交 username 和 password 两个参数
测试 admin'#,返回 Login successful.,单引号闭合和 # 注释可用
测试 admin')#,返回完整 SQL 报错:near ')#' AND password = 'x'',确认存在括号包裹和报错回显
登录成功页面只显示 “Login successful.” 不显示数据,UNION 注入虽然可行但数据无法回显。改用报错注入
使用 extractvalue 函数触发 XPath 错误,将查询结果嵌入错误信息中:
|
|
返回 XPATH syntax error: '~10.4.13-MariaDB',确认报错注入可用
extractvalue 的错误输出有 32 字符长度限制,需要用 substr 分段提取
枚举表名:
|
|
返回 ~flag,users
查看 flag 表结构:
|
|
返回 ~id,flag
先获取 flag 长度:
|
|
返回 ~42,共 42 个字符
分段提取 flag(每段最多 32 字符):
|
|
返回 ~flag{42710fc0-a
|
|
返回 ~ff5-4e3e-8b53-4
|
|
返回 ~ab3454aab86}
拼接得到 flag{42710fc0-aff5-4e3e-8b53-4ab3454aab86}
复盘:这题的核心是报错注入。页面不显示查询数据,但 SQL 错误信息会被完整输出。extractvalue(1,concat(0x7e,子查询)) 通过 XPath 语法错误将子查询结果泄露到错误信息中。注意 32 字符长度限制,需要配合 substr 分段提取
EZSQL_13(字符型 SQL 注入 - 布尔盲注 - 无数据显示)
访问靶机,页面是一个"Employee Query"员工查询系统,输入 Employee ID 查询
正常查询 id=1 返回"查询成功",但页面不显示任何数据(无 <td> 标签)
测试 id=1 and 1=1 和 id=1 and 1=0,两者响应哈希相同,说明数字型注入无效(and 可能被过滤)
测试字符型注入,id=1' and '1'='1 返回"查询成功",id=1' and '1'='0 返回"未找到员工信息",且两者响应哈希不同。确认存在字符型布尔盲注
无 WAF 过滤,union、select、from 等关键词均可使用,但页面不显示数据,UNION 注入无法回显。采用布尔盲注逐字符提取
判断 flag 长度:
|
|
返回"查询成功",flag 长度为 42 逐字符提取 flag,通过判断响应是"查询成功"(true)还是"未找到"(false)来确定每个字符:
|
|
返回"查询成功" → 第 1 个字符为 ‘f’
编写脚本对每个位置遍历 a-z0-9{}_ 字符集,42 个字符约需 42×35 ≈ 1500 次请求
提取全部 42 个字符,得到 flag{fd8914fb-d709-48a6-85e5-cbb368984e7f}
复盘:这题的核心是布尔盲注——页面不显示查询数据,只能通过响应状态(“查询成功” vs “未找到”)判断条件真假。与报错注入不同,布尔盲注需要逐字符遍历提取数据,请求量较大但逻辑简单。字符型注入需要用 ' 闭合和 # 注释
EZSQL_14(字符型 SQL 注入 - 时间盲注 - 无数据显示)
访问靶机,页面是一个"Employee Query"员工查询系统,输入 Employee ID 查询
正常查询 id=1 返回"The query results have been sent to your email.",页面不显示任何数据
测试 id=1' and '1'='1 和 id=1' and '1'='0,两者响应内容完全相同(MD5 哈希一致),无布尔差异
尝试时间盲注:
|
|
响应延迟 3 秒,确认存在字符型时间盲注,sleep() 函数可用,# 注释有效
无 WAF 过滤,select、union、from、where 等关键词均可正常使用
确认数据库名,sleep 条件为真时延迟 3 秒:
|
|
返回延迟 3 秒,数据库名为 user
编写二分查找脚本,利用 ascii(substr(...)) 配合 sleep(if(...)) 逐字符提取数据
枚举表名,首字符 ASCII > 100(对应 ’e’),确认 employees,flag 两张表:
|
|
查看 flag 表列名,确认 id,flag 两列:
|
|
逐字符提取 flag 值,每个字符通过二分查找(~7 次请求/字符),42 个字符约 300 次请求:
|
|
提取全部 42 个字符,得到 flag{eaaa6edb-1425-4cca-9060-4db595c17008}
复盘:这题的核心是时间盲注——页面不显示查询数据,且布尔条件无响应差异,只能通过 sleep() 函数的延迟来判断条件真假。与布尔盲注相比,时间盲注的每个请求都需要等待 sleep 时间,提取速度更慢。字符型注入需要用 ' 闭合和 # 注释。做 SQL 注入题遇到无数据显示且无布尔差异时,时间盲注是标准解法
EZSQL_15(字符型 SQL 注入 - 宽字节注入绕过 addslashes + UNION 联合查询注入)
访问靶机,页面是一个登录表单,GET 方法提交 username 和 password 两个参数
正常登录 admin / admin123,返回 Welcome admin
测试单引号注入 admin'#,返回 Login failed.,没有报错。尝试各种注入 payload(admin'--+-、admin' or '1'='1、admin'#)全部返回 Login failed.,说明后端对单引号做了转义处理(addslashes() 或 mysql_real_escape_string())
关键发现:admin%bf' or 1=1-- - 返回 Welcome admin,说明存在宽字节注入(Wide-Byte Injection)。原理是 addslashes() 在单引号前添加反斜杠 \(%5c),而 %bf%5c 在 GBK 编码中是一个合法的双字节字符,反斜杠被"吃掉",单引号 %27 逃逸出来
确定列数,依次测试 1~8 列:
|
|
4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列
获取数据库名和版本:
|
|
数据库名为 user,版本为 MariaDB 10.4.13
枚举当前数据库的表:
|
|
只有一张表:flag
查看 flag 表的列名(表名用十六进制 0x666c6167 绕过单引号转义):
|
|
四列:id、name、passwd、secret
直接 dump flag 表:
|
|
返回 1:admin:admin123:flag{6746fd7a-7c7e-4681-a63a-2aa67e5b3d80},成功获得 flag
复盘:这题的核心是宽字节注入(Wide-Byte Injection)。后端使用 addslashes() 对单引号进行转义,但在 GBK 编码环境下,%bf%5c 构成一个合法的双字节字符,吞掉了转义用的反斜杠,使单引号逃逸。绕过方式是在注入的单引号前添加 %bf(或其他高字节如 %81~%fe)。防御时应使用 mysql_set_charset() 设置字符集而非 addslashes(),或统一使用参数化查询(Prepared Statements)