前言

正常测试的时候,发现java后端对json处理都会去测试一下是否存在反序列化,但是后端处理json的组件很多,比如fastjsonjacksongson等,怎么判断是否使用了fastjson呢?
有一个简便无危害的方式,就是通过dnslog来判断。
大佬们讨论的[issue](https://github.com/alibaba/fastjson/issues/3077)

说明

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

  1. public static String name(int value) {
  2. switch(value) {
  3. case 1:
  4. return "error";
  5. case 2:
  6. return "int";
  7. case 3:
  8. return "float";
  9. case 4:
  10. return "string";
  11. case 5:
  12. return "iso8601";
  13. case 6:
  14. return "true";
  15. case 7:
  16. return "false";
  17. case 8:
  18. return "null";
  19. case 9:
  20. return "new";
  21. case 10:
  22. return "(";
  23. case 11:
  24. return ")";
  25. case 12:
  26. return "{";
  27. case 13:
  28. return "}";
  29. case 14:
  30. return "[";
  31. case 15:
  32. return "]";
  33. case 16:
  34. return ",";
  35. case 17:
  36. return ":";
  37. case 18:
  38. return "ident";
  39. case 19:
  40. return "fieldName";
  41. case 20:
  42. return "EOF";
  43. case 21:
  44. return "Set";
  45. case 22:
  46. return "TreeSet";
  47. case 23:
  48. return "undefined";
  49. case 24:
  50. return ";";
  51. case 25:
  52. return ".";
  53. case 26:
  54. return "hex";
  55. default:
  56. return "Unknown";
  57. }
  58. }

分析

  • fastjson 1.2.68
  • jdk 8u261

分析一下checkAutoType
03.Fastjson的dnslog探测方式分析 - 图1
这个黑名单检测绕过后,会来到如下几个if语句,写了个简单的注释

  1. // 从ConcurrentHashMap类变量mapping中尝试获取这个类,mappings有点像维护的一个基础类库
  2. clazz = TypeUtils.getClassFromMapping(typeName);
  3. // 如果mapping里面没有这个类,就会尝试从this.deserializers.buckets这个IdentityHashMap类的Map中尝试获取clazz,这个有点像开发者维护的一个可信任类
  4. if (clazz == null) {
  5. clazz = this.deserializers.findClass(typeName);
  6. }
  7. // 如果clazz还是为null,就会尝试从this.typeMapping中去获取类,但这个类默认是空的
  8. if (clazz == null) {
  9. clazz = (Class)this.typeMapping.get(typeName);
  10. }
  11. // 如果在白名单,就直接加载这个clazz
  12. if (internalWhite) {
  13. clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, true);
  14. }
  15. // 如果clazz不为null,且不满足后续的判定条件,就直接返回clazz
  16. if (clazz != null) {
  17. if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {
  18. throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
  19. } else {
  20. return clazz;
  21. }
  22. }

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

  • mappings

    1. ((ConcurrentHashMap) mappings).keySet()

    03.Fastjson的dnslog探测方式分析 - 图2

  • this.buckets

    1. Object[] a = new Object[9000];
    2. for(int i = 0; i < this.buckets.length; ++i) {
    3. IdentityHashMap.Entry bucket = this.buckets[i];
    4. if (bucket != null) {
    5. for(IdentityHashMap.Entry entry = bucket; entry != null; entry = entry.next) {
    6. Object key = bucket.key;
    7. a[i] = key.name;
    8. }
    9. }
    10. }
    11. return a;

    03.Fastjson的dnslog探测方式分析 - 图3

  • this.typeMapping

默认为空
汇总一下所有类:

  1. java.lang.IndexOutOfBoundsException
  2. java.lang.Integer
  3. java.lang.NoSuchFieldException
  4. java.lang.Long
  5. java.math.BigInteger
  6. java.lang.LinkageError
  7. java.lang.StringIndexOutOfBoundsException
  8. java.lang.StackOverflowError
  9. long
  10. java.lang.VerifyError
  11. java.util.LinkedHashMap
  12. java.util.Calendar
  13. java.lang.StackTraceElement
  14. [long
  15. java.lang.NoSuchMethodError
  16. java.util.concurrent.atomic.AtomicLong
  17. java.util.TreeMap
  18. java.util.Date
  19. java.lang.NoSuchFieldError
  20. java.util.concurrent.atomic.AtomicInteger
  21. java.lang.Short
  22. java.util.Locale
  23. java.lang.InstantiationException
  24. java.lang.SecurityException
  25. java.sql.Timestamp
  26. java.util.concurrent.ConcurrentHashMap
  27. java.util.UUID
  28. java.lang.IllegalAccessError
  29. com.alibaba.fastjson.JSONObject
  30. [short
  31. java.util.HashSet
  32. [byte
  33. java.lang.Boolean
  34. java.sql.Date
  35. short
  36. java.lang.Object
  37. java.util.BitSet
  38. [char
  39. java.lang.Float
  40. java.math.BigDecimal
  41. java.lang.Character
  42. java.lang.InternalError
  43. [double
  44. byte
  45. double
  46. java.lang.Exception
  47. java.lang.Double
  48. [B
  49. java.lang.TypeNotPresentException
  50. [C
  51. [D
  52. java.text.SimpleDateFormat
  53. [F
  54. [I
  55. java.util.TreeSet
  56. [J
  57. java.util.ArrayList
  58. java.lang.IllegalMonitorStateException
  59. com.alibaba.fastjson.JSONArray
  60. [S
  61. java.lang.String
  62. java.lang.Number
  63. java.util.LinkedHashSet
  64. [Z
  65. java.lang.NegativeArraySizeException
  66. java.lang.NumberFormatException
  67. java.lang.RuntimeException
  68. char
  69. java.lang.OutOfMemoryError
  70. java.lang.IllegalStateException
  71. java.sql.Time
  72. java.lang.NoSuchMethodException
  73. java.util.Collections$EmptyMap
  74. [boolean
  75. float
  76. java.lang.AutoCloseable
  77. java.lang.NullPointerException
  78. java.lang.Byte
  79. [int
  80. com.alibaba.fastjson.JSONPObject
  81. java.lang.Cloneable
  82. java.lang.IllegalAccessException
  83. java.util.IdentityHashMap
  84. java.util.HashMap
  85. java.lang.NoClassDefFoundError
  86. java.util.Hashtable
  87. java.util.WeakHashMap
  88. java.lang.IllegalThreadStateException
  89. java.lang.IllegalArgumentException
  90. int
  91. java.util.concurrent.TimeUnit
  92. boolean
  93. java.lang.InstantiationError
  94. java.lang.InterruptedException
  95. [float
  96. java.util.regex.Pattern
  97. com.alibaba.fastjson.JSONArray
  98. java.lang.StringBuilder
  99. java.nio.charset.Charset
  100. java.math.BigDecimal
  101. char
  102. java.io.File
  103. java.lang.String
  104. boolean
  105. java.net.InetSocketAddress
  106. java.lang.Character
  107. java.lang.Number
  108. java.util.concurrent.ConcurrentHashMap
  109. javax.xml.datatype.XMLGregorianCalendar
  110. java.net.Inet4Address
  111. java.sql.Date
  112. java.util.Collection
  113. com.alibaba.fastjson.JSONPath
  114. java.util.concurrent.atomic.AtomicIntegerArray
  115. java.util.TreeMap
  116. short
  117. java.util.Currency
  118. java.sql.Time
  119. java.lang.Integer
  120. double
  121. java.lang.Class
  122. java.math.BigInteger
  123. com.alibaba.fastjson.JSONObject
  124. java.util.concurrent.atomic.AtomicBoolean
  125. java.util.concurrent.atomic.AtomicLongArray
  126. java.util.HashMap
  127. java.util.TimeZone
  128. java.lang.Comparable
  129. java.util.ArrayList
  130. java.text.SimpleDateFormat
  131. com.alibaba.fastjson.JSONPObject
  132. java.lang.StringBuffer
  133. byte
  134. java.io.Closeable
  135. java.lang.Double
  136. java.util.concurrent.atomic.AtomicInteger
  137. int
  138. java.lang.Float
  139. java.net.URL
  140. java.util.List
  141. java.lang.Object
  142. java.sql.Timestamp
  143. java.lang.StackTraceElement
  144. java.net.Inet6Address
  145. java.util.concurrent.atomic.AtomicLong
  146. java.net.URI
  147. java.util.UUID
  148. java.lang.Cloneable
  149. java.util.LinkedHashMap
  150. long
  151. java.lang.Short
  152. java.lang.Byte
  153. [C
  154. java.lang.ref.WeakReference
  155. java.lang.ref.SoftReference
  156. java.util.concurrent.ConcurrentMap
  157. java.util.Calendar
  158. java.util.Date
  159. java.util.Locale
  160. java.lang.Long
  161. java.util.Map
  162. java.io.Serializable
  163. java.util.concurrent.atomic.AtomicReference
  164. java.lang.Boolean
  165. float

挖掘

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

  1. java.net.InetSocketAddress
  2. java.net.Inet4Address
  3. java.net.URL
  4. java.net.Inet6Address
  5. java.net.URI

那我们从第一个开始

java.net.InetSocketAddress

初始payload

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

通过checkAutoType后,成功按照我们的预期返回
03.Fastjson的dnslog探测方式分析 - 图4
然后跟到371行,执行反序列化操纵,跳过这一步会直接抛出错误异常,所以我们跟进
03.Fastjson的dnslog探测方式分析 - 图5
跟进后,判断类,通过
开始判断token,这个时候我们的token是16,即,,不等于8,进入else
03.Fastjson的dnslog探测方式分析 - 图6
来到了parser.accept(token)后,不知道这个函数干啥的,跟进一下,发现原来是判断当前的token是不是传入的token,很明显这里我们是16不是12,所以会抛出异常。
03.Fastjson的dnslog探测方式分析 - 图7
那我们修改一下payload,给他提供一个 {,也就是token=12

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

这个时候我们就能成功通过accept这个函数了,继续向下
03.Fastjson的dnslog探测方式分析 - 图8
发现有个变量className,下方要求他等于address,且期望它下一位的token是17(:
那我们看看className是怎么得来的,跟进stringVal
03.Fastjson的dnslog探测方式分析 - 图9
发现这就是一个字符串切割函数,this为我们输入的字符串,因为this.hasSpecial为false
this.np就是当前这个字符串现在的游标位置,而this.sp则是切割的长度,我们再看下这个this.sp是怎么变得
前期阶段,是统计2个"中间的长度的,方便后面切割
03.Fastjson的dnslog探测方式分析 - 图10
后面估计也差不多,发现在进入反序列化过后,paper.accept的时候,会调用nextToken,其中会用到this.sp
跟一下发现,这个字符就是我们payload中{后面的,,期望一个",但是我们传入的是,,所以this.sp就等于0了
03.Fastjson的dnslog探测方式分析 - 图11
所以还需要增加一对",来增加this.sp的值,达到切割字符串的目的

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

更换payload后,成功通过上方的判断,进入this.scanString(),这里面this.sp++会判断我们输入的字符串长度
03.Fastjson的dnslog探测方式分析 - 图12
此时this.sp问题解决了,我们继续回到lexer.stringVal(),跟进,可以看到现在切割出来的字符串,就是我们传入的aaa
03.Fastjson的dnslog探测方式分析 - 图13
继续往后,发现className要等于address,所以给我们的aaa改成address即可,此外期望token是17,那还需要加一个:
03.Fastjson的dnslog探测方式分析 - 图14
所以修改后的payload

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

一路顺利,到了parser.parseObject这里会进行类型转换为InetAddress
03.Fastjson的dnslog探测方式分析 - 图15
又是一路顺利,到了deserializer.deserialze,只不过这次传入的TypeInetAddress
03.Fastjson的dnslog探测方式分析 - 图16
又回到了熟悉的地方
03.Fastjson的dnslog探测方式分析 - 图17
然后一顿调,抛出异常了
03.Fastjson的dnslog探测方式分析 - 图18
回看了一下,原来是这个地方要求是16(:),而我们是4(String)。。。
03.Fastjson的dnslog探测方式分析 - 图19
那哪个地方的String出问题了呢?
分析一下当前游标的位置,发现就是"address":后面应该是个,,而不是String
03.Fastjson的dnslog探测方式分析 - 图20
所以再改payload

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

又回到刚才的地方,这次过了,但是要求lexer.stringVal()val
03.Fastjson的dnslog探测方式分析 - 图21
熟悉的函数,刚才我们分析过了,就是需要一个"xxx",从上面也可以看出来"a"是我们后面的键值对中的键
那我们给a改成val

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

然后回到刚才的条件,继续向后,都是满足的,我们payload中val的值赋给了objVal
03.Fastjson的dnslog探测方式分析 - 图22
类型转换,赋值给strVal
03.Fastjson的dnslog探测方式分析 - 图23
返回InetAddress.getByName(strVal)
03.Fastjson的dnslog探测方式分析 - 图24
InetAddress.getByName会尝试通过域名获取IP
03.Fastjson的dnslog探测方式分析 - 图25
所以给strVal的值设置为dnslog的URL即可
最终Payload

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

成功
03.Fastjson的dnslog探测方式分析 - 图26

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

java.net.Inet4Address

初始payload

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

运行,经过checkAutoType后反序列化,和上面一样的错,需要val,而此时的值是a
03.Fastjson的dnslog探测方式分析 - 图27
我们给a换成val就行了

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

最后解析的时候,一样的方法,InetAddress.getByName(strVal)
03.Fastjson的dnslog探测方式分析 - 图28
最终POC

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

java.net.Inet6Address

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

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

java.net.URL

这个就不能直接来硬的了,根据我们之前分析过的ysoserial#URLDNS这条链,如果将一个URL对象放置到HashMap中,那么在进行计算hashcode的时候,会触发dnslog请求
所以我们让fastjson给对象反序列化为URL对象的后,再把它放到HashMap中即可

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

下断点分析,fastjson给前面还原成了URL对象,当做了key
03.Fastjson的dnslog探测方式分析 - 图29
然后继续跟,发现使用put将其添加到HashMap中,所以dnslog收到了请求
03.Fastjson的dnslog探测方式分析 - 图30

java.net.URI

  1. 不能和URL类类似似使用HashMap计算key的hashCode方法去发起请求
  2. 内部也没有类似的发起请求的方法

03.Fastjson的dnslog探测方式分析 - 图31
所以目前没找到利用方法emmmmm

总结

还有一些畸形的payload,可以去前言里面看看issue,原理都差不多
只是大佬们对fastjson的解析流程真的理解太透彻了,羡慕了

参考文献