Output Encoding

Although output encoding only has six bullets in the section on OWASP SCP Quick Reference Guide, undesirable practices of Output Encoding are rather prevalent in Web Application development, thus leading to the Top 1 vulnerability: Injection.

As Web Applications become more complex, the more data sources they usually have, for example: users, databases, thirty party services, etc. At some point in time collected data is outputted to some media (e.g. a web browser) which has a specific context. This is exactly when injections happen if you do not have a strong Output Encoding policy.

Certainly you’ve already heard about all the security issues we will approach in this section, but do you really know how do they happen and/or how to avoid them?

XSS - Cross Site Scripting

Although most developers have heard about it, most have never tried to exploit a Web Application using XSS.

Cross Site Scripting has been on OWASP Top 10 security risks since 2003 and it’s still a common vulnerability. The 2013 version is quite detailed about XSS, for example: attack vectors, security weakness, technical impacts and business impacts.

In short

You are vulnerable if you do not ensure that all user supplied input is properly escaped, or you do not verify it to be safe via server-side input validation, before including that input in the output page. (source)

Go, just like any other multi-purpose programming language, has everything needed to mess with and make you vulnerable to XSS, despite the documentation being clear about using the html/template package. Quite easily, you can find “hello world” examples using net/http and io packages. And without realizing it, you’re vulnerable to XSS.

Imagine the following code:

package main

import "net/http"
import "io"

func handler (w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, r.URL.Query().Get("param1"))
}

func main () {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

This snippet creates and starts an HTTP Server listening on port 8080 (main()), handling requests on server’s root (/).

The handler() function, which handles requests, expects a Query String parameter param1, whose value is then written to the response stream (w).

As Content-Type HTTP response header is not explicitly defined, Go http.DetectContentType default value will be used, which follows the WhatWG spec.

So, making param1 equal to “test”, will result in Content-Type HTTP response header to be sent as text/plain.

Content-Type: text/plain

But if param1 first characters are “<h1>”, Content-Type will be text/html.

Content-Type: text/html

You may think that making param1 equal to any HTML tag will lead to the same behavior, but it won’t. Making param1 equal to “<h2>”, “<span>” or “<form>” will make Content-Type to be sent as plain/text instead of expected text/html.

Now let’s make param1 equal to <script>alert(1)</script>.

As per the WhatWG spec, Content-Type HTTP response header will be sent as text/html, param1 value will be rendered, and here it is, the XSS (Cross Site Scripting).

XSS - Cross-Site Scripting

After talking with Google regarding this situation, they informed us that:

It’s actually very convenient and intended to be able to print html and have the content-type set automatically. We expect that programmers will use html/template for proper escaping.

Google states that developers are responsible for sanitizing and protecting their code. We totally agree BUT in a language where security is a priority, allowing Content-Type to be set automatically besides having text/plain as default, is not the best way to go.

Let’s make it clear: text/plain and/or the text/template package won’t keep you away from XSS, since it does not sanitize user input.

package main

import "net/http"
import "text/template"

func handler(w http.ResponseWriter, r *http.Request) {
        param1 := r.URL.Query().Get("param1")

        tmpl := template.New("hello")
        tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
        tmpl.ExecuteTemplate(w, "T", param1)
}

func main() {
        http.HandleFunc("/", handler)
        http.ListenAndServe(":8080", nil)
}

Making param1 equal to “<h1>” will lead to Content-Type being sent as text/html. This is what makes you vulnerable to XSS.

XSS while using text/template package

By replacing the text/template package with the html/template one, you’ll be ready to proceed… safely.

package main

import "net/http"
import "html/template"

func handler(w http.ResponseWriter, r *http.Request) {
        param1 := r.URL.Query().Get("param1")

        tmpl := template.New("hello")
        tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
        tmpl.ExecuteTemplate(w, "T", param1)
}

func main() {
        http.HandleFunc("/", handler)
        http.ListenAndServe(":8080", nil)
}

Not only Content-Type HTTP response header will be sent as text/plain when param1 is equal to “<h1>”

Content-Type: text/plain while using html/template package

but also param1 is properly encoded to the output media: the browser.

No XSS while using html/template package

SQL Injection

Another common injection that’s due to the lack of proper output encoding is SQL Injection. This is mostly due to an old bad practice: string concatenation.

In short, whenever a variable holding a value which may include arbitrary characters such as ones with special meaning to the database management system is simply added to a (partial) SQL query, you’re vulnerable to SQL Injection.

Imagine you have a query such as the one below:

ctx := context.Background()
customerId := r.URL.Query().Get("id")
query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + customerId

row, _ := db.QueryContext(ctx, query)

You’re about to be exploited and subsequently breached.

For example, when provided a valid customerId value you will only list that customer’s credit card(s). But what if customerId becomes 1 OR 1=1?

Your query will look like:

SELECT number, expireDate, cvv FROM creditcards WHERE customerId = 1 OR 1=1

… and you will dump all table records (yes, 1=1 will be true for any record)!

There’s only one way to keep your database safe: Prepared Statements.

ctx := context.Background()
customerId := r.URL.Query().Get("id")
query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = ?"

stmt, _ := db.QueryContext(ctx, query, customerId)

Notice the placeholder ?. Your query is now:

  • readable,
  • shorter and
  • SAFE

Placeholder syntax in prepared statements is database-specific. For example, comparing MySQL, PostgreSQL, and Oracle:

MySQL PostgreSQL Oracle
WHERE col = ? WHERE col = $1 WHERE col = :col
VALUES(?, ?, ?) VALUES($1, $2, $3) VALUES(:val1, :val2, :val3)

Check the Database Security section in this guide to get more in-depth information about this topic.


Licenses and Attributions


Speak Your Mind