Flash CTF – Library

This challenge, in the Web Exploitation category, gives us the following prompt:

I’ve been developing this little book library app in Go, but I think something is wrong with the way it’s handling book requests. Some of the book titles seem to be giving unexpected results… Maybe you can figure it out?

… along with a source code download, and a link to a live instance of the website.

The website looks like a pretty simple list of books. We can click on each one to view some details about it, and… that’s about it. Looking at the HTML or the network requests doesn’t reveal much, it’s a pretty simple, static website. Let’s peek at the server’s source code – particularly the main.go file (we’ve been hinted that it’s a Go challenge).

It’s a pretty short program. Up top, we have a ReadBook function that takes a file path argument, reads the file, and returns its contents as a string – pretty straightforward. Below, a homeHandler function appears to be responsible for rendering, via a template file, the home page of the website.

And below that, a bookHandler function appears to render the detail pages for individual books that are clicked on:

func bookHandler(w http.ResponseWriter, r *http.Request) {
    userBook := r.URL.Query().Get("book")
    bookContent, validBook := books[userBook]

    if validBook {
        tmpl := template.Must(template.ParseFiles("templates/book.html"))
        tmpl.Execute(w, bookContent)
        return
    }

    if userBook != "" {
        tmpl, err := template.New("book").Parse(userBook)
        if err != nil {
            http.Error(w, "Template parsing error: "+err.Error(), http.StatusInternalServerError)
            return
        }
        tmpl.Execute(w, ReadBook{})
        return
    }

    http.Error(w, "No book specified", http.StatusBadRequest)
}

This function is of interest because it actually processes user input – the ?book=... query parameter in the URL, which gets put in the userBook variable. Let’s go through the logic.

It looks like the code first checks if userBook refers to one of the book IDs in the books map, such as 1984 or brave_new_world, and simply renders the book.html template with the book’s description as an argument (we can take a look at this template – it’s pretty simple). If the ?book parameter doesn’t refer to a book ID in the map, and if it’s a non-empty string, then it looks like:

  • A new template is allocated with the name “book”.
  • The template body is created by parsing the contents of userBook – the user’s input parameter. Assuming no errors…
  • The template is executed, with the argument being a blank instance of the ReadBook struct. The result is written out via w as an HTTP response.

Let’s try this, by visiting /books?book=12345. Sure enough, the result is just the string 12345.

Let’s try using some Go template language. The list of default functions might be useful. We find that /books?book={{gt 2 1}} returns true, and /books?book={{gt 1 2}} returns false, so sure enough, our templates are executing.

This appears to be a Server-side template injection vulnerability. Under the conditions of most webapps and templating languages, a user should never really be able to control the templates, just the data that goes into them. Granted, Go’s templating language is fairly well locked-down, and is indeed occasionally exposed to the user (Grafana Loki does this, for example), but it’s only as secure as the functions that are available to be called through it.

The default set of functions is also pretty locked down (it really only gives us simple stuff like gtslice, and printf), and the app doesn’t use .Funcs() to make any extra ones available to the template. But it does pass us an empty ReadBook struct as an argument. Let’s look at the top of the code again.

type ReadBook struct{}

func (rb ReadBook) ReadBook(filePath string) string {
    // read file off disk ...
}

Go doesn’t have classes, but it does have methods that you can define on types, by adding a reciever argument after func. In this case, the ReadBook struct has the ReadBook() method defined on it, so if I instantiate r := ReadBook{}, I can then call r.ReadBook("file.txt").

The argument to a Go template is accessed with . (a single period). Let’s try calling the ReadBook function on it. Something like /books?book={{.ReadBook("blah")}}?

Template parsing error: template: book:1: unexpected "(" in operand

Go templates have a weird function-call syntax. Let’s fix that. /books?book={{.ReadBook "blah"}}

Error reading file: open blah: no such file or directory

Closer… /books?book={{.ReadBook "flag.txt"}}

MetaCTF{S3rv3r_S1d3_T3mpl4t3_1nj3ct10n_1n_G0l$nG!!}

And there’s the flag!