← Back to all posts
Code Auditing4 min read

Critical Vulnerabilities in Go Code Auditing (SQLi\CMD\SSRF)

A Go-based web vulnerability lab for reinforcing knowledge through hands-on practice, covering vulnerability code analysis and remediation for SQL injection, command execution, and SSRF in the Gin framework.

#Golang#programming language#backend
Read in Chinese→

0x01 Critical Vulnerabilities in Go Code Auditing

A web vulnerability lab written in Go, created as a hands-on exercise to reinforce relevant knowledge.

https://github.com/godzeo/go-gin-vul

The entire web application uses the Gin framework and is adapted from go-gin-Example. There is no frontend framework, only Swagger, so just send requests directly.

0x02 Vulnerability Code Analysis and Remediation

0x01 SQLi

One of the most common coding issues in practice is SQL injection caused by concatenating column or table names after an ORDER BY clause.

Because table and column names cannot be used in parameterized queries, using an allowlist or escaping is recommended.

0x011 Common Incorrect Concatenation

This mainly involves joining strings using methods such as fmt.Sprintf() and buffer.WriteString().

Simply put, concatenating first and querying afterward is problematic.

db.Select(xxx).First(&user) 

db.Where(fmt.Sprintf("name = '%s'", xxx)).Find(&user) 

db.Raw("select name from " + xxx).First(&user) 

db.Exec("select name from " + xxx).First(&user) 

0x012 Common Cases Where Concatenation Is Required in Business Logic

For developers, SQL injection remediation mainly involves two scenarios:

  1. Concatenation of regular values;
  2. Concatenation of table or column names.

The reason is covered in a previous article. In short, using prepared statements for column names will cause them to become invalid.

Collapsible code and request packet

routers/api/unAuth/sql.go

db.Order(xxxx).First(&user)

For column names, the safest remediation is to use an allowlist.

validCols := map[string]bool{"col1": true, "col2":true}

if _, ok := validCols[xxxx]; !ok {
fmt.Println("illegal column")
return
}
db.Order(xxxx)
POST /sql/login HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 106

user=user&password=123456 AND EXTRACTVALUE(9509,CONCAT(0x5c,(SELECT user from blog.blog_login LIMIT 0,1)))

After allowlist remediation

POST /sql/loginSafe HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 106

user=user&password=123456 AND EXTRACTVALUE(9509,CONCAT(0x5c,(SELECT user from blog.blog_login LIMIT 0,1)))

Command Execution

When using system-call functionality such as exec,
an allowlist should be used to restrict the commands that can be executed.
Do not use bash or sh.

Collapsible code and request packet

Direct concatenation

routers/api/unAuth/cmd.go

    ipaddr := c.PostForm("ip")
    Command := fmt.Sprintf("ping -c 4 %s", ipaddr)
    output, err := exec.Command("/bin/sh", "-c", Command).Output()
   if err != nil {
      fmt.Println(err)
      return
   }
   c.JSON(200, gin.H{
      "success": output,
   })
POST /api/vul/cmd HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 23

ip=127.0.0.1 | echo zeo

// Parameter-binding concatenation

type MyMsg struct {
Domain   string `json:"domain"`
Password string `json:"password"`
}


// ---> 声明结构体变量
var a MyMsg
// ---> 绑定数据
if err := c.ShouldBindJSON(&a); err != nil {
c.AbortWithStatusJSON(
http.StatusInternalServerError,
gin.H{"error": err.Error()})
return
}
output, _ := exec.Command("/bin/bash", "-c", "dig "+a.Domain).CombinedOutput() // python -c is also vulnerable
println(output)
c.JSON(200, gin.H{
"success": output,
})
POST /api/vul/cmd2 HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/json
Content-Length: 64

{
    "domain":"baidu.com | whoami",
    "password":"pssss"
}

Remediation:
Do not use "bash", "-c", although this only imposes restrictions and cannot fully remediate the issue.
It is recommended to hard-code the command directly or use an allowlist,
or use a pattern like this:

func safe(c *gin.Context) {
	cmd := c.Query("cmd")
	a := exec.Command("ls", cmd)
}

0x03 SSRF

SSRF attacks often lead to unauthorized operations or access to data within an organization.
In some cases, an SSRF vulnerability may allow an attacker to execute arbitrary commands.

Collapsible code and request packet

Common functions

http.Get(url)
http.Post(url, contentType, body)
http.Head(url)
http.PostForm(url, data)
http.NewRequest(method, url, body)

Full echo SSRF
routers/api/unAuth/ssrf.go

        url := c.PostForm("q")
   res, err := http.Get(url)
   if err != nil {
      fmt.Println(err)
      fmt.Println("get image failed")
   }
   body, err := ioutil.ReadAll(res.Body)
   defer res.Body.Close()
   if err != nil {
      log.Fatalln(err)
   }
   log.Println(string(body))
   c.JSON(200, gin.H{
      "success": string(body),
   })
POST /api/vul/ssrf HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 33

q=http://www.badiu.com/robots.txt

Simple allowlist remediation

    func GetImageSafe(c *gin.Context) {
   q := c.PostForm("q")
   url := "https://test.image.com/path/?q="
   res, err := http.Get(url + q)
   if err != nil {
      fmt.Println(err)
      fmt.Println("get image failed")
   }
   body, err := ioutil.ReadAll(res.Body)
   defer res.Body.Close()
   if err != nil {
      log.Fatalln(err)
   }
   log.Println(string(body))
   c.JSON(200, gin.H{
      "success": string(body),
   })
}
[GIN] 2022/10/22 - 22:44:28 | 500 |   946.54142ms |       127.0.0.1 | POST     /api/safe/ssrf
Get "https://test.image.com/path/?q=@http://www.badiu.com/": x509: certificate is not valid for any names, but wanted to match test.image.com
get image failed

Of course, this remediation is overly simplistic. We will examine a more mature remediation approach later.