General Coding Practices

Cross-Site Request Forgery

By OWASP’s definitionCross-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]

A simple attack scenario may look like:

  1. Victim is authenticated at https://foo.com
  2. Attacker sends a chat message to the Victim with the following link:
    https://foo.com/account/[email protected]
    
  3. Victim’s account recovery email address is changed to [email protected], giving the Attacker full control over it.

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 attack is possible because the server does not distinguish between requests made during a legit user session workflow (navigation), and “malicious” ones.

The Solution

In theory

As previously mentioned, CSRF targets state-changing requests. Concerning Web Applications, most of the time that means POST requests issued by form submission.

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.

The specific nonce/token should obey to the following requirements:

  • Unique per user session
  • Large random value
  • Generated by a cryptographically-secure random number generator

Note: Although HTTP GET requests are not expected to change state (said to be idempotent), due to undesirable programming practices they can in fact modify resources. Because of that, they could also be targeted by CSRF attacks.

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.

Most Web Application Frameworks already offer a solution out-of-the-box and you’re advised to enable it. If you’re not using a Framework, the advice is to adopt one.

The following example is part of the Gorilla web toolkit for Go programming language. You can find gorilla/csrf on GitHub

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)

    // Add the middleware to your router by wrapping it.
    http.ListenAndServe(":8000",
        csrf.Protect([]byte("32-byte-long-auth-key"))(r))
    // PS: Don't forget to pass csrf.Secure(false) if you're developing locally
    // over plain HTTP (just don't leave it on in production).
}

func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
    // signup_form.tmpl just needs a {{ .csrfField }} template tag for
    // csrf.TemplateField to inject the CSRF token into. Easy!
    t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{}{
        csrf.TemplateTag: csrf.TemplateField(r),
    })
    // We could also retrieve the token directly from csrf.Token(r) and
    // set it in the request header - w.Header.Set("X-CSRF-Token", token)
    // This is useful if you're sending JSON to clients or a front-end JavaScript
    // framework.
}

func SubmitSignupForm(w http.ResponseWriter, r *http.Request) {
    // We can trust that requests making it this far have satisfied
    // our CSRF protection requirements.
}

OWASP has a detailed Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet, which you’re recommended to read.

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).

Regular expressions are a notation for describing sets of character strings. When a particular string is in the set described by a regular expression, we often say that the regular expression matches the string. (source)

It is well-known that Regular Expressions are hard to master. Sometimes, what seems to be a simple validation, may lead to a Denial-of-Service.

Go authors took it seriously, and unlike other programming languages, the decided to implement RE2 for the regex standard package.

Why RE2

RE2 was designed and implemented with an explicit goal of being able to handle regular expressions from untrusted users without risk. (source)

With security in mind, RE2 also guarantees a linear-time performance and graceful failing: the memory available to the parser, the compiler, and the execution engines is limited.

Regular Expression Denial of Service (ReDoS)

Regular Expression Denial of Service (ReDoS) is an algorithmic complexity attack that provokes a Denial of Service (DoS). ReDos attacks are caused by a regular expression that takes a very long time to be evaluated, exponentially related with the input size. This exceptionally long time in the evaluation process is due to the implementation of the regular expression in use, for example, recursive backtracking ones. (source)

You’re better off reading the full article “[Diving Deep into Regular Expression Denial of Service (ReDoS) in Go][8]” 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.

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]{2,3}[.]{1}[a-z]{2,3}))$

If you try to match [email protected] against this regular expression you may feel confident that it does what you’re looking for. If you’re developing using Go, you’ll come up with something like this:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    testString1 := "[email protected]"
    testString2 := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"
    regex := regexp.MustCompile("^([a-zA-Z0-9])(([\\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$")

    fmt.Println(regex.MatchString(testString1))
    // expected output: true
    fmt.Println(regex.MatchString(testString2))
    // expected output: false
}

Which is not a problem:

$ go run src/redos.go
true
false

However, what if you’re developing with, for example, JavaScript?

const testString1 = '[email protected]';
const testString2 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!';
const regex = /^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$/;

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?

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.

RE2 does not support constructs where only backtracking solutions are known to exist, such as Backreferences and Lookaround.

Consider the following problem: validating whether an arbitrary string is a well-formed HTML tag: a) opening and closing tag names match, and b) optionally there’s some text in between.

Fulfilling requirement b) is straightforward .*?. But fulling requirement a) is challenging because closing a tag match depends on what was matched as the opening tag. This is exactly what Backreferences allows us to do. See the JavaScript implementation below:

const testString1 = '<h1>Go Secure Coding Practices Guide</h1>';
const testString2 = '<p>Go Secure Coding Practices Guide</p>';
const testString3 = '<h1>Go Secure Coding Practices Guid</p>';
const regex = /<([a-z][a-z0-9]*)\b[^>]*>.*?<\/\1>/;

console.log(regex.test(testString1));
// expected output: true
console.log(regex.test(testString2));
// expected output: true
console.log(regex.test(testString3));
// expected output: false

\1 will hold the value previously captured by ([A-Z][A-Z0-9]*).

This is something you should not expect to do in Go.

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))
}

Running the Go source code sample above should result in the following errors:

$ 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: >

You may feel tempted to fix these errors, coming up with the following regular expression:

<([a-z][a-z0-9]*)\b[^>]*>.*?<\\/\\1>

Then, this is what you’ll get:

go run src/backreference.go
panic: regexp: Compile("<([a-z][a-z0-9]*)\b[^>]*>.*?<\\/\\1>"): error parsing regexp: invalid escape sequence: `\1`

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

While developing something from scratch, you’ll probably find a nice workaround to help with the lack of some features. On the other hand, porting existing software could make you look for full featured alternative to the standard Regular Expression package, and you’ll likely find some (e.g. [dlclark/regexp2][7]). Keeping that in mind, then you’ll (probably) lose RE2’s “safety features” such as the linear-time performance.


Licenses and Attributions


Speak Your Mind