Evan's Code and Other Things

Don't BF Me! (WeCTF 2020)

Solved solo!

The Challenge

CategoryKey Exploit
WebVariable injection

The handout contained server-side PHP code and the HTML for a small website with a password entry form. There was also a Dockerfile in there, so players could spin up local instances for testing.

Original Prompt

Shou uses Recaptcha for his site to make it “safer”.

Hint: The password is so long that makes any bruteforcing method impotent.

Handout: https://github.com/wectf/2020p/blob/master/dont-bf-me/handout.zip

Summary

The files:

The form and backend code use Google’s recaptcha service to verify form submissions. login.php contains checks for a bad or missing g-recaptcha-response parameter. It returns error messages if the checks detect an issue.

After those checks, there’s a simple check for the correct password:

1
2
3
// check password
if($password == $CORRECT_PASSWORD) {
    echo $FLAG;

After reading the prompt, I decided to take them at their word that the password couldn’t, or shouldn’t, be brute-forced. If nothing else, millions of Recaptcha API calls might get expensive. Also, it’s PHP so the odds of finding a vuln in the server-side code seemed pretty good ;)

Finding a vulnerability

Types?

I didn’t know much about PHP going into this, apart from its bad reputation, the fact that recent versions have addressed some issues that contributed to that reputation, and the dreadful === operator.

The use of == in the password check stood out to me. However, careful consideration of documented type coercion rules for ==, along with a few tests, convinced me this wasn’t it. My input would be compared as a string to the string in CORRECT_PASSWORD.

Next, I decided to look at how form fields were being read in, but took a quick detour on the way.

PHP version

PHP has been changing in recent years to address the issues that earned it a bad reputation. While searching through PHP documentation for this challenge, I found a lot of cautions and notes about differences between versions. A quick look at the provided Dockerfile seemed wise:

1
FROM php:7.3-apache

PHP version 7.3. Noted.

parse_str

It just so happens that this function was changed in PHP 8.0 to remove dangerous behavior! In PHP versions before 8.0, if parse_str is given only one its first argument (a string), it parses the input as a query string from a URL and stores the parsed values in variables with the same names as the query parameters. This overrides variables in scope when parse_str is called if there are query parameters with the same names.

In login.php, parse_str is used in the deprecated, unsafe way:

1
parse_str($_SERVER["QUERY_STRING"]);

Since this line comes after include "constant.php";{:.php}, any query parameter that shares a name with a constant can be used to override that constant in login.php!

My solution

Sending a custom request to the challenge server would allow me to set both CORRECT_PASSWORD and the URL used to verify the Recaptcha token. However, I felt like setting up a publicly-hosted JSON file or finding one to point to would be way too much effort.

So I simply pressed F12 to open the developer tools in my browser and added a single line of HTML to the form by right-clicking in the inspector and selecting “Edit as HTML”.

Before:

1
2
3
4
...
<form class="form-group" method="GET" action="login.php">
    <input type="password" class="form-control" name="password" placeholder="Password">
    ...

And after:

1
2
3
4
5
...
<form class="form-group" method="GET" action="login.php">
    <input type="text" class="form-control" name="CORRECT_PASSWORD" placeholder="Password">
    <input type="password" class="form-control" name="password" placeholder="Password">
    ...

All that remained was to type the same thing in both fields and click submit!

Since I hadn’t messed with or omitted the Recaptcha field, the server verified my request’s Recaptcha successfully and gave me the flag.