Solution to Adepts of 0xCC Challenge 01
Authors: @kachakil and @samsa2k8
Summary
A PHP challenge where all source code, configuration and Docker files were provided at: https://github.com/Adepts-Of-0xCC/CHALLENGE-01
The target machine (containing the real flag) was available at: http://challenge01.adepts.of0x.cc/
The goal was to read the contents of the /flag
file.
Unsuccessful approaches
The Dockerfile
reveals that the PHP version installed on the host was the latest one, and no special flags or additional modules were used. It's also trivial (and highly recommended) to run that container locally. The majority of the testing was done locally, by removing some useful functions from the disabled ones (like print_r
, or get_declared_classes
) in the php.ini
file, by returning all errors and warnings, or by running PHP from the CLI. Alternatively, one can print variables like PHP_VERSION
to see that we were targeting v7.4.3
.
By analyzing the contents of the custom php.ini
, we can see that the only differences with the default configuration file are in the open_basedir
and disable_functions
directives, so the challenge seems to be related to bypassing these directives.
There are several write-ups and publicly available documentation explaining different techniques on how to bypass such configurations, but most of them wouldn't work in this scenario. For instance, it wasn't possible to call functions like ini_set
, chdir
, putenv
, error_log
or email
, among many others, because these were included in the list of disabled functions, so in principle it won't be possible to abuse LD_PRELOAD
, "tightening" the open_basedir
configuration with "..", etc.
It's important to highlight that some functions (such as chdir
) were listed as "enabled" in the challenge's main page, while they weren't actually usable. This is because the script was using function_exists
to show that information. According to the documentation, this doesn't guarantee that they'll work:
A function name may exist even if the function itself is unusable due to configuration or compiling options
Achieving (limited) PHP code execution is trivial. You just need to upload an arbitrary file and access the resulting URL, which will be a file with the .php
extension. For instance, using curl
:
curl -v -F 'owl=@exploit.php' http://challenge01.adepts.of0x.cc/
Reading or writing files within the open_basedir
restricted paths was also easy, using classes like SplFileObject
. Listing directories anywhere was also possible, using the FilesystemIterator
or DirectoryIterator
classes, or the glob
wrapper, for instance.
In some scenarios, that may have been enough to solve the challenge, by writing .htaccess
or .user.ini
files, or if the flag was hidden somewhere inside the open_basedir
paths, or by creating files with other extensions or folders like cgi-bin
, etc. However, none of these attacks were possible with the current configuration.
It was also possible to invoke a few of the disallowed functions (such as finfo_open
or finfo_file
, using the equivalent finfo
class, for instance), but that didn't lead to any useful result. Again, the open_basedir
restrictions were in place.
Other failed attempts involved creating ZIP and Phar files, even with symlinks inside, but none of that worked. The idea was to decompress a file resulting in a symlink, or access one of these files from a PHP wrapper and maybe enabling access to the /flag
file. Anyway, even if you create a symlink manually from the OS, this wouldn't bypass the open_basedir
restrictions, but in that case, you could access the file directly and Apache would return the flag, so it was definitely worth a try.
Since libxml
was apparently enabled and the PHP documentation says that classes like DOMDocument
should be enabled by default in our target version, other potential attacks may have involved XXE vulnerabilities. That also didn't work, since all XML functions were disabled, and the XML classes were unavailable.
Solution
One of the many attempts involved a public exploit for a bug which doesn't seem to be fixed in our PHP version:
https://github.com/mm0r1/exploits/blob/master/php-concat-bypass/exploit.php
This didn't return any HTTP response, but looking at the error.log
file we noticed that it was as a result of a "Segmentation fault". Running the exploit from the CLI was even clearer. After exploring dozens of other potential solutions, we tried the same exploit in a default installation, and it worked. Then, we tried it in the original Docker container, but with a default php.ini
file and it also worked.
After enabling the exploit's internal logs and noticing where it was failing, we carefully analyzed which PHP functions were used by the exploit and we quickly noticed that pack
function was there, and in the list disallowed ones too. Fortunately, this function wasn't hard to rewrite with an equivalent, so we replaced this part of the code:
private function free($addr) {
$payload = pack("Q*", 0xdeadbeef, 0xcafebabe, $addr);
With this:
private function pack_qword($q) {
$st = "";
for ($i=0; $i<8; $i++) {
$st .= chr($q&0xff);
$q /= 256;
}
return $st;
}
private function free($addr) {
$payload = self::pack_qword(0xdeadbeef).self::pack_qword(0xcafebabe).self::pack_qword($addr);
Obviously, we also adapted the payload at the very beginning of the script as follows:
new Pwn("cat /flag");
Finally, we uploaded the final exploit and accessed the URL from the path returned by the application.
The flag was: AdeptsOf0xCC{PHP_is_the_UAF_land}