Go has been one of the most popular languages for building microservices. Its simplicity and less overhead have proven to favor its rise in the community. Today, developers are using it to build web services for internet-scale. Even though it's a great language, you still have to follow certain best practices to ensure your service is secure and trustworthy.
In this blog, I am going to cover some common security pitfalls while building a Go application, namely:
- Cross-site Scripting(XSS)
- SQL injection
- Cross-site Request Forgery(CSRF)
- Common Vulnerabilities and Exposures(CVE)
Cross-site Scripting(XSS)
In the Cross-Site Scripting (XSS) attack, an attacker injects a malicious script into the app to modify its behavior. It's one of the most common attacks performed on websites.
Suppose you have an API endpoint that accepts a query parameter. You use that query parameter to build the HTML response page. An attacker can replace the query parameter with a javascript, and when you return the HTML response, a JavaScript command is executed on the user's browser. Let's with a code example.
package main
import (
"log"
"net/http"
"text/template"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
t, _ := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
t.ExecuteTemplate(w, "T", name)
}
func main() {
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Now you can run your server using go run main.go
. Go and visit http://localhost:8080/hello?name=umesh
Instead of passing a text as a name
parameter, we will now be passing a JavaScript command, and it will get executed in the browser.
So, here’s how you can prevent it: simply replace text/template
with html/template
. html/template
produces escaped HTML output in contrasts to text/template
.
package main
import (
"html/template"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
t, _ := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
t.ExecuteTemplate(w, "T", name)
}
func main() {
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
SQL Injection
Go provides you with an inbuilt package to perform DB queries. You can use database/sql
to perform various database queries.
So suppose you are taking some user input and using it directly to perform database operations.
package main
import (
"database/sql"
)
...
func someDBOperations() {
id := userInput("id")
_, err := db.Exec("SELECT * from stories WHERE user_id =" + id)
if err != nil {
panic(err)
}
}
...
As in the above example, if an attacker sends you a SQL input as id('select * from users') instead of the usual value, and you execute the query, you will query the users present in the table and return the answer to the malicious user instead of the expected results.
To avoid this, you should validate the user input. So you can perform simple validations on the user input before passing it on to the DB. After that, you should always use parameterized variables or use prepared statements.
package main
import (
"database/sql"
)
...
func someDBOperations() {
id := userInput("id")
_, err := db.Exec("SELECT * from stories WHERE user_id = ?", id)
if err != nil {
panic(err)
}
}
By changing the db.Exec
in the above example to using query params instead of using concatenation. We have avoided possible SQL injection attacks.
Cross-site Request Forgery(CSRF)
Cross-site Request Forgery happens when the web server does not verify whether the user authorizes the request; it only checks whether the request was created from the user's browser. The website assumes that the user generates every request that originates from the user’s browser.
This assumption leads to attacks such as CSRF because many other websites also run their code on the user's browser, and the attacker can capture the session to make requests on the user's behalf.
How can you prevent this from happening?
A webserver should generate a CSRF token unique to a session every time a user visits their website and store it inside the cookie on the user's browser. Whenever the user submits a form, it should also provide the CSRF token along with a cookie.
When a user submits the form with the cookie, the server verifies if the CSRF token in the cookie and form is identical. Make sure you are using the same-site cookies so that you will be able to restrict the cookie’s access to the same-site context. These steps will prevent the attacker from making any false request on behalf of your user.
You can use gorilla/csrf . It provides an easy way to implement CSRF protection, and you can use it to protect your website from any of these attacks. Here’s an example where you add a CSRF middleware on top of your HTTP handler and set the CSRF token in the header while returning the response.
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
type user struct {
name string
}
func GetUserByID(w http.ResponseWriter, r *http.Request) {
data := &user{
name: "Someone",
}
w.Header().Set("X-CSRF-Token", csrf.Token(r))
resp, err := json.Marshal(data)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("something wen wrong"))
return
}
w.WriteHeader(http.StatusOK)
w.Write(resp)
}
func main() {
router := mux.NewRouter()
csrfMiddleware := csrf.Protect([]byte("925d21d89fc208f62cad90b017cbc35b2dab62e06105c1268e04c9f6714cad94ba9babf715eac60597a67d2a28b1abe3d20a072bf02bd6f04655e95263780fce"))
router.Use(csrfMiddleware)
router.HandleFunc("/user/{id}", GetUserByID).Methods("GET")
log.Fatal(http.ListenAndServe(":8000", router))
}
If a user submits a form or submits a request, the middleware can immediately check if the request is made by the user or the attacker. Depending on the CSRF token, it refuses the requests.
Common Vulnerabilities and Exposures(CVE)
Common Vulnerabilities and Exposures (CVE) is a catalog of publicly known security vulnerabilities and exposures.
Every software or tool ever created has introduced vulnerabilities in their code, and Go is no exception. Now we will take a look at how to find these vulnerabilities and plug them by keeping your library updated.
We will use WhiteSource Vulnerability Database to explore CVE. It's free and has a database of all the open-source CVE. It looks something like this.
You can search through the CVE using an ID, or an open-source project. As we are interested in Go
, we will search for it.
Once you have searched for it, you will find multiple results with Go
in their name, but we are interested in this one.
After clicking on it, it will take you to the detail page where you can look at the Go
vulnerabilities reported over the year. If you look at the graphs, you will notice they denote the severity of the CVE reported and their reporting time. In the case of Go
the frequency is less, but it still has many CVE reported since its launch.
Now suppose you want to know about a specific CVE, you can use the search functionality to find out more about it. Let search about one of the very recent security vulnerability CVE-2019-16276
.
You can click on the link and explore more about the vulnerability. You can check the severity, and explore more details about the CVE.
Go team resolved these issues and adds a patch to fix it as soon as they get reported. You have to upgrade your Go version to avoid these vulnerabilities. But sometimes it might take time to deliver the patch depending upon the criticality, so if it's something that is going to impact your app, you can look for a temporary fix.
Conclusion
Go is an excellent language, and most of the time, it provides pretty strong security out of the box. But no code is immune to mistakes, so you should check it as much as you can and use security tools at your disposal.
If you liked this post, please share it with others so that it can help them as well. You can tag me on Twitter @imumesh18. You can subscribe to my newsletter to read more from me.