Skip to content

Go secure coding

Scan use gosec and staticcheck for analyzing Go projects.

G102: Bind to all interfaces

Binding to all network interfaces can potentially open up a service to traffic on unintended interfaces, that may not be properly documented or secured. This check test looks for a string pattern “0.0.0.0” that may indicate a hardcoded binding to all network interfaces.

Incorrect

package main

import (
    "net"
)

func main() {
    net.Listen("tcp", ":8080")
}
    l, err := net.Listen("tcp", "0.0.0.0:2000")

G103: Audit the use of unsafe block

Using the unsafe package in Go gives you low-level memory management and many of the strength of the C language but also gives flexibility to the attacker of your application. The pointer arithmetic is one of the examples from the unsafe package which can be used for data leak, memory corruption or even execution of attackers own script.

Also, you should keep in mind that the "unsafe" package is not protected by Go 1 compatibility guidelines.

If you want to ignore this rule you can do it, as usual, using the "exclude" option in the command line interface.

Example code:

package main
import (
    "fmt"
    "unsafe"
)
type Fake struct{}
func (Fake) Good() {}
func main() {
    unsafeM := Fake{}
    unsafeM.Good()
    intArray := [...]int{1, 2}
    fmt.Printf("\nintArray: %v\n", intArray)
    intPtr := &intArray[0]
    fmt.Printf("\nintPtr=%p, *intPtr=%d.\n", intPtr, *intPtr)
    addressHolder := uintptr(unsafe.Pointer(intPtr)) + unsafe.Sizeof(intArray[0])
    intPtr = (*int)(unsafe.Pointer(addressHolder))
    fmt.Printf("\nintPtr=%p, *intPtr=%d.\n\n", intPtr, *intPtr)
}

G104: Audit errors not checked

Really useful feature of Golang is the ability to return a tuple of a result and an error value from a function. There is an unspoken rule in Golang that the result of a function is unsafe until you make check the error value. Many security exploits can be performed when the error value is not checked.

Example code:

package main
import "fmt"
func test() (int,error) {
    return 0, nil
}
func main() {
    v, _ := test()
    fmt.Println(v)
}

other example:

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func a() error {
    return fmt.Errorf("This is an error")
}

func b() {
    fmt.Println("b")
    ioutil.WriteFile("foo.txt", []byte("bar"), os.ModeExclusive)
}

func c() string {
    return fmt.Sprintf("This isn't anything")
}

func main() {
    _ = a()
    a()
    b()
    c()
}

G106: Audit the use of ssh.InsecureIgnoreHostKey

G107: Url provided to HTTP request as taint input

Getting an URL from an untrusted source like user input gives the ability of an attacker to redirect your application to bad websites and perform additional attacks. One of the examples is as shown below the http.Get() function issues a GET to the specified URL and if the result is appropriate GET will follow the redirect after calling Client's CheckRedirect function. That means that the attacker can send your application to various places.

This problem can be used to achieve SSRF atttacks via http requests with variable url.

Incorrect

package main
import (
    "net/http"
    "io/ioutil"
    "fmt"
    "os"
)
func main() {
    url := os.Getenv("tainted_url")
    resp, err := http.Get(url)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
            panic(err)
    }
    fmt.Printf("%s", body)
}
package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

var url string = "https://www.appthreat.com"

func main() {

    resp, err := http.Get(url)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s", body)
}

G108: Profiling endpoint automatically exposed on /debug/pprof

G109: Potential Integer overflow made by strconv.Atoi result conversion to int16/32

G110: Potential DoS vulnerability via decompression bomb

G201: SQL query construction using format string

SQL injection is one of the top security issues developers make and the consequences of this can be severe. Using the format string function in the fmt Golang package to dynamically create an SQL query can easily create a possibility for SQL injection. The reason is that the format string function doesn't escape special characters like ' and it's easy to add second SQL command in the format string.

Examples of problematic code:

package main
import (
    "database/sql"
    "fmt"
    "os"
)
func main(){
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        panic(err)
    }
    q := fmt.Sprintf("SELECT * FROM foo where name = '%s'", os.Args[1])
    rows, err := db.Query(q)
    if err != nil {
        panic(err)
    }
    defer rows.Close()
}

G202: SQL query construction using string concatenation

Incorrect

import (
    "database/sql"
)

var staticQuery = "SELECT * FROM foo WHERE age < "

func main() {
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        panic(err)
    }
    var gender string = "M"
    rows, err := db.Query("SELECT * FROM foo WHERE gender = " + gender)
    if err != nil {
        panic(err)
    }
    defer rows.Close()
}

Correct

Two of the ways to escape SQL injection when using Golang are:

  • use static queries
package main
import (
        "database/sql"
)
const staticQuery = "SELECT * FROM foo WHERE age < 32"
func main(){
        db, err := sql.Open("sqlite3", ":memory:")
        if err != nil {
            panic(err)
        }
        rows, err := db.Query(staticQuery)
        if err != nil {
            panic(err)
        }
        defer rows.Close()
}
  • Use the database/sql By using the database/sql package along with argument placeholders you are able to construct SQL statements that are automatically escaped properly. The key distinction here is that you aren’t trying to construct the SQL statement yourself, but instead you are providing arguments that can be easily escaped. The underlying driver for database/sql will ultimately be aware of what special characters it needs to handle and will escape them for you, preventing any nefarious SQL from running.
package main
import (
        "database/sql"
        "bufio"

)
func main(){
        db, err := sql.Open("sqlite3", ":memory:")
        if err != nil {
            panic(err)
        }
        in := bufio.NewReader(os.Stdin)
        name, err := in.ReadString('\n')
        if err != nil {
            panic(err)
        }
        rows, err := db.Query("SELECT * FROM foo WHERE name = ?", name)
        if err != nil {
            panic(err)
        }
        defer rows.Close()
}

It is highly recommended to use the database/sql package in Golang instead of fmt package for SQL queries.

G203: Use of unescaped data in HTML templates

G204: Audit use of command execution

G301: Poor file permissions used when creating a directory

G302: Poor file permissions used with chmod

G303: Creating tempfile using a predictable path

G304: File path provided as taint input

Trying to open a file provided as an input in a variable. The content of this variable might be controlled by an attacker who could change it to hold unauthorised file paths form the system. In this way, it is possible to exfiltrate confidential information or such.

Incorrect

package main

import (
    "fmt"
    "io/ioutil"
    "strings"
)

func main() {
    repoFile := "path_of_file"
    byContext, err := ioutil.ReadFile(repoFile)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s", string(byContext))
}

Correct

package main

import (
    "fmt"
    "io/ioutil"
    "path/filepath"
    "strings"
)

func main() {
    repoFile := "path_of_file"
    byContext, err := ioutil.ReadFile(filepath.Clean(repoFile))
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s", string(byContext))
}

G305: File traversal when extracting zip archive

G306: Poor file permissions used when writing to a new file

G307: Deferring a method which returns an error

G401: Detect the usage of DES, RC4, MD5 or SHA1

G402: Look for bad TLS connection settings

G403: Ensure minimum RSA key length of 2048 bits

G404: Insecure random number source (rand)

G501: Import blacklist: crypto/md5

G502: Import blacklist: crypto/des

G503: Import blacklist: crypto/rc4

G504: Import blacklist: net/http/cgi

G505: Import blacklist: crypto/sha1

G601: Implicit memory aliasing of items from a range statement

The rules used by staticcheck can be found here


Last update: January 25, 2023