背景

查找资料,学习golang的代码审计。漏洞类型是相似的,主要是学习golang漏洞代码中的表现形式。

Vulnerability-goapp是模拟漏洞的一个靶场。

搭建

作者提供docker,不建议使用源码(作者数据库结构没给,而且数据库连接一大堆地方。。。。)。

1
2
3
4

git clone https://github.com/Hardw01f/Vulnerability-goapp.git
cd Vulnerability-goapp
docker-compose up -d

代码分析

目录遍历

访问main.go入口文件,看见如下代码:

1
2
// http.Handle 路由  http.StripPrefix 进行路径分层(处理映射)  http.FileServer 显示当亲路径
http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/"))))

emmm其实也不算什么问题,就是单纯记录下:

image

反射型XSS - 【一】

mian.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
http.HandleFunc("/", sayYourName) // 当前网址执行 sayYourName 函数
...
...
func sayYourName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() // 解析表单
fmt.Println(r.Form)
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println("r.Form", r.Form)
fmt.Println("r.Form[name]", r.Form["name"])
var Name string
for k, v := range r.Form { // 遍历提交数据键值对
fmt.Println("key:", k)
Name = strings.Join(v, ",")
}
fmt.Println(Name)
fmt.Fprintf(w, Name) // 输出,存在XSS
}
...

image

存储型XSS - 【一】

在login界面找到注册请求是/new,在main.go中查找路由

1
http.HandleFunc("/new", register.NewUserRegister)

跟进函数 /pkg/register.go

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

func NewUserRegister(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
t, _ := template.ParseFiles("./views/public/new.gtpl")
t.Execute(w, nil)
} else if r.Method == "POST" {
r.ParseForm()
fmt.Println(r.FormValue("mail"))
fmt.Println(r.FormValue("name"))
fmt.Println(r.FormValue("age"))
fmt.Println(r.FormValue("passwd"))
if CheckUserDeplicate(r.FormValue("mail")) { // 检查邮件是否重复

if RegisterUser(r) { // 跟进 RegisterUser

...
...

func RegisterUser(r *http.Request) bool {
db, err := sql.Open("mysql", "root:rootwolf@tcp(mysql)/vulnapp") // 连接数据库
if err != nil {
log.Fatal(err)
}

age, err := strconv.Atoi(r.FormValue("age"))
if err != nil {
fmt.Println(err)
return false
}
// 向表格中插入新的行 这里我们写入什么数据就直接执行
_, err = db.Exec("insert into user (name,mail,age,passwd) value(?,?,?,?)", r.FormValue("name"), r.FormValue("mail"), age, r.FormValue("passwd"))
if err != nil {
fmt.Println(err)
return false
}
return true
}

image

触发点在top路由:

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
47
48

...

http.HandleFunc("/top", showUserTopPage)

...
...


func showUserTopPage(w http.ResponseWriter, r *http.Request) {
userName, sessionID, userID, err := cookie.GetUserIDFromCookie(r) // GetUserIDFromCookie 从数据库中请求
if err != nil {
fmt.Println(err)
}
if cookie.CheckSessionsCount(userID, sessionID) {
login.StoreSID(userID, sessionID)
} else {
fmt.Println("not register sessionID")
}
if sessionID == "" {
fmt.Println("sid not exist")
t, _ := template.ParseFiles("./views/public/error.gtpl")
t.Execute(w, nil)
} else {
if r.Method == "GET" {
if userID != 0 {
uid := strconv.Itoa(userID)
cookieUserID := &http.Cookie{
Name: "UserID",
Value: uid,
}

deleAdminID := &http.Cookie{
Name: "adminSID",
Value: "",
}

http.SetCookie(w, cookieUserID)
http.SetCookie(w, deleAdminID)
p := Person{UserName: userName} // 没有过滤,
t, _ := template.ParseFiles("./views/public/top.gtpl")
t.Execute(w, p)
}
} else {
http.NotFound(w, r)
}
}
}

以此类推,后台后很多的触发点。

存储型XSS - 【二】

后台用户编辑处存在XSS,路由为/profile/edit/confirm

1
http.HandleFunc("/profile/edit/confirm", user.ShowEditConfirm)

跟进 usermanager.go

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

func ShowEditConfirm(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
if cookie.CheckSessionID(r) {
fmt.Println(r.Referer())
userName := r.FormValue("username")
age := r.FormValue("age")
mail := r.FormValue("mail")
fmt.Println("mail : ", mail)
address := r.FormValue("address")
animal := r.FormValue("animal")
word := r.FormValue("word")
userAge, err := strconv.Atoi(age)
if err != nil {
fmt.Printf("%+v\n", err)
}
_, _, uid, err := cookie.GetCookieValue(r)
if err != nil {
fmt.Printf("%+v\n", err)
http.NotFound(w, nil)
return
}

userMail, _, err := GetUserInfos(uid)
if err != nil {
fmt.Printf("%+v", err)
}

userImage, _, _, _, err := GetUserMoreDetails(uid)
if err != nil {
fmt.Printf("%+v", err)
http.NotFound(w, nil)
return
}

u := User{UserName: userName, Mail: userMail, Age: userAge, Image: userImage, Address: address, Animal: animal, Word: word}

t, _ := template.ParseFiles("./views/users/users_confirm.gtpl")
t.Execute(w, u) // 没有过滤 直接引用
}
} else {
http.NotFound(w, nil)
}
}

image

存储型XSS - 【三】

search.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

func SearchPosts(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
searchWord := r.FormValue("post")
fmt.Println("value : ", searchWord)
...
...
p := Results{StringResults: removedExtraSpace}

fmt.Println(p)

t, _ := template.ParseFiles("./views/search/searchpost.gtpl")
t.Execute(w, p)

} else {
http.NotFound(w, nil)
}
}

image

SQL注入 - 【一】

漏洞形成原理都是相通的,参数过滤没做好导致能执行sql语句。

跟进search.go

1
2
3
4
5
6
7
8
9
10
11

func SearchPosts(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
searchWord := r.FormValue("post")
fmt.Println("value : ", searchWord)
testStr := "mysql -h mysql -u root -prootwolf -e 'select post,created_at from vulnapp.posts where post like \"%" + searchWord + "%\"'"

fmt.Println(testStr)

testres, err := exec.Command("sh", "-c", testStr).Output()
...

使用命令执行sql语句,searchWord 并且没有过滤。

1
123%" and sleep(10)#

跟踪数据,存在于搜索界面:

image

命令执行 - 【一】

这里大部分数据库查询使用的是exec.Command函数在命令行执行,如果参数可控就可能造成命令执行,跟进search.go

1
2
3
4
5
6
7
8
9
10
11

func SearchPosts(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
searchWord := r.FormValue("post")
fmt.Println("value : ", searchWord)
testStr := "mysql -h mysql -u root -prootwolf -e 'select post,created_at from vulnapp.posts where post like \"%" + searchWord + "%\"'"

fmt.Println(testStr)

testres, err := exec.Command("sh", "-c", testStr).Output()
...

构造命令执行语句,因为没回显,这里用DNSlog测试:

1
"'|ping alxaep.dnslog.cn|'"

执行的时候需要编码一次(其他地方漏洞类似,就不重复举例子了):

image

任意文件上传

main.go

1
http.HandleFunc("/profile/edit/upload", uploader.UploadImage)

跟进

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

func UploadImage(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
err := r.ParseMultipartForm(32 << 20) // 解析请求数据
if err != nil {
fmt.Printf("%+v\n", err)
return
}
if cookie.CheckSessionID(r) {
file, handler, err := r.FormFile("uploadfile") // 解析数据格式,返回文件内容名字错误信息
if err != nil {
fmt.Printf("%+v\n", err)
return
}
defer file.Close()
f, err := os.OpenFile("./assets/img/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Printf("%+v\n", err)
return
}
defer f.Close()
io.Copy(f, file) // 写入内容
UpdateDatabase(r, handler.Filename)

http.Redirect(w, r, "/profile", 301)
}
} else {
http.NotFound(w, nil)
}
}

上传文件名没有进行审核,导致任意文件上传漏洞

image

文件也可以通过“../../”形式进行跨目录上传。

CSRF - 【一】

存在很多处,比如密码修改和提交留言的地方,之类以提交留言为例子,使用burp生成CSRF的POC进行测试

image

跟进 post.go 中的 ShowTimeline 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

func ShowTimeline(w http.ResponseWriter, r *http.Request) {
...
} else if r.Method == "POST" {
if cookie.CheckSessionID(r) { // 只是检查了cookie,没有防止CSRF的地方
_, _, uid, err := cookie.GetCookieValue(r)
if err != nil {
fmt.Printf("%+v\n", err)
http.NotFound(w, nil)
return
}

fmt.Println(uid, r.FormValue("post"))

postText := r.FormValue("post")

fmt.Println(reflect.TypeOf(postText))

StorePost(uid, postText)

http.Redirect(w, r, "/timeline", 301)
...

CSRF - 【二】

密码修改处,基本逻辑是先在 /profile/changepasswd 中验证两次输入是否一致,再进入 /profile/compchangepasswd 写入,其中在 ConfirmPasswdChange 函数中对 Referer 进行了限制,但是并不影响CSRF的执行,这里主要写的就有毛病,按照正常的方式也修改不了密码…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func ConfirmPasswdChange(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
if cookie.CheckSessionID(r) {
if r.Referer() == "http://localhost:9090/profile/changepasswd" {
_, _, uid, err := cookie.GetCookieValue(r)
if err != nil {
fmt.Printf("%+v\n", err)
}

newPasswd := r.FormValue("passwd")
confirmPassword := r.FormValue("confirm")

fmt.Println(newPasswd, confirmPassword, uid)

db, err := sql.Open("mysql", "root:rootwolf@tcp(mysql)/vulnapp")
if err != nil {
fmt.Printf("%+v", err)
}
defer db.Close()

_, err = db.Exec("update vulnapp.user set passwd = ? where id = ?", newPasswd, uid)
...

其他

还存在一些越权漏洞,没什么学习价值就没写(主要参数都在cookie里面可以进行随意改动导致的越权)。

虽然代码乱糟糟的,但是总而言之还是学到了一些东西的,算是golang审计的一个小入门。

参考资料/学习

  1. Vulnerability-goapp-Go语言漏洞平台审计过程
  2. Vulnerability-goapp