本篇是漏洞编号:CVE-2018-1260的复现和分析过程笔记,https://pivotal.io/security/cve-2018-1260 


【前方高能预警】分析过程可能非常的细,如果看不下去的话,可以看看先知大佬的分析文,最后会贴出地址。


安装Demo:

    从GitHub搜索找到了spring-security-oauth2-example 的代码,下载导入IDEA,配置JDK8.0

image.png

        

    配置mysql连接,创建对应的数据库和表,这里提供一份sql文件,创建数据库alan-oauth,导入sql即可。地址:demo.sql

image.png       

     为了复现这个漏洞需要对源码进行修改,找到src/main/java/config/OAuthSecurityConfig.java 修改configure函数为下面内容:

@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        clients.withClientDetails(clientDetails());
        clients.inMemory()
                .withClient("client")
                //.secret("secret")
                .authorizedGrantTypes("authorization_code")
                .scopes();
    }


        到此环境已经准备好,现在可以准备调试复现漏洞了。

复现&调试

        复现:

        使用sping-boot run 启动环境,访问GET类型的授权URL:

http://localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com/&scope=%24%7BT%28java.lang.Runtime%29.getRuntime%28%29.exec%28%22/Applications/Calculator.app/Contents/MacOS/Calculator%22%29%7D

       它会跳转到一个登录页面,此时随意输入账号密码,点击登录即可弹出计算器:

image.png 

    调试分析

            上面我们已经复现了漏洞,现在进行调试跟踪一下弹计算器的过程做一做笔记,尽量写的详细一点。正向分析过程如下

            从POC的复现触发过程,访问的URI /oauth/authorize 入手,我们尝试找到处理这个uri的地方,你可以通过源码全局搜索找到这个文件的地方

文件地址:

    org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoin

    上面的代码中定义了2个跳转地址:

private String userApprovalPage = "forward:/oauth/confirm_access";

private String errorPage = "forward:/oauth/error";

    这两个地址就是用于完成该函数后,跳转过去的地址。

    3、这个文件里定义了两个ModelAndView,分别处理GET/POST不同类型的认证的;

我们找到了处理的入口函数后,现在可以开始调试了,在处理GET请求的函数逻辑里下断点,然后debug运行起来

        

        运行起来后,用浏览器访问GET请求的认证POC地址,在出现的登录框里任意输入账号密码,点击Login

                可以看到请求进来后,停在了断点处,parameters值就是GET请求里传进来的4个参数;

调试Trick-1:

        为了防止进入该断点逻辑后,再无法回到当前逻辑处,我们可以在该逻辑的下一行逻辑或代码处再下一断点。如下

        现在我们可以放心大胆的跟进去吧,不用担心迷路后再也回不到这里来了。

        到工厂里看看,这里前面几个处理是在读取值初始化,需要重点关注的是extractScopes,它的参数就是前面传进去的4个值,我们进去看看。

    

    为了防止迷路,好习惯要开始养成不要忘了。

    进去后是这样的,如下图所示:

            

    如上图所示,先使用我们传入的scope值,按照空格分割,处理得到scopes 集合,然后实例化了一个clientDetails对象,这里面包含的就是我们的本地oauth的配置信息, 这个信息在代码中配置截图如下:

        


到了这里我们可以认真分析一下这里到代码,如下图:


于是返回的是这个:

image.png

回到上层函数继续跟进,我们可以看到我们传入的参数将拿去实例化一个认证请求类。

image.png

这个认证请求类,目测是不需要多关注了,我们还是简单进去看看。再下个断点进去

image.png


image.png

    

    到此,工厂返回的认证类实例化对象流程已完成,返回的认证类的scope被赋值了我们的恶意语句。回顾一下,恶意的scope在这个厂里生成时,在extractScopes 取值时,完全信任了客户端请求中传入的值。我们继续往下跟,看看后面有没有做处理,以及怎么被触发的。流程回到处理GET请求认证的ModelAndView处,此时的状态如下:

image.png


紧跟着是一些if else的判断,大概浏览下,这些都是能顺利通过判断的,往后浏览发现156行处需要关注,根据取名意思推测这可能是最后一道门,看看对Scope做了什么校验:

			oauth2RequestValidator.validateScope(authorizationRequest, client);

image.png

    为了防止断点跟飙了,在下一个逻辑160行代码再下一断点,然后跟进看看这个validateScope的逻辑

    调试Trick-2:

            想直接跳过很多逻辑直接到下一个断点,不想单步,或一个函数一个函数的调,下一个跟进点打断点后,直接Resume Prog

    跟进去发现这里开始比对传入的scope和本地配置的scope值:image.png


    继续跟进

image.png


    因为我们本地没有配置scope,所以这个里的clientScopes为空的第一个if条件不满足,不会抛出异常Invalid scope。请求带scope不为空,第二个if条件也不满足不会抛出异常Empty scope 

    image.png    

继续往下看,下面的代码如下:

image.png

        我们需要跟进checkForPreApproval了解下做了些什么操作,跟进去后发现逻辑有点多,较复杂。


调试Trick-3:

        当函数逻辑较复杂时,为了防止绕晕后,因意外跳出了该函数而懊悔,我们可以在函数退出的地方先下断点。   

        从单词的字面意思可以理解为检查先前是否被批准,被允许的,我们跟进去看看;image.png

        理解一下上面的代码,创建的ClientDetails类的实例化对象client,代表了本地配置的信息,requestedScopes代表的是 authorizationRequest里初始化后的scope集合,for循环就是在遍历authorizationRequest里设置的scope的集合是否包含在本地配置的范围如果包含就添加进approvedScopes。很明显这里不成立,因为本地我们没有配置scope,这个的approvedScopes最后为空的,如下图:

    image.png

        继续跟下去,发现在本地配置没有找到,开始查询数据库,截图如下:

image.png

image.png

        我的数据库完全是空的,所以这里也不会查到任何信息,最后的approvedScopes也是为空的,所以我们在退出地方下断点,直接跳过去,看看退出时,approved值也应该是false,如下图:

image.png

     回到上层,继续往下看

image.png

    我们跟进去看看,里面是做什么的 :

image.png

跟下去看到,会将当前带有恶意scope的authorizationRequest,forward到/oauth/confirm_access处,如下图:

image.png


    我们继续跟下去,先搜索找到/oauth/confirm_access的处理地方,下断点等候流程跳过去;

找到位置如下图:

org/springframework/security/oauth2/provider/endpoint/WhitelabelApprovalEndpoint.java

image.png

这里已经看到了危险的气息,点击Resume Program直接到这里来,我们可以看到给SpelView解析的模版里面,已经带入了恶意的scope的值,这个模版的页面就是提供给前端手动认证的页面。

image.png


这里就不需要再跟进了,直接Resume Program,即可触发弹出计算器;

image.png

    


        至此整个漏洞的触发流程已经分析完成,回顾一下整个分析过程我们揪出一下产生漏洞是由于输入的scope没有安全检查,当没有配置scope值时,输入的scope经过一层层处理,最终到了SPEL解析引擎


补丁修复:

    commit记录: https://github.com/spring-projects/spring-security-oauth/commit/adb1e6d19c681f394c9513799b81b527b0cb007c

    从补丁的变化来看,去除了SpelView的解析模版方式了,直接拼接的HTML,并用HtmlUtils.htmlEscape编码输出页面的值,。   



参考:

        1、https://pivotal.io/security/cve-2018-1260

        2、https://xz.aliyun.com/t/2330