Golang open redirects are among the least expected types of attacks, and yet they can cause a lot of harm to application users. Hackers are quickly making the internet a minefield, with tricks and links stealing user information. As such, you're best staying a step ahead when building apps with Golang.
This post contains information, code examples, and advice to help you create safer navigation links in Golang applications. This is the start to writing better Go code and creating applications that don't leave end users at the mercy of hackers.
What Is a Golang Open Redirect?
Golang web applications are a series or network of pages that you can navigate through using links and buttons. Typically, a Golang open redirect attack takes form when the links you generate to navigate the application take in arguments that lead out of your root domain. This way, even when your end users don't notice it, the browser redirects them to an external (clone) resource. There, they risk having their identities stolen through illegitimate input forms.
Usually, with Golang web applications, code sections generate URLs to pass state-changing input to the server. Open redirect vulnerabilities can carry user input to different storage locations that the developer intends.
If you haven't sanitized the URLs generated when passing information from the front to the back end, attackers can go as far as loading different redirects with payloads that harm your users. More on this when we discuss sanitization methods shortly. For now, let's look at some examples of Golang open redirects to further cement the concept.
Examples of Golang Open Redirects
Depending on the Go framework used to build a web application, some default redirects will help navigate between pages and pass input to the server. Usually, these operate by appending input and any arguments to the end of a webpage on which links originate. This means that after you fill in a form, the URL that the browser loads will look like this:
https://www.this-is-a-domain/you-were-on-this-page/this-is-your-input-or-request
This is in good order, but when taken advantage of, it will look something like this:
https://www.this-is-a-domain/you-were-on-this-page/url=http://a-clone-of-this-domain/this-is-your-input-or-request
At the end of the day, your input can be parsed and saved on the clone destination. The source of this redirect can be a form, as below:
func register(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
g := Member{
Usern: r.FormValue("usern"),
Name: r.FormValue("name"),
Passwd: r.FormValue("passwd"),
Repasswd: r.FormValue("repasswd"),
Phone: r.FormValue("phone"),
Email: r.FormValue("email"),
Study: r.FormValue("study"),
Address: r.FormValue("address"),
Date: datastore.SecondsToTime(time.Seconds()),
}
if g.Passwd == g.Repasswd && g.Usern != "" && g.Name != "" && g.Phone != "" && g.Email != "" {
_, err := datastore.Put(c, datastore.NewIncompleteKey("Member"), &g)
if err != nil {
http.Error(w, err.String(), http.StatusInternalServerError)
return
}
} else {
http.Redirect(w, r, "/signin", http.StatusFound)
}
http.Redirect(w, r, "/view", http.StatusFound)
}
The code above is part of the signup.go file in this open source project on GitHub. The app is for online ticket reservations, so the redirect() function is a perfect opportunity for hackers to acquire as much information about users as possible. Using system logic and process flow, the next form for such an instance would ask for banking information to attach to the new account and their desired destination. I'll leave the rest to your imagination.
How It Happens
The Golang open redirect attack launchpad is where only the /signin page is the next destination. This leaves a very open (and dangerous) parameter space for hackers to play with. The Go docs specify that your redirect() function syntax should be as follows:
func Redirect(w ResponseWriter, r *Request, url string, code int)
If you look at our example code, the w (writer) and r (request) constructors are empty, in which case the defaults (http.StatusOK) take over. When w is set, the Go server will take a look at the application's header map before writing any form data to a new state. The request is typically the "action" part of the form. It tells the server what call to make. The default is GET, which basically loads whichever next page the form is set to trigger.
What you don't see in the example, and most crucial to Golang open redirects, is the url and the string variables. These two are acceptable, so they'll run into the redirect link outside of the source file. Hackers can spring them into the picture by sending long links to users through email and even on social media. Even craftier would be a shortened link. So think twice before clicking or even using those bitly links that reveal their content only once the browser starts loading.
Another Example
Another example of how Golang open redirects can take shape is when users make legitimate searches for content on your application, only for the results to redirect to external sources. Of course, the Google search integration with applications is good to help users access the wider internet from within your application. However, your search bars can be the gateway to potentially harming users.
Consider the code below, which is part of this repository of an open source project:
func searchHandler(w http.ResponseWriter, r *http.Request) {
context := &Search{}
t, err := template.ParseFile("template/search.html")
if err != nil {
http.Error(w, err.String(), http.StatusInternalServerError)
}
context.T = t
ae := appengine.NewContext(r)
query := r.FormValue("q")
queryLength := len([]int(query))
if queryLength > 8 {
query = query[0:8]
http.Redirect(w, r, fmt.Sprintf("search?q=%s", query), 302)
return
}
// lowercase only
if query != strings.ToLower(query) {
http.Redirect(w, r, fmt.Sprintf("search?q=%s", strings.ToLower(query)), 302)
return
}
context.Q = query
hashTable := make(map[string]byte, 0)
if 0 != queryLength {
if queryLength > 8 {
query = query[0:8]
}
var e *word.Enable
var err os.Error
if e, err = loadEnable(); err != nil {
ae.Errorf("%v", err)
}
channel := word.StringPermutations(query)
for p := range channel {
if valid := e.WordIsValid(p); !valid {
continue
}
if _, inHash := hashTable[p]; !inHash {
context.Permutations = append(context.Permutations, p)
hashTable[p] = 1
}
}
}
// display template
if err := context.T.Execute(w, context); err != nil {
http.Error(w, err.String(), http.StatusInternalServerError)
}
}
This time, the redirect function openly lets input from the search bar become part of the URL through this section of code:
http.Redirect(w, r, fmt.Sprintf("search?q=%s", query), 302)
While you can trust everyone else to build good/clean links, this makes life easier for hackers as they can build redirects to their own pages. This basically creates a generator for them to plant their own URLs and send to legitimate application users.
The second code example tries to limit search queries to a maximum of eight characters. (This is good; you'll soon see how.)
queryLength := len([]int(query))
if queryLength > 8 { query = query[0:8]
http.Redirect(w, r, fmt.Sprintf("search?q=%s", query), 302) return }
However, it's not a perfect sanitization method for Golang open redirect attacks. Which brings us to the advice and strategy part of our post.
Conclusion: How to Fix and Prevent Open Redirects
The best way to fix the Golang open redirect vulnerabilities? Impose parameters and limits to what can run in the browser. The second example used an array length counter to limit users from using too many characters. This can lock out attacks that want to use long URLs. However, it also leaves users at a disadvantage and makes for bad experience, especially with search boxes.
However, a smarter approach would be to whitelist a finite set of URLs that can run against your own. In the same way that the input turns into an array, you can set for that array to be scanned and shut down if it contains external content sources.
Having a team of developers that know the Golang open redirect vulnerability is a good strategy. This way, you can keep your applications sanitized. As code piles up, you can also have tools like StackHawk constantly scan new code for any vulnerabilities.
This post was written by Taurai Mutimutema. Taurai is a systems analyst with a knack for writing, which was probably sparked by the need to document technical processes during code and implementation sessions. He enjoys learning new technology and talks about tech even more than he writes.