无字母webshell学习
0x00 前言
本篇文章学习P神的两篇文章:
0x01 第一道题目
问题 · 禁用
问:如何编写一个不使用数字和字母的webshell,具现为如下代码:
1 |
|
思路 · 变换
题目的if
语句和eval
语句不仅有先后关系,还具有功能上的差异:if
语句不能把shell
的内容当做PHP
代码执行,但是eval
可以。这点差异是本文方法的基础。
本节方法的核心在于字符变换。如果我们的payload
本身不具有字母和数字,但是在eval
里可以变换成危险的字母和数字,那么就可以webshell
。
P神文章主要使用PHP5
的assert()
,本文也一样。下面引用P神原文:
不过在此之前,我需要说说php5和7的差异。
php5中assert是一个函数,我们可以通过
$f='assert';$f(...);
这样的方法来动态执行任意代码。但php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点。但也无需过于担心,比如我们利用file_put_contents函数,同样可以用来getshell。
下文为了方便起见,使用PHP5作为环境,PHP7相关的利用方法自己探索吧。
本人使用PHPstudy_pro
的php5.5.9nts
。
法一 · 异或
读者可以先去了解PHP
的异或机制
在PHP
中,两个字符异或后得到一个字符。如下:
我们可以通过选择一些不可见字符和符号异或出特定的字符,再拼接成攻击语句。P神给出了一个示例:
1 |
|
因为其中存在很多不可打印字符,所以我用url编码表示了
执行结果如下:
大家有没有想过一个问题:为什么这里一定要用assert()
,或者不能把assert()
换成eval()
?
这是因为payload
前面的部分构造了$_POST[_]
,但是这仅仅只是字符串而不能发挥功能,所以要想办法把这个字符串当做PHP
代码执行。eval()
虽然可以实现这个功能,但是eval()
本身不是函数而是语言结构,所以通过字符串拼接eval()
没有意义。但是assert()
在PHP5
(可能更高的版本也一样)是函数,可以通过字符串并执行$_POST[_]
。
法二 · 取反
与法一思路一致,都是通过字符变换实现webshell
。不过这里采用取反的思想,并且在Hackbar
上要手动编码一次。
给出代码:
1 |
|
法三 · 递增
PHP
的++
运算符效果如下(--
运算符不具有):
如果具有a
字符和A
字符,我们便可以构造出所有字母。因为PHP
函数是大小写不敏感的,所以我们执行ASSERT($_POST[_])
即可,便省去了获取小写a
。
怎么获取A
字符呢?在PHP
中,若强制连接数组和字符串,数组会被转换成字符串,其值为Array
。如下图:
但是这样仍然不够简单。思考深入一点,只要把数组当做字符串执行,数组就会被转换成字符串Array
。看下图:
通过上面的技巧,我们可以构造出所有大写字符。再通过字符拼接,我们可以得到ASSERT($_POST[_]);
字符串。具体的webshell
P神已经写好了:
1 |
|
结果如下图:
小结 · 感悟
本题三法的核心都是变换。这其实基于一个简单的逻辑:
webshell
代码在执行时必须要字母和数字,虽然这些被禁用了,但是因为webshell
核心语句在PHP环境里且执行前是可控的,那么我们可以在这个空档期进行变换,从而得到webshell
的核心语句并顺利在PHP
环境下执行。
上述的时期:PHP环境里且执行webshell
核心语句前,这个时期其实就是下面代码的A,B,C···
:
1 | eval('A,B,C···core statement') |
本题最大的感悟就是思路要放开,而思路放开的基础就是想到问题的本质。我浅析一下本题:
题目在
eval()
前进行过滤,那我们就得在eval()
里得到正常的webshell
语句并执行。eval()
允许多语句(几个分号的事情),这就为我们得到正常的webshell
语句提供了很大的操作空间。至此,我们的问题就是如何得到正常的webshell
语句,对此本题的三个方法都有讲述。至此问题解决。
就这么简单吗?有时并非如此。如果服务端检测到这么多的符号熵值,还是会怀疑。
评论 · 优质
0x02 第二道题目
问题 · 升级
问题如下:
1 |
|
同上题相比,这题不仅限制了$
、_
,还限制了payload
的长度。
PHP7 · 轻松解决
在PHP7下,我们可以轻松通过取反操作实现webshell
。这是因为PHP7前不允许($a)();
,PHP7
修改了表达式执行的顺序,具体逻辑如下图:
PHP7前是不允许用
($a)();
这样的方法来执行动态函数的,但PHP7中增加了对此的支持。所以,我们可以通过('phpinfo')();
来执行函数,第一个括号中可以是任意PHP表达式。
综上所述,我们可以构造($a)();
,其中把$a
设置成phpinfo
,完整的payload
如下:
1 | (~%8F%97%8F%96%91%99%90)(); |
根据PHP7
的表达式执行逻辑,先取反,再执行phpinfo()
。效果如下:
PHP7
下我们轻松解决了该问题
PHP5 · .
与glob
如果采用上面PHP7
的方法,在PHP5
中会报错:
具体原因上面有谈到过,这里就不赘叙了,主要谈谈PHP5
中该怎么解决题目。
这里P神的思考非常经典,我引用一段:
以及P神的经验:
这些都是学好安全的必备品质,这里不赘叙,言归正传。
使用反引号执行系统命令,不仅成功避免了使用字母和数字,还节约了很多字符。我们现在可以执行系统命令了,但是要怎么查看文件呢?
查看文件的指令由两部分组成:命令和文件路径。这两方面我们都得考虑,对此P神给出了两个提示:
命令可以使用.
,文件名使用glob
通配符来过滤,如下示例:
1 | . /???/?????? |
执行结果:
我一开始想的是/etc/passwd
,但是很明显有文件先匹配然后执行了,这说明上面指令有很多符合条件的文件,如下(还有很多文件):
如此我们便无法读取到目标文件,必须对路径进行过滤。有两个方法可以帮助我们:
[^x]
表示“这个位置不是x
”,利用这一点可以过滤许多文件,但是要求这些文件路径有符号或者特殊字符,^
可以用!
代替glob
支持利用[0-9]
来表示一个范围。比如[@-[]
包含ASCII
码在@
和[
间(包括@
和[
)的字符,我们可以成功过滤掉该位置是小写字符的文件。
但是对于本人在云服务器上的文件(如上图)并不能很好地契合实验效果,就不再展示了,推荐读者阅读P神的文章。
在文件路径适宜的情况下,通过以上的过滤可以实现读取文件,P神payload
:
1 | ?code=?><?=`. /???/????????[@-[]`;?> |
结果也用P神的:
至此第二种方法成功。
0x03 总结
学习P神的文章受益匪浅!P神看待问题从本质出发、思路广泛、研究细致、善读文档,值得吾辈学习。