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
}