PHP类型
PHP连接,拦截到三个流量包
第⼀个数据包
第⼀个包设置了 session:set-cookie
把pass参数后的值进⾏url解码:
shells.cryptions.phpXor.PhpEvalXor.java 追踪 inti()
进⼊函数:this.evalContent = generateEvalContent();
对key参数后⾯的内容⽤⼀下脚本进⾏解密:
<?php
function encode($D,$K){
for($i=0;$i<strlen($D);$i++) {
$c = $K[$i+1&15];
$D[$i] = $D[$i]^$c;
}
return $D;
}
function isGzipStream($bin){
if (strlen($bin)>=2){
$bin=substr($bin,0,2);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
switch ($typeCode) {
case 31139:
return true;
break;
default:
return false;
}
}else{
return false;
}
}
$pass = "填充";
$key='3c6e0b8a9c15224a';
$data=encode(base64_decode($pass),$key);
if(isGzipStream($data)){
echo "666\n";
echo gzdecode($data);
}
else{
echo $data;
}
?>
得到结果如下,就是右边的⼤⻢:
第⼆个数据包
数据包返回“ok” 则表⽰连接成功
burp返回的数据如下:16位和后16位是md5值,中间标⻩的才是服务端返回的数据
依旧⽤上述脚本进⾏解密:得到结果如下
第三个数据包
对key参数的值进⾏解密,得到参数名:getBasicInfo → 字面意思,就是获取服务端的基本信息
分析下Response,对前后16个值进⾏拆分,⽤上述脚本解密,得到如下:
特征分析
PHP类型:
-
第⼀个包很⻓,注⼊⼤⻢,⽤来进⾏后续的操作,包含 run、bypass_open_basedir、formatParameter、evalFunc 等⼆⼗多个功能函数,具备 代码执⾏、⽂件操作、数据库操作等诸多功能;且⽤于设置session,返回包存在:set-cookie
-
第⼆个包之后都存在cookie值,是第⼀包设置的
-
Response 的值都是前后16位md5值包裹真正的返回内容,如果载荷是PHP_XOR_BASE64,md5的计算⽅式为:substr(md5($pass.$key),0,16);
其中,pass=设置的连接密码,key是设置的md5值前16位
而 md5 值落在 0123456789ABCDEF范围内,因此很容易去匹配,正则匹配类似于(?i:[0-9A-F]{16})[\w+/]{4,}=?=?(?i:[0-9A-F]{16}) 需要注意的是md5需要同时匹配字⺟⼤⼩写两种情况,因为在JAVA版webshell响应中为⼤写字⺟,在PHP版中为⼩写字⺟。
-
cookie中最后的分号
-
请求字段[弱特征]:
5.1 所有请求中Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
5.2 所有响应中Cache-Control:no-store, no-cache, must-revalidate
JAVA格式
观察webshell的⽣成
进入代码 core.ui.component.dialog.GenerateShellLoder
存在⽅法: generateButtonClick()
先判断 密码、密钥、有效载荷以及加密器是否都已填写。
如果都填写之后则通过 Application.getCryption() 函数获取到⼀个Cryption 加密器的实例, 然后调⽤该实体的 generate ⽅法⽣成webshell⽂件的字节数组
Cryption 是⼀个接⼝,找到继承该接⼝的实体类:
实现⽅法:generate() 传⼊password和key
接着调⽤
Generate.GenerateShellLoder(password, functions.md5(secretKey).substring(0, 16), false);
这⼉对key进⾏md5加密,并取前16个字符。还有形参:isBin 接着追踪,来到 shells.cryptions.JavaAes.Generate 调⽤⽅法:GenerateShellLoder(String pass, String secretKey, boolean isBin)
该⽅法⼜调⽤:GenerateShellLoder("", pass, secretKey, isBin) 这⼉的形参isBin是⽤来指定读取的模板
为true,则获取raw,flase就是base64
模板⽂件如下⽬录:
在读取完 base64GlobalCode.bin 以及 base64Code.bin ⽂件内容后, ⾸先⽤传⼊ Generate.GenerateShellLoder ⽅法时的 password 和处理过的 secretKey 来替换从base64GlobalCode.bin 模板读取出来的 {pass} 以及 {secretKey} 字符
接着根据⽤户选择的格式⽣成对应的shell⽂件
之后程序⽤处理过后的 base64GlobalCode.bin内容以及读取到的base64Code.bin 内容来分别替换shell.jsp⽂件中的{globalCode} 以 及 {code}字符, 替换成功后就会在⽤户指定的⽬录下⽣成真正的哥斯拉jsp版本的webshell⽂件。
模板⽂件:shell.jsp
通信特征
来到对应⽬录⽂件:core.ui.component.frame.ShellSetting
testButtonClick() ⾸先调⽤ updateTempShellEntity() 对类ShellEntity 进⾏初始化;shellContext 就是ShellEntity的实例对象
再调⽤ initShellOpertion() ⽅法作⽤如下:
-
从shellEntity 获取http对象 (每个ShellEntity对象都有⾃⼰的http成员变量, ⽤于各⾃ShellEntity的Http请求)
-
通过payload名称获得Payload接⼝的实例类
-
通过payload和Cryption名称获得Cryption接⼝的实例类
Payload接⼝类型负责定义webshell管理⼯具的功能动作, ⽽ Cryption接⼝类型负责定义webshell管理⼯具的加解密⾏为。上⽂中 Cryption接⼝的实现类型是JavaAesBase64类型, 这⾥的Payload接⼝的实现类型为JavaShell类型
接着代码会调⽤ cryptionModel.init() 和 cryptionModel.check()先来到 init() ⽅法,这个⽅法会将属性state设置为true,以此通过 check() ⽅法的逻辑判断
到43⾏ this.payload = this.shell.getPayloadModule().getPayload();
获取class⽂件,然后调⽤ dynamicUpdateClassName("payload", data); 步⼊⽅法查看
综上代码简单理解:获取到 payload.class类的字节码内容后, 该init⽅法将获取到的字节码内容通过http请求发送到服务端
我们来到45⾏:this.http.sendHttpResponse(this.payload); ⽤来发送数据,其中包含了加密的算法
继续向下分析,再通过 this.cryptionModel.check() 的逻辑判断后,来到⽅法:
this.payloadModel.init(this);
this.payloadModel 指的就是 JavaShell 做了些成员变量赋值的操作,不具体分析
还是回到ShellEntity的 initShellOpertion() ,调⽤完 payloadModel.init(this) 后,继续调⽤ this.payloadModel.test()
这个⽅法会对返回的内容进⾏判断,如果返回ok 则判断连接成功,执⾏下⼀步操作
这个⽅法对应建⽴连接的第⼆个数据包,⽤⼯具进⾏解密操作:ok
我们再细看下这个 test() ⽅法,究竟是怎么实现的,他调⽤了 evalFunc() ⽅法,传⼊三个参数
调⽤ fillParameter() ⽅法,由于我们传⼊的第⼀个参数为null,因此跳过if语句,执⾏ parameter.add(”methodName”,funcName);
当然从客户端发送的数据都会gzip压缩,然后再通过 sendHttpResponse() ⽅法发送给服务端
对其配对的形式有点类似于:key-value 我们⽤⼯具进⾏解密,如下所⽰
当然了,第三个数据包是⽤来获取服务端的基础信息:⽅法名 getBasicsInfo
java的流量细节和php差不多的,能把代码捋下来 研究他的流量细节都没问题
欢迎大家关注麋鹿安全,我们的文章会第一时间发布在公众号平台,如果不想错过我们新鲜出炉的好文,那就请扫码关注我们的公众号!