前言
最近一直在青岑靶场做web入门(菜到只能做这个),今天分享一下自己的wp。(含ai成分)
靶场地址:https://ctf.qingcen.net/
BASIC
BASIC
根据题目提示,f12查看源码
发现flag
BASIC_1
尝试f12查看源码,结果给封了
shift+ctrl+i打开开发者工具或者右上角的更多工具
发现flag
BASIC_2
打开网页发现只有“提交反馈”和“返回官网”能够交互“返回官网”内没有有效信息,查看源码也没有发现有用内容
使用抓包工具抓取提交反馈的请求,发现请求体中有个is_admin=0
尝试修改is_admin=1,进行发包,返回flag
BASIC_3
访问靶场
ctrl+u发现可疑的url js/key.js
跳转js/key.js,发现是JSfuck编码
直接在控制台输入JSfuck编码,解码后发现密码
发现flag
BASIC_4
访问靶场
依旧ctrl+u 发现可疑的url
跳转/static/main.js
这是一段JavaScript混淆代码,利用ASCII码对关键字符串进行编码
解码后:
|
|
代码分析: String.fromCharCode()函数:将ASCII数字数组转换为字符串
逻辑是点击提交按钮->checkInvite()执行->获取输入值->与QCCTF_VIP_2026相同->执行POST /flag,返回flag
BASIC_5
访问靶场
老规矩ctrl+u 发现可疑url /static/main.js
关键代码分析
分数 currentScore 完全在前端计算和存储,服务端 /claim 接口未验证分数真实性,直接信任前端提交的数据。
利用burp抓包,POST请求,发现"data":“eyJzY29yZSI6MH0=” 即{“score”:0} 需要score为1000
将{“score”:1000} 编码为base64 eyJzY29yZSI6MTAwMH0=
返回"data":“eyJmbGFnIjogImZsYWd7YWVmNTBmY2QtMmQ2MS00NzM3LTg0YTctYWY5YTc2ZWE5MTExfSJ9”
发现flag
BASIC_6
访问靶场
ctrl+u 未发现可用信息
f12打开开发者工具 网络页面 发现X-Flag:flag{8c931b47-fba4-4659-88eb-5bb442ec0ef2}
BASIC_7
访问 http://docker.qingcen.net:45040/,看到一个 3×3 拼图游戏,需要把 9 块拼图拖拽到正确位置后点击"提交"。
关键发现
查看页面源码,核心逻辑在 <script> 中:
- CSRF Token:每个会话会生成一个 CSRF token(const csrf = “65bdfa841e802c39510e4d59eb5c1820”)
- 提交逻辑:拼图完成后,JS 会向 /complete.php 发送 POST 请求:
fetch(’/complete.php’, {
method: ‘POST’,
body: new URLSearchParams({ csrf, solved: ‘1’ })
}); - 重定向链:提交成功后,响应头显示 302 跳转链:- /complete.php → /vault.php → /decoy.php
关键点:/vault.php 虽然返回了 302 Redirect 到 /decoy.php,但 响应体仍然包含 flag 内容!
解题过程
|
|
总结
这道题的核心考点是 HTTP 302 重定向与响应体的关系。题目利用两层误导:
- 正常浏览器会跟随重定向,先到 vault.php,再到 decoy.php(诱饵页面),用户只看到诱饵
- 但实际上 vault.php 虽然发了 302,响应体里已经包含了 flag —— 用 curl 不跟随重定向(或只跟随到vault.php)就能直接看到
BASIC_8
访问靶场
根据题目提示,寻找源代码文件,我们进行目录爆破
查看字典
发现只有index.php和index.phps状态码正常
访问index.phps
分析一下,发现只要GET请求参数a=QCyYdS 就会返回flag文件
BASIC_9
访问靶场
根据提示挨个试一试,index.phps没有,robots.txt有信息
访问获得flag
BASIC_10
访问靶场
没发现有用信息,进行目录扫描(这里用的是dirsearch)
常用格式:python3 dirsearch.py -u <目标URL> [选项];
-e 指定扩展名(如 -e php,html,txt);
-w 指定字典文件,不指定则用默认字典;
-x 排除状态码(如 -x 403,404)可减少无效结果;
扫描结果会列出存在的路径与状态码,便于发现隐藏文件或后台。
扫出[04:18:53] 200 - 177B - /sitemap.xml 进行访问
又发现/wqw.php,访问
发现不是管理员权限,用hackbar进行修改user=admin,发送请求,获得flag
BASIC_11
访问靶场
没有发现有用信息,robots.txt里是假的
继续目录扫描(这里用的是dirsearch) 字典httppath.txt
扫出fl4g.php,访问,获得flag
BASIC_12
访问靶场
点击各个文档,可以发现参数id发生变化,对参数id进行爆破
可以发现id=121的长度和别的不一样且不在显示的几个文档中,访问
找到flag
BASIC_13
访问靶机
发现需要密码,且username为admin,一般是弱口令,尝试admin123,获得flag
BASIC_14
访问靶场
虽然指定/admin_secret.txt 就能获得flag,但是长度不满足<17
代码中 $flag = fopen(’/admin_secret.txt’, ‘r’) 打开了目标文件,这会在进程中创建一个文件描述符。
在 Linux 中: fopen() 打开的文件会分配文件描述符,文件描述符可通过 /proc/self/fd/3~10 目录访问
?filename=/proc/self/fd/5 返回flag
EZREQUEST
EZREQUEST
访问靶场
根据GET请求参数a=QCCTF http://docker.qingcen.net:40964/?a=QCCTF
打开hackbar,Use POST-method b=yyds
EZREQUEST_1
访问靶场
GET请求参数a=a
打开Hackbar Use POST method b=b
添加请求头X-Forward-For: 127.0.0.1 绕过IP限制
添加请求头User-Agent:QingcenSafe
添加请求头Via:xujinyingcangming.top
添加请求头Cookie:user=admin
EZPHP
EZPHP
访问靶机
分析条件 使$a存在且弱类等于0 is_numeric($b)返回false $b>2026
绕过方法
$a=‘a’(字符串与0比较转为0)
$b=‘2027a’(含字母,不是纯数字)
‘2027a’ 与数字比较时转为 2027
拼接一下 最终Payload : ?a=a&b=2027a
EZPHP_1
访问靶机
分析条件
array_search(“QCCTF”, $qc) 使用弱类型比较==(0 == “QCCTF”)为true
$key === 1 返回的键名必须全等于1
构造payload qc=[null,0]
索引 0:null → null == “QCCTF” 为 false
索引 1:0 → 0 == “QCCTF” 为 true ✓
返回键名:1
1 === 1 → true → 输出 flag
EZPHP_2
访问靶机
分析条件
$qc[“n”] 存在、是数组、非空 “n”: [0]
array_search(“QCCTF”, $qc) 找到值 利用 0 == “QCCTF” 为 true
array_search(“QCyyds”, $qc[“n”]) 找到值 利用 0 == “QCyyds” 为 true
$qc[“n”] 中不能有真正的 “QCyyds” 用 0 代替字符串
构造payload qc={“n”:[0],“a”:0}
EZMD5
EZMD5
访问靶机
分析条件
比较方式 ==弱类型比较
$admin_hash 以 0e 开头的字符串
弱类型特性 0e… 被当作科学计数法,值为 0
只要找到MD5值以0e开头的字符串
payload: QC=QNKCDZO
EZMD5_1
访问靶机
分析条件
$a!=$b 两个值不相等
md5($a)==md5($b) MD5 弱类型相等
1:找到两个0e开头的字符串
“0e…"==“0e…” → 0 == 0 → true
payload: a=QNKCDZO&b=240610708
2:数组绕过
md5(array) 返回 NULL,NULL == NULL 为 true
payload:a[]=1&b[]=2
EZMD5_2
访问靶机
分析条件
|
|
EZMD5_3
访问靶机
$a != $b 两个值不相等
md5($a) === md5($b) MD5 全等比较
不影响使用数组绕过
payload: ?a[]=1&b[]=2
EZMD5_4
访问靶机
分析条件
GET 参数 QC
MD5 值最后6位等于 d54e23
MD5后六位碰撞
|
|
找到字符串:gsdj5
payload: ?QC=gsdj5
EZMD5_5
访问靶机
分析条件
$a != $b:两个值不相等
sha1($a) == sha1($b):两个sha1值相等
sha1() 处理数组返回 false:当传入数组时,sha1() 函数会报错并返回 false
依旧可以使用数组绕过 只要两个数组不一样即可,且sha1都返回false
payload: ?a[]=1&b[]=2
EZMD5_6
访问靶机
分析条件
和上一题类似,只是从弱类型比较变成强类型比较
payload: ?a[]=1&b[]=2
EZMD5_7
访问靶机
和EZMD5_3源码一样 尝试数组绕过 ?a[]=1&b[]=2 返回错误
看一下php版本 PHP 8.3 数组绕过错误
直接找MD5值相同的咯 a=d131dd02c5e6eec4693d9a0698aff95c2fcab58712467eab4004583eb8fb7f8955ad340609f4b30283e488832571415a085125e8f7cdc99fd91dbdf280373c5bd8823e3156348f5bae6dacd436c919c6dd53e2b487da03fd02396306d248cda0e99f33420f577ee8ce54b67080a80d1ec69821bcb6a8839396f9652b6ff72a70
b=d131dd02c5e6eec4693d9a0698aff95c2fcab50712467eab4004583eb8fb7f8955ad340609f4b30283e4888325f1415a085125e8f7cdc99fd91dbd7280373c5bd8823e3156348f5bae6dacd436c919c6dd53e23487da03fd02396306d248cda0e99f33420f577ee8ce54b67080280d1ec69821bcb6a8839396f965ab6ff72a70
这里值太长了 记得复制完整哟-。-
这是一组值不同 但是MD5值相同的字符串 将这两个二进制串通过 URL 编码后作为 GET 参数 a 和 b 传递,成功绕过了 === 严格比较,获取到 flag
EZINFOLEAK
EZINFOLEAK
访问靶场
依次点开日志查看信息
发现base64编码
secret_file_b64=Zmw0Zy50eHQ=
解码后为 secret_file_b64=fl4g.txt
路径穿越获得flag ../../../fl4g.txt
EZINFOLEAK_1
访问靶机
和上一题一样
发现base64编码
secret_file_b64=Zmw0Zy50eHQ=
解码后为 secret_file_b64=fl4g.txt
路径穿越 ../../../fl4g.txt 但是没有flag回显 发现过滤了../
利用双写绕过 ….//….//….//fl4g.txt
EZINFOLEAK_2
访问靶机
没发现有用信息 怀疑flag在环境变量中
环境变量存储敏感信息:Docker容器常将Flag存放在环境变量中
/proc/1/environ:存储PID=1进程的环境变量
利用路径穿越 /../../../../../../proc/1/environ
EZINFOLEAK_3
访问靶机
尝试常见PHP诊断路径 phpinfo.php
在页面搜索flag
EZINFOLEAK_4
访问靶场
是一个烟花发射器页面,没有可用信息
用dirsearch扫描一下
发现是.git源码泄露 用GitHack还原源码
获得flag
EZINFOLEAK_5
还是放烟花
用dirsearch扫描一下
感觉还是.git源码泄露
用GitHack还原源码
发现HKBRLMlv.php
用POST请求就可以获得flag
EZINFOLEAK_6
依旧放烟花
没信息用dirsearch扫描一下
发现是.git源码泄露
由于GitHack只能还原最新版本
所以用脚本自动还原所有版本
脚本
获得flag
EZINFOLEAK_7
访问靶场
既然是信息泄露,应该不是sql注入的做法
没有信息,使用dirsearch扫描一下
/.svn/entries: 这是老版本 SVN 的特征文件,证明存在泄露。
存在.svn泄露
下载wc.db文件,这是一个SQLite数据库
使用 SQLite Viewer 在线打开 wc.db
查看 NODES 表,找到文件名和 checksum
SVN pristine 文件路径格式:
/.svn/pristine/[前2位]/[完整sha1].svn-base
访问flag.php文件
EZINFOLEAK_8
访问靶机
没什么有用信息,用dirsearch扫描
发现.hg信息泄露
Mercurial 是分布式版本控制系统,类似于 Git。当 .hg 目录被意外暴露在 Web 服务器上时,攻击者可以获取源代码。
使用dvcs-ripper
获得flag
EZINFOLEAK_9
访问靶机
查看注释
当使用 vim 编辑文件时,会创建 .filename.swp 交换文件用于崩溃恢复。如果服务器上的交换文件未被删除且可访问,攻击者可以恢复文件内容。
直接访问
EZINFOLEAK_10
访问靶机
查看注释
虽然 flag.txt 已被删除(访问返回 “flag.txt has been deleted”),但 vim 的交换文件 .flag.txt.swp 仍然存在于服务器上,包含了被删除文件的原始内容。
输入http://docker.qingcen.net:42128/.flag.txt.swp 下载flag.txt.swp文件
利用strings .flag.txt.swp 提取内容
EZINFOLEAK_11
访问靶机
当前平台仅支持通过 macOS Finder 进行文件上传
.DS_Store 是 macOS 系统自动生成的隐藏文件,用于存储文件夹的显示选项和元数据。当用户使用 macOS Finder 上传文件到服务器时,.DS_Store 文件可能被一起上传,导致目录结构泄露
直接访问
EZINFOLEAK_12
EZCMD
EZCMD
访问靶机
分析条件
escapeshellcmd() 的局限性:
该函数用于转义shell元字符,防止命令注入
但它不阻止执行单个命令
只转义 ; | & $ ( ) < > 等特殊字符
不影响ls,cat等命令
查看根目录
cmd=ls ../
cmd=ls ../../
cmd=ls ../../../
cmd=cat ../../../flag
EZCMD_1
访问靶机
分析条件
用户输入 $cmd 直接拼接到 system() 函数中
可以通过命令分隔符注入任意命令
cmd=; ls /
cmd=; cat /flag
EZCMD_2
访问靶机
分析条件
system($cmd.” >/dev/null 2>&1") 用户输入在前,输出被重定向
可以利用注释符#绕过
cmd=cat /flag #
EZCMD_3
访问靶机
分析条件
禁止空格字符
输出重定向到 /dev/null
%09 URL编码的Tab,Shell解析为分隔符
%0a URL编码的换行,使命令独立于重定向
cmd=cat%09/flag%0a
EZCMD_4
访问靶机
页面名为robot 我们访问robots.txt
访问4atP5Aup.php
分析条件,看看过滤了哪些命令
escapeshellcmd()会转义shell命令中的特殊字符,防止命令注入
我们需要读取flag文件
cmd=cat /flag->cmd=dd if=/flag->cmd=a=fl;b=ag;dd if=/$a$b->cmd=eval a=fl;b=ag;dd if=/$a$b
EZCMD_5
访问靶机
分析条件
把所有的字母全部过滤,典型的无字母命令执行
/???/?? 匹配 /bin/ca (为什么不直接用cat呢,直接用???匹配不到cat,通配符 ? 不会像命令名那样自动搜索 PATH,必须指定完整路径!)
$’\164’ 是字母 t 的八进制表示(ASCII码116)
/????.??? 匹配 /flag.txt
最终执行: /bin/cat /flag.txt
EZCMD_6
访问靶机
分析条件
典型的一句话木马
qc=system(’ls /’);
qc=system(‘cat /flag’);
EZCMD_7
访问靶机
分析条件
eval(’$qc’) 将字符串转换为PHP代码执行
仅过滤flag 没有过滤ls
发现flag的路径 /flag
利用chr() 函数拼接字符串
echo file_get_contents(’/flag’); 读取并输出
EZCMD_8
访问靶机
分析条件
过滤了system() 和 flag
利用shell_exec() 代替 system()
利用strrev()反转字符串绕过flag
?qc=echo shell_exec(’ls /’);
发现路径
?qc=readfile(strrev(‘galf/’));
EZCMD_9
访问靶场
分析条件
禁用空格和system()
scandir(目录路径) 列出目录中的所有文件和文件夹,返回数组 搭配print_r() 打印数组
?qc=print_r(scandir(’/’));
发现flag的路径 /flag
?qc=readfile(’/flag’); 不含过滤字符
EZCMD_10
访问靶机
分析条件
仅过滤 ; 利用?>绕过
payload ?qc=echo file_get_contents(’/flag.txt’)?>
EZCMD_11
访问靶机
分析条件
qc 参数会进入 eval($qc),只过滤了分号 ; flag 在 flag.php 里
直接构造highlight_file(‘flag.php’)?> 读取flag即可
EZCMD_12
访问靶机
分析条件
可以看出这题本质是 eval() 代码执行,只是做了一层黑名单过滤.
被过滤的字符很多,尤其关键的是:
|
|
这意味着:不能直接写 flag.php,因为 . 被过滤。不能方便地写字符串、数组下标、拼接这些常规做法。但函数名、括号、分号、下划线都还能用,所以可以继续走纯 PHP 利用.
先用目录函数确认当前目录文件:
|
|
目标是不直接写出 flag.php,而是从 scandir() 返回结果里间接取出它.
构造:
|
|
原因是:
scandir(getcwd()) 返回 [’.’ , ‘..’ , ‘flag.php’ , ‘index.php’] array_reverse() 后变成 [‘index.php’ , ‘flag.php’, ‘..’ , ‘.’] next() 取第二个元素,正好就是 flag.php
虽然 next() 正常要求传引用变量,但这里 error_reporting(0) 把 warning 压掉了,实际仍然能拿到值.
最终payload:
|
|
EZCMD_13
访问靶机
分析条件
preg_replace() 使用了 /e 修饰符,在 PHP 5.x 里,/e 会把替换内容当成 PHP 代码执行,利用思路是让 \1 替换成我们可控的内容,再借助双引号里的 ${…} 字符串插值触发函数执行
|
|
可以成功弹出 phpinfo(),说明利用成立
接着尝试命令执行 PHP 5.6 的特性:未定义常量会被当成字符串.
|
|
回显当前目录只有index.php 说明flag不在当前目录,继续列根目录
|
|
也就是 ls / 回显里可以看到flag 说明flag在flag文件中 最后读出flag
|
|
等价执行了 cat /flag
EZCMD_14
访问首页 直接把源码高亮了出来
|
|
qc 参数里只要没有字母和数字,就会被 eval() 执行。也就是说,过滤规则只拦了 [a-zA-Z0-9],但没有拦住 PHP 里的其他语法能力。
PHP 支持字符串按位异或,因此可以只用符号拼出字母。比如:
|
|
结果就是 “a”,因为 33 ^ 64 = 97。同理还能继续构造别的字符,例如:
|
|
这样就能在完全不出现字母数字的情况下,拼出函数名和命令字符串。另外,变量名里的下划线 _ 不在过滤范围内,所以可以直接用 $_、$__ 这类变量名 最终payload
|
|
第一行拼出的是 system,第二行拼出的是 cat /flag,第三行相当于:system(“cat /flag”);
把它放进 qc 参数并做 URL 编码后发送即可
EZCMD_15
访问首页 源码展示
|
|
看到这里基本就已经确定思路了。程序会先用 highlight_file(FILE) 把当前文件源码输出出来,然后判断是否存在 qc 参数;如果存在,就直接把 $_GET[‘qc’] 送进 exec() 执行。整个过程没有任何过滤,也没有白名单限制
不过这里有一个小细节需要注意:exec() 和 system() 不一样,它默认不会把命令执行结果直接回显到页面上,所以即使命令成功执行,页面看起来也还是源码。这种情况下,最简单的办法就是把命令输出重定向到 Web 目录下的文件里,再通过浏览器访问这个文件读取结果
首先可以用一条简单命令验证是否真的能够执行系统命令,例如:
|
|
这里的意思是执行 id,然后把输出写入 /var/www/html/1.txt。接着访问:http://docker.qingcen.net:30911/1.txt
这说明命令执行已经成功,并且当前 Web 服务权限为 www-data,接下来就是读取 flag。常见情况下 flag 会放在根目录 /flag,所以直接执行:
|
|
这里的 %20 是空格的 URL 编码。命令会把 /flag 的内容写入 /var/www/html/f1.txt。然后访问:
EZCMD_16
访问首页 源码展示
|
|
第一眼看上去像是只是“创建了一个匿名函数”,似乎没有调用它。但这里真正危险的点在于 create_function()。它在 PHP 7.4 中虽然已经废弃,但本质上仍然会把传入的代码字符串丢进 eval() 里处理,所以这里其实是一个代码注入点
程序拼接出来的代码逻辑相当于
|
|
如果我们传入正常内容,比如:qc=1 这当然没问题。但如果我们传入:
|
|
拼接后就会变成:
|
|
这样一来,} 先把匿名函数体提前闭合,后面的 system(‘id’); 就跳到了函数外部,在 eval() 解析阶段直接执行,最后再用 // 注释掉后面原本多出来的 ; 和结束部分。也就是说,虽然程序没有显式调用匿名函数,我们仍然可以通过闭合函数体的方式把恶意代码插进去并立刻执行
先用下面这个 payload 验证命令执行
|
|
URL 编码后实际访问的是:
|
|
回显中可以直接看到uid=33(www-data) gid=33(www-data) groups=33(www-data),说明代码执行已经成功。接下来直接读取 flag。常见路径先尝试 /flag
payload
|
|
成功获得flag
OK看到这里,此章就到此结束了,我们下一篇再见,各位佬。