Golang
Secure Coding Practices/Recommendations 2021
A1 - Injections
SQL Injection
SQLi is one of the most common vulnerabilities that persists in the web applications. It mostly arises due to old bad practice of string concatenation and improper output encoding.
Vulnerable SQL implementation -
ctx := context.Background()
customerId := r.URL.Query().Get("id")
query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + customerId
row, _ := db.QueryContext(ctx, query)
When a normal input is provided to the query, it takes that (un-sanitized) input and provides the details. If some input is provided with special meaning to the SQL statement, it also goes in and executes as one SQL statement, hence we can say that the SQL query is partially controlled by the input of the user, which may lead to the SQLi vulnerability.
In the above code snippet, if 1
is passed to the customerID
, it provides the customer details. If the user inputs 1 OR 1=1
, this will dump all the table records, as 1=1
will hold true in all the cases.
Mitigation can be use of Prepared Statements with Parameterized Queries.
Safe Code -
ctx := context.Background()
customerId := r.URL.Query().Get("id")
query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = ?"
stmt, _ := db.QueryContext(ctx, query, customerId)
If there is any reason not to use the prepared statements - we suggest the use of Input Validation and Output Encoding.
Note : ?
is replaced with $1, $2 ...
in Postgress SQL.
A2 - Broken Authentication & Session Management
Session Management
There are a few things that are to be kept in mind, when dealing with sessions -
The application should only recognize the server’s session management controls
The session creation should be done on a trusted system.
In highly sensitive or critical operations - the token should be generated per-request instead of per session.
Create a sufficiently strong token to by put in cookie that will be validating the requests, where secrets can be environment variable or some properly secure mechanism from where the secret can be obtained.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) signedToken, _ := token.SignedString([]byte($SECRET))
Use HTTPS in all the requests, to avoid sniffing and prevent MiTM attacks.
err := http.ListenAndServeTLS(":443", "cert/cert.pem", "cert/key.pem", nil) if err != nil { log.Fatal("ListenAndServe: ", err) }
A3 - Sensitive Data Exposure
The most common problem is that sensitive data is not encrypted. Weak key creation and management, as well as weak algorithm, protocol, and cypher usage, are frequent when crypto is used, especially for weak password hashing storage strategies. Server-side flaws are very easy to identify for data in transit, but data at rest is more difficult.
Do the following, at a minimum, and consult the references:
- Classify data processed, stored or transmitted by an application. Identify which data is sensitive according to privacy laws, regulatory requirements, or business needs.
- Apply controls as per the classification.
- Don’t store sensitive data unnecessarily. Discard it as soon as possible or use PCI DSS compliant tokenization or even truncation. Data that is not retained cannot be stolen.
- Make sure to encrypt all sensitive data at rest.
- Ensure up-to-date and strong standard algorithms, protocols, and keys are in place; use proper key management.
- Encrypt all data in transit with secure protocols such as TLS with perfect forward secrecy (PFS) ciphers, cipher prioritization by the server, and secure parameters. Enforce encryption using directives like HTTP Strict Transport Security (HSTS).
- Disable caching for response that contain sensitive data.
- Store passwords using strong adaptive and salted hashing functions with a work factor (delay factor), such as Argon2, scrypt, bcrypt, or PBKDF2.
- Verify independently the effectiveness of configuration and settings.
Go provides supplementary libraries which offers supports of BLAKE2b (64-bit platforms) and BLAKE2s (8-bit to 32-bit platforms) implementation, which is considered strongest and most flexible. SHA256 is also a right option to implement hashing.
To import blake2s, we have to first get the library
go get golang.org/x/crypto/blake2s
package main
import "fmt"
import "io"
import "crypto/md5"
import "crypto/sha256"
import "golang.org/x/crypto/blake2s"
func main () {
h_md5 := md5.New()
h_sha := sha256.New()
h_blake2s, _ := blake2s.New256(nil)
io.WriteString(h_md5, "Go Language Secure Coding Practices")
io.WriteString(h_sha, "Go Language Secure Coding Practices")
io.WriteString(h_blake2s, "Welcome to Go Language Secure Coding Practices")
fmt.Printf("MD5 : %x\n", h_md5.Sum(nil))
fmt.Printf("SHA256 : %x\n", h_sha.Sum(nil))
fmt.Printf("Blake2s-256: %x\n", h_blake2s.Sum(nil))
}
// Output:
// MD5 : ea9321d8fb0ec6623319e49a634aad92
// SHA256 : ba4939528707d791242d1af175e580c584dc0681af8be2a4604a526e864449f6
// Blake2s-256: 1d65fa02df8a149c245e5854d980b38855fd2c78f2924ace9b64e8b21b3f2f82
Reference
https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure
A7 - Cross Site Scripting (XSS)
XSS has been in the OWASP Top 10 list of web vulnerabilities since 2003 and it’s still a very common vulnerability. Since most of the frameworks, that are developed now a days are stealthy against XSS, but still there are scenarios when they can be easily exploited.
You are vulnerable if you do not ensure that all user supplied input is properly escaped, or you do not verify it to be safe via server-side input validation input in the output page. (source)
Golang provides several packages to handle traffic over a network, one such example is net/http and io packages and using these make the application vulnerable to XSS.
Golang follows Mime Sniffing Standards to automatically set the Content-Type headers, if not explicitly defined.
See more about Mime Sniffing Standards - here.
Now, let consider a code for example,
package main
import "net/http"
import "io"
func handler (w http.ResponseWriter, r *http.Request) {
io.WriteString(w, r.URL.Query().Get("data"))
}
func main () {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
What the above code does is, it take a value through the parameter - data
whose value is returned as the response.
Following the standards, if we pass some text, it will give the Content-Type
header to be text/plain
. But if we pass some tag, it will refer to the Mime Sniffing Standards and allocate the Content-Type
accordingly.
So, if we pass <h1>
in the data
, the content header will be text/html
, and similarly if we pass some script tag, it will take the Content-Type to be text/html by default if not specified explicitly, which will allow XSS.
Possible mitigation techniques -
Proper input sanitization and output encoding is to be performed.
Use of comparatively safe packages like html/template.
package main import "net/http" import "html/template" func handler(w http.ResponseWriter, r *http.Request) { param1 := r.URL.Query().Get("param1") tmpl := template.New("hello") tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`) tmpl.ExecuteTemplate(w, "T", param1) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) }
A10 - Error Handling and Logging
Error handling refers to catch any errors in the application logic that may cause the system to crash. Logging allows the identification of all operations that have occurred and helps determine what actions need to be taken to protect the system. Since attackers often attempt to remove all traces of their action by deleting logs, logs must be centralized.
Error handling
In Go, there is a built-in error
type. The different values of error type indicate an abnormal state. Usually in Go, if the error value is not nil
then an error has occurred.
We can specify our own error types using the [errors.New](http://errors.New)
function:
{...}
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
//If an error has occurred print it
if err != nil {
fmt.Println(err)
}
{...}
In Go, there are additional error handling functions, these functions are panic , recover and defer . When an application state is panic its normal execution is interrupted, any defer statements are executed, and then the function returns to its caller. recover is usually used inside defer statements and allows the application to regain control over a panicking routine, and return to normal execution. The following snippet, based on the Go documentation explains the execution flow:
func main () {
start()
fmt.Println("Returned normally from start().")
}
func start () {
defer func () {
if r := recover(); r != nil {
fmt.Println("Recovered in start()")
}
}()
fmt.Println("Called start()")
part2(0)
fmt.Println("Returned normally from part2().")
}
func part2 (i int) {
if i > 0 {
fmt.Println("Panicking in part2()!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in part2()")
fmt.Println("Executing part2()")
part2(i + 1)
}
Output:
Called start()
Executing part2()
Panicking in part2()!
Defer in part2()
Recovered in start()
Returned normally from start().
Logging
Logging should always be handled by the application and should not rely on a server configuration.
All logging should be implemented by a master routine on a trusted system, and the developers should also ensure no sensitive data is included in the logs (e.g. passwords, session information, system details, etc.), nor is there any debugging or stack trace information. Additionally, logging should cover both successful and unsuccessful security events, with an emphasis on important log event data.
Important event data most commonly refers to all:
- Input validation failures.
- Authentication attempts, especially failures.
- Access control failures.
- Apparent tampering events, including unexpected changes to state data.
- Attempts to connect with invalid or expired session tokens.
- System exceptions.
- Administrative functions, including changes to security configuration settings.
- Backend TLS connection failures and cryptographic module failures.
func main() {
var buf bytes.Buffer
var RoleLevel int
logger := log.New(&buf, "logger: ", log.Lshortfile)
fmt.Println("Please enter your user level.")
fmt.Scanf("%d", &RoleLevel) //<--- example
switch RoleLevel {
case 1:
// Log successful login
logger.Printf("Login successful.")
fmt.Print(&buf)
case 2:
// Log unsuccessful Login
logger.Printf("Login unsuccessful - Insufficient access level.")
fmt.Print(&buf)
default:
// Unspecified error
logger.Print("Login error.")
fmt.Print(&buf)
}
}
From the perspective of log access, only authorized individuals should have access to the logs. Developers should also make sure that a mechanism that allows for log analysis is set in place, as well as guarantee that no untrusted data will be executed as code in the intended log viewing software or interface.
As a final step to guarantee log validity and integrity, a cryptographic hash function should be used as an additional step to ensure no log tampering has taken place.
{...}
// Get our known Log checksum from checksum file.
logChecksum, err := ioutil.ReadFile("log/checksum")
str := string(logChecksum) // convert content to a 'string'
// Compute our current log's SHA256 hash
b, err := ComputeSHA256("log/log")
if err != nil {
fmt.Printf("Err: %v", err)
} else {
hash := hex.EncodeToString(b)
// Compare our calculated hash with our stored hash
if str == hash {
// Ok the checksums match.
fmt.Println("Log integrity OK.")
} else {
// The file integrity has been compromised...
fmt.Println("File Tampering detected.")
}
}
{...}
Miscellaneous
Data Protection
Comments
Sometimes developers leave comments like To-do lists in the source code, and sometimes, in the worst-case scenario, developers may leave credentials.
// Secret API endpoint - /api/mytoken?callback=myToken
fmt.Println("Just a random code")
In the above example, the developer has an endpoint in a comment which, if not well protected, could be used by a malicious user.
URL
Passing sensitive information using the HTTP GET method leaves the web application vulnerable because:
- Data could be intercepted if not using HTTPS by MITM attacks.
- Browser history stores the user’s information. If the URL has session IDs, pins or tokens that don’t expire (or have low entropy), they can be stolen.
- Search engines store URLs as they are found in pages
- HTTP servers (e.g. Apache, Nginx), usually write the requested URL, including the query string, to unencrypted log files (e.g. access_log )
req, _ := http.NewRequest("GET", "[http://mycompany.com/api/mytoken?api_key=000s3cr3t000](http://mycompany.com/api/mytoken?api_key=000s3cr3t000)", nil)
If your web application tries to get information from a third-party website using your api_key , it could be stolen if anyone is listening within your network or if you’re using a Proxy. This is due to the lack of HTTPS.
File Management
File uploading should be restricted to authenticated users. Make sure only accepted file types can be uploaded to the server. The file types should be whitelisted. This can be done using the Go function DetectContentType(data []byte) string
This function detects the MIME types of a file.
{...}
// Write file to a buffer
buff := make([]byte, 512)
_, err = file.Read(buff)
{...}
// Check file type
filetype := http.DetectContentType(buff)
{...}
// Validate filetype against a whitelist of allowed filetypes
switch filetype {
case “image/jpeg”, “image/jpg”:
fmt.Println(filetype)
case “image/gif”:
fmt.Println(filetype)
case “image/png”:
fmt.Println(filetype)
default:
fmt.Println(“unknown file type uploaded”)
}
{...}
The uploaded file should not have execution privileges.
Input Validation
In order to avoid erroneous input caused by humans, go provides several packages to handle such inputs out of the box. It provides packages such as, strconv
(handles string conversion to other datatypes); strings
(function to handle strings and it’s properties); regexp
(for handling inputs using regular expressions) and validating for utf8
characters and for encoding/decoding runes and back to utf-8 characters.
Note: Before writing writing your own regular expression have a look at OWASP Validation Regex Repository
There are other means by which we can take care of the validation part in golang, because not everything can be handled by go, out of the box - such as whitelisting, boundary checking, null byte checks, character escaping, etc. (We are going to look all of these in detail)
Resources and References
For input text validation - go-playground/validator is the most widely used package with struct and field supports.
Other than than other security packages can be take from a repository/toolkit called Gorilla, which has packages like mux
( for http routers and URL matchers), websocket
, csrf
(CSRF prevention middleware for go web apps and services), etc.
Input Sanitization
Process of removing and replacing submitted data. It can also be stated as defense-in-depth strategy if implemented after validation of the input data.
Things to take care of,
Escape input strings -
EscapeString()
andUnescapeString()
are a part ofnet/html
package and these functions can be used to escape and unescape special characters respectively.Note:
EscapeString()
function only escapes the following five characters: < , > , & , ' and " .... s := "<script>alert('xss')</script>" fmt.Println(html.EscapeString(s)) ...
To escape strings URLs, use of
url.PathEscape
in packagenet/url
can be a good choice.const s = `Foo's Bar?` fmt.Println(url.PathEscape(s))
For anything specific manual encoding can be required.
- Strip all the tags - Striping out tags in input strings and to achieve this no out of the box module is present, but alternatives can be used.
html/template package has a stripTags() function, which can be exported.
Use of third party libraries to achieve this are
- kennygrant/sanitize - Package sanitize provides functions to sanitize html and paths with go (golang).
- maxwells/sanitize - A dead simple Go HTML whitelist-sanitization library.
- microcosm-cc/bluemonday - bluemonday is a HTML sanitizer implemented in Go. It is fast and highly configurable.
Remove line breaks, tabs and extra whitespaces from templates - Packages like
text/template
andhtml/template
provide this functionality by using minus (-) sign inside the action delimiter.{{- 3}}!={{- 4 }} // Output: 3!=4 {{ -3 }} // Output: -3
- Strip all the tags - Striping out tags in input strings and to achieve this no out of the box module is present, but alternatives can be used.