Broken Authentication in Go Lang
Vulnerable example
The following snippet is of a web application, written in Go Lang, which has an administrative functionality that allows a user list to be shown when a specific URL is called.
func Users(w http.ResponseWriter, r * http.Request) {
cookieUser, _: = r.Cookie("username")
if cookieUser == nil {
http.Redirect(w, r, "/login?errMsg=User+Not+Authorized", http.StatusFound)
return
}
// show user list
}
The /admin/users
URI is available to everyone who provides a cookie with the name set to username. A malicious actor could manipulate the HTTP request and add the Cookie header without performing a successful authentication:
GET /login HTTP/1.1
Host: www.vulnerableapp.com
Cookie: username=admin
Since the authentication routine only checks for the presence of the username cookie, access is granted:
HTTP/1.1 200 OK
Content-type: text/html
... sensitive data ...
Prevention
The Go standard library does not have baked-in support for sessions or session handling. Apply the recommended authentication and authorization mechanisms depending on the web framework of choice.
If you’re not using a web framework, you can consider implementing session management using the Gorilla’s Sessions package as demonstrated below:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/sessions"
)
var (
key = [] byte(GenerateRandomKey(16))
store = sessions.NewCookieStore(key)
)
func users(w http.ResponseWriter, r * http.Request) {
session, _: = store.Get(r, "go-session")
// Check if user is authenticated
if auth, ok: = session.Values["authenticated"].(bool);
!ok || !auth {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// List Users
fmt.Fprintln(w, userList)
}
func login(w http.ResponseWriter, r * http.Request) {
session, _: = store.Get(r, "cookie-name")
username: = r.Form["username"][0]
password: = r.Form["password"][0]
if areUserCredentialsCorrect(username, password) {
// Set user as authenticated
session.Values["authenticated"] = true
session.Values["username"] = username
session.Save(r, w)
// redirect user to authenticated page
return
}
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
func logout(w http.ResponseWriter, r * http.Request) {
session, _: = store.Get(r, "cookie-name")
// Revoke users authentication
session.Values["authenticated"] = false
session.Save(r, w)
}
func main() {
http.HandleFunc("/admin/users", users)
http.HandleFunc("/login", login)
http.HandleFunc("/logout", logout)
http.ListenAndServe(":8050", nil)
}
When the user performs an authentication attempt, the browser will issue the HTTP request below:
POST /login HTTP/1.1
Host: vulnerableapp.com
Content-type: application/urlencoded
username=user&password=secret
Upon successful authentication, the application returns the session cookie go-session
which is a random base64 encoded string. The string does not contain user information. The user’s session information is stored server-side in a Map object, where the value of the go-session
cookie is the key.
HTTP/1.1 302 Found
Location: /admin/users
Set-Cookie: go-session=MTU2MzkxNzIyM3xEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUlHYzNSeWFXNW5EQW9BQ0hWelpYSnVZVzFsQm5OMGNtbHVad3dHQUFSMWMyVnlCbk4wY21sdVp3d1BBQTFoZFhSb1pXNTBhV05oZEdWa0JHSnZiMndDQWdBQnxqctGY_S5vt53QTJRqfZXNtXOvdcHrAyjJG3IvXZh7YQ==;
References
MITRE - CWE 287: Improper Authentication OWASP - Testing for Bypassing Authentication Schema Gorilla Toolkit - Sessions OWASP - Go Secure Coding Practices