Analyzing a Broken Authentication Vulnerability in Golang

Analyzing a Broken Authentication Vulnerability in Golang

Analytical article of couple code snippets that will introduce us to the skill of code review, and a brief explanation of Broken Authentication vulnerablity.

What is an Authentication?

Authentication is the process to verify the user identity from the data provided by him/her, and authentication can be implemented in many ways, but in our case we will focus on Basic Access Authentication

What is Basic Access Authentication?

Basic Access Authentication is a built-in authentication scheme in HTTP protocol.

The client sends GET HTTP Request to the server, the server responds with WWW-Authenticate header contains authentication challenge information, after that the client side sends the Authorization header that contains a base64-encoded string (authentication data), Ex: username:password

Flow:

If we intercepted the request of a Basic Access Authentication process, It will be looking like this:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

In browser:

As anything, this also can be vulnerable.

Broken Authentication Vulnerability?

Broken Authentication is typically caused by poorly implemented authentication and session management functions.

Boken authentication attacks aim to take over one or more accounts giving the attacker the same privileges as the attacked user. Authentication is “broken” when attackers are able to compromise passwords, keys or session tokens, user account information, and other details to assume user identities.

In this article we are going to cover a vulnerable authentication implementation example in Golang.

func auth(fn http.HandlerFunc) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    user, pass, _ := r.BasicAuth()
    if !check(user, pass) {
       w.Header().Set("WWW-Authenticate", "Basic realm=\\"MY REALM\\"")
       http.Error(w, "Unauthorized.", 401)
       return
    }
    fn(w, r)
  }
}
 
func check(u, p string) bool {
  password:= os.Getenv("MYPASSWORD")
  user:= os.Getenv("MYUSER")
  if u == user || p== password {
    return true 
  }
  return false
} 
 
func handler(w http.ResponseWriter, r *http.Request) {
    [...]
}
 
func main() {
    http.HandleFunc("/",auth(handler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Code Review #1:

Auth Function is playing the part of a security layer for Handler Function it will take the user and password provided from the client and checks using Check Function if they are valid or not, in case of valid data it will go further with the Handler Funciton in case of invalid data it will set the WWW-Authenticate header and returns Unauthorized with the status code 401.

The problem here is the developer in Check Function used the OR operator || to check if p and u are equal to password and username

  if u == user || p== password {
    return true 
  }

Means, if attacker used a valid username with invalid password or vice versa, Auth Function will call Handler Function

Patch #1:

In this case the patch will be easy, and it is using AND operator && rather than OR operator || :

  if u == user && p== password {
    return true 
  }

Here is the patch that has been committed by the developer:

func auth(fn http.HandlerFunc) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    user, pass, _ := r.BasicAuth()
    if !check(user, pass) {
       w.Header().Set("WWW-Authenticate", "Basic realm=\\"MY REALM\\"")
       http.Error(w, "Unauthorized.", 401)
    }
    fn(w, r)
  }
}
 
func check(u, p string) bool {
  password:= os.Getenv("MYPASSWORD")
  user:= os.Getenv("MYUSER")
  if u == user && p== password {
    return true 
  }
  return false
} 
 
func handler(w http.ResponseWriter, r *http.Request) {
    [...]
}
 
func main() {
    http.HandleFunc("/",auth(handler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

And it is also vulnerable.

Code Review #2:

The diffrent between the first code and the second code, is the return statment in Auth Function :

       w.Header().Set("WWW-Authenticate", "Basic realm=\\"MY REALM\\"")
       http.Error(w, "Unauthorized.", 401)
       return

The Auth Function (In this case) will always call the Handler Function in any case of Check Function (invalid or valid data), because the return  statement that will exit the Auth Function  isn’t there.

This is an usual mistake done by developers, developers may remove some important lines accidentally or to make the code shorter.

Patch #2:

func auth(fn http.HandlerFunc) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    user, pass, _ := r.BasicAuth()
    if !check(user, pass) {
       w.Header().Set("WWW-Authenticate", "Basic realm=\\"MY REALM\\"")
       http.Error(w, "Unauthorized.", 401)
       return
    }
    fn(w, r)
  }
}
 
func check(u, p string) bool {
  password:= os.Getenv("MYPASSWORD")
  user:= os.Getenv("MYUSER")
  if u == user && p== password {
    return true 
  }
  return false
} 
 
func handler(w http.ResponseWriter, r *http.Request) {
    [...]
}
 
func main() {
    http.HandleFunc("/",auth(handler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Conclusion:

  • Think twice before you implement anything.
  • Patch !== the final fix.