Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kachakil/17ea369f5372e51f37ff825345fd9248 to your computer and use it in GitHub Desktop.
Save kachakil/17ea369f5372e51f37ff825345fd9248 to your computer and use it in GitHub Desktop.

Solution to Adepts of 0xCC Challenge 01

Authors: @kachakil and @samsa2k8


A PHP challenge where all source code, configuration and Docker files were provided at:

The target machine (containing the real flag) was available at:

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'

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.


One of the many attempts involved a public exploit for a bug which doesn't seem to be fixed in our PHP version:

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}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment