Don't BF Me! (WeCTF 2020)
Solved solo!
The Challenge
Category | Key Exploit |
---|---|
Web | Variable 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:
index.php
is processed into the HTML page we interact withlogin.php
registered as the form submit action inindex.php
, so it handles form submissions from that pageconstants.php
is imported at the tops of the other two files to provide useful constants
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:
|
|
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:
|
|
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:
|
|
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:
|
|
And after:
|
|
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.