JMCTF2021-GoOSS


JMCTF2021-GoOSS

知识点概要:URL重定向漏洞、SSRF

题目有源码,本文末尾有完整代码

gin框架参考:

https://www.topgoer.com/gin%E6%A1%86%E6%9E%B6/

https://gin-gonic.com/zh-cn/docs/examples/

/vul路由关键代码

上半部分ShouldBindJSON是json数据的绑定,下半部分url.Url相当于从json数据中取出url键值对,前缀如果不是http://127.0.0.1:1234/,就返回403报错

关于json数据绑定可参考:https://gin-gonic.com/zh-cn/docs/examples/binding-and-validation/

然后看fileMidderware中间件关键代码

URL后缀需要是反斜杠/结尾,如果不是反斜杠结尾,就在URL末尾加上反斜杠,然后302重定向访问URL

所以hackbar传递Payload如下(../多一个少一个都不行)

{"url":"http://127.0.0.1:1234//127.0.0.1/?file=/flag&../../../.."}

这个payload跟LineCTF2022-MemoDrive有几分相像之处,个人猜测最后访问的地址应该是http://127.0.0.1/../../../../flag

参考

https://blog.csdn.net/weixin_45805993/article/details/116671017

https://boogipop.com/2023/07/02/BUUCTF%20Web%20WriteUp%207/

完整代码

index.php

<?php
// php in localhost port 80
readfile($_GET['file']);
?>

main.go

package main

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"github.com/gin-gonic/gin"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"
)

type File struct {
	Content string `json:"content" binding:"required"`
	Name string `json:"name" binding:"required"`
}
type Url struct {
	Url string `json:"url" binding:"required"`
}

func md5sum(data string) string{
	s := md5.Sum([]byte(data))
	return hex.EncodeToString(s[:])
}

func fileMidderware (c *gin.Context){
	fileSystem := http.Dir("./files/")
	if c.Request.URL.String() == "/"{
		c.Next()
		return
	}
	f,err := fileSystem.Open(c.Request.URL.String())
	if f == nil {
		c.Next()
	}
	//
	if err != nil {
		c.Next()
		return
	}
	defer f.Close()
	fi, err := f.Stat()
	if  err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if fi.IsDir() {

		if !strings.HasSuffix(c.Request.URL.String(), "/") {
			c.Redirect(302,c.Request.URL.String()+"/")
		} else {
			files := make([]string,0)
			l,_ := f.Readdir(0)
			for _,i := range l {
				files = append(files, i.Name())
			}

			c.JSON(http.StatusOK, gin.H{
				"files" :files,
			})
		}


	} else {
		data,_ := ioutil.ReadAll(f)
		c.Header("content-disposition", `attachment; filename=` + fi.Name())
		c.Data(200, "text/plain", data)
	}

}

func uploadController(c *gin.Context) {
	var file File
	if err := c.ShouldBindJSON(&file); err != nil {
		c.JSON(500, gin.H{"msg": err})
		return
	}

	dir := md5sum(file.Name)

	_,err:= http.Dir("./files").Open(dir)
	if err != nil{
		e := os.Mkdir("./files/"+dir,os.ModePerm)
		_, _ = http.Dir("./files").Open(dir)
		if e != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": e.Error()})
			return

		}

	}
	filename := md5sum(file.Content)
	path := "./files/"+dir+"/"+filename
	err = ioutil.WriteFile(path, []byte(file.Content), os.ModePerm)
	if err != nil{
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(200, gin.H{
		"message": "file upload succ, path: "+dir+"/"+filename,
	})
}
func vulController(c *gin.Context) {

	var url Url
	if err := c.ShouldBindJSON(&url); err != nil {
		c.JSON(500, gin.H{"msg": err})
		return
	}

	if !strings.HasPrefix(url.Url,"http://127.0.0.1:1234/"){
		c.JSON(403, gin.H{"msg": "url forbidden"})
		return
	}
	client := &http.Client{Timeout: 2 * time.Second}

	resp, err := client.Get(url.Url)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer resp.Body.Close()
	var buffer [512]byte
	result := bytes.NewBuffer(nil)
	for {
		n, err := resp.Body.Read(buffer[0:])
		result.Write(buffer[0:n])
		if err != nil && err == io.EOF {

			break
		} else if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
	}
	c.JSON(http.StatusOK, gin.H{"data": result.String()})
}
func main() {
	r := gin.Default()
	r.Use(fileMidderware)
	r.POST("/vul",vulController)
	r.POST("/upload",uploadController)
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	_ = r.Run(":1234") // listen and serve on 0.0.0.0:8080
}

文章作者: wa0er
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 wa0er !
评论
  目录