前言
这个坑就直接填了吧,毕竟这东西还是很有意思的,而且对很多waf都可以起到很好的效果。
无字符webshell
众所周知,我们经常写入的webshell不外乎就是那些常见的一句码,最多过滤一些关键字或者关键符号。但是有些题目,却不允许我们上传的webshell中存在数字与字母,这时候就需要编造特殊的webshell来绕过了。
编写这类无字符webshell的核心就是,将非字母、数字的字符经过各种变换,最后能构造出a-z中任意一个字符。然后再利用PHP允许动态函数执行的特点,拼接处一个函数名,然后动态执行之即可。
方法一
利用php两个字符串异或后得到的仍是一个字符串的操作,通过异或来代替字母与数字,达到绕过的目的:
1 | <?php |
当然我们可以通过这个脚本获取异或值:
1 | <?php |
方法二
除了异或,在php中,取反也是可以得到字符串的,例如'和'{2}
的结果为"\x8c"
,其取反就为s
。
这种是利用中文utf-8编码的性质来取反的,和的utf-8编码的第三部分的取反结果为s,然后利用这个性质,采用.=的方式逐个叠加成马。而用到的0~2数字我们可以通过字符比较大小方式确定一个0或者1,然后再通过++或者–的方式取到一个任意大小的数字。
放一个payload:
1 | <?php |
或者我们直接用汉字取反,然后通过.=的方式构建payload:
1 | <?php |
可以用下面这个exp获取取反的字符:
1 | <?php |
方法三
这个方法运用到了php递增的递增运算,而php的自增运算和c是不一样的:
递增/递减运算符
比如:
1 | $a='a'; |
所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。那么怎么获取到字符a呢?我们发现数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。
在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array:
1 | echo ''.[] |
而由于php大小写不敏感,因此取第一个大写A就可以了,payload:
1 | <?php |
ps.assert不适用于高版本php,高版本php可以使用file_put_contents等其他函数替代
方法四
如果$
或者长度遭到限制,上面的方法就失效了,不过在php7中支持使用($a)();
这种方法来调用命令,因此我们可以直接使用取反来构建payload:
1 | (~%8F%97%8F%96%91%99%90)(); |
方法五
但是在php7以前都不支持这种表达,这时我们要应用linux shell下的两个知识点:
- shell下可以使用
.
来执行任意脚本文件 - linux文件名支持glob通配符替代
比如我们将echo "ls"
写入mrl64中,然后执行.mrl64
,我们发现ls被执行成功了。
而通配符都有哪些呢:
1 | *可以代替0个及以上任意文件 |
我们发送一个上传文件的POST包时,文件会被保存在这个/tmp/phpXXXXXX临时文件夹下,XXXXXX是六位随机大小写字母,由于干扰我们的文件全部都是小写的,因此我们可以通过限制大写来执行文件。由于大写字母的前后是@
与[
,因此payload:
1 | `.+/???/????????[@-[]` |
因此这样就可以执行我们上传文件的内容,我们可以直接命令执行,也可以写入一句马等等。
方法六
这个方法使用在python上的,因为没有学习过pythonjail相关的知识,因此就不做详细分析了,把文章放出来作为参考:
一道有趣的pyjail题目分析
关于无字符webshell,可以仔细研究下p牛的两篇文章:
一些不包含数字和字母的webshell
无字母数字webshell之提高篇
无参数rce
无字符webshell过滤的是字母及数字,或许还有些特殊符号,而无参数rce中过滤的往往都是符号,因此这类payload的构造往往和函数的应用有关。
方法一
php中的session_id()可以读取cookie中的PHPSESSID值,而这个函数的使用需要我们开启session,这里就要用到session_start()。因此我们可以利用这两个函数构造payload:
1 | eval($_GET('a')); |
方法二
这个方法运用到了get_defined_vars()
,这个函数会返回由所有已定义变量所组成的数组,我们通过GET与POST方法传入的变量都可以被读取出来。而由于这个函数本身返回的是个二维数组,因此我们可以通过读取数组的函数来取值。
一些读取数组的函数:
1 | end() - 将内部指针指向数组中的最后一个元素,并输出。 |
因此我们这样构建payload:
1 | eval($_GET('a')); |
分析这个payload,首先将二维数组转化为一维数组,由于GET方式默认就是第一个元素,因此直接用current()就可以了,POST方式就用next(),以此类推。接着由于我们再次定义了$b并进行传参,因此current(get_defined_vars())
得到的结果应该为:
1 | array(2){ |
这时候我们取最后一个元素,得到的就是phpinfo();
,因此就可以执行我们想要执行的语句了。
但如果网站的过滤较为严格时,我们可能要使用$_FILES
,这里我们就需要上传文件来解决问题了,编写exp:
1 | import requests |
方法三
getallheaders()可以获取在apache2环境下的http头文件,并且返回的也同样是数组,因此我们可以更改头文件的内容,比如在头文件最后添加一个mrl64=phpinfo();
,这样getallheaders()返回的头文件中的最后一个内容就是["mrl64"]=>string(10) "phpinfo();"
了,利用end()将这个内容读取出来,就可以执行命令了,payload:
1 | eval($_GET('a')); |
方法四
这个方法的思路跳出了常规的rce,核心思想是直接读取文件,和任意文件读取比较相近。
getcwd()
- 获取当前目录,返回字符串scandir()
- 进行当前目录遍历,返回数组dirname()
- 进行目录上跳chdir()
- 更改当前目录readfile()
- 读取文件
比如我们想要读取/var/www/flag
,就可以这样构建payload:
1 | eval($_GET('a')); |
详细的无参数rce,可以参考下面这篇文章:
PHP Parametric Function RCE
总结
以上这些方法都是从各路大神里总结出来的,对于一些经典的rce题目应该会有所帮助,相信还有更多的方法等待我们去发现、去挖掘。