查看原文
其他

CVE-2018-14847:一个能修复自己的RouterOS漏洞

格物实验室 绿盟科技研究通讯 2021-03-12


2018年10月7日,来自全球知名高科技网络安全公司Tenable的安全研究人员Jacob Baines针对CVE-2018-14847[2]发布了一段新的概念验证(PoC)代码[1],实现了在受漏洞影响的MikroTik路由器上的远程代码执行。我们第一时间对PoC进行了研究,目前我们对漏洞利用的部分改进已经合入了Tenable的Github仓库[7]。本文将对CVE-2018-14847目录穿越漏洞成因进行分析,同时阐述我们的一些发现,如何通过受此漏洞影响的Winbox指令进行任意文件上传,从而实现一些更有趣的利用方式。我们能够利用CVE-2018-14847在RouterOS 6.42中触发后门shell,或在其他漏洞的配合下,通过在LD_LIBRARY_PATH中注入动态链接库的方法,对存在漏洞的可执行文件进行热补丁修复。我们还将在文章中介绍一种“修改”只读文件系统修复漏洞的方法。


一、漏洞分析


1环境简介

MikroTik RouterOS是一个基于Linux开发的网络设备操作系统,兼容x86、ARM、MIPS等多种CPU架构,因此RouterOS也可以安装在PC上将其作为软路由,提供防火墙、VPN、无线网络等多种功能与服务。

RouterOS提供了多种途径对其进行管理与配置,包括SSH、Telnet、Web界面(Webfig)与客户端软件(Winbox)等。本文讨论的漏洞,位于RouterOS与客户端软件Winbox通信过程中所使用的Winbox私有协议中。

RouterOS官网提供了针对多种CPU架构的ISO安装镜像,我们下载6.41.3版本的ISO镜像并安装一个虚拟机。 

在虚拟机中配置好IP等基本设置后,我们对各个配置项进行探索,可以总结出一些基本认知。

1.RouterOS可以通过Web(80端口)、SSH(22端口)、Winbox(8291端口)、Telnet(23端口)进行远程访问,使用相同的用户名与密码进行身份验证。

2.命令行方式访问(SSH、Telnet)会登录到一个厂商定制的shell界面,只能运行厂商实现的管理功能,无法运行Linux命令。



3.通过Webfig途径登录后,与服务器的交互全部通过一个名为master-min-xxxxx.js的脚本进行加密传输。这个加密协议我们下文将称之为JSProxy。

4.Webfig与Winbox途径提供的配置选项几乎完全相同,根据Tenable与Talos等前人的研究,二者在传输层的编码方式上有些许差异,但在应用层的指令编号、指令参数等保持一致。



2协议分析
在上节我们提到,使用GUI管理RouterOS的两个方式Webfig网页端与Winbox客户端,分别采用JSProxy与Winbox协议与RouterOS进行通信,下面我们将分别分析两种协议的通信格式。

在JSProxy通信过程中,浏览器与服务器通信采用变种的MS-CHAP-2方式生成会话密钥,并用于全程的数据加解密,在此我们不作深入讲解,通过JSProxy我们能够略窥RouterOS控制所需要的指令格式与指令映射关系。

对JSProxy的payload进行解密后,我们得到一条条JSON编码的数据。基本格式如下:

JSON数据中的每个键值对包含了三种信息:字段类型、字段名、字段值。JSON键的第一个字母(上图蓝色部分)代表字段类型,包括字符串、整型、布尔值、数组等。JSON键的剩下6个十六进制位(上图红色部分)代表字段名,通常是一个编号,表示数据值的含义,我们下文称之为key ID。JSON值(上图黄色部分)就是这个字段的数据值。

到这里我们可以了解到,RouterOS管理页面的通信方式是通过键值对的请求与回复实现的,键值对的编码方式在JSProxy与Winbox中不尽相同,但数据含义是相同的。我们对 master-min-xxxxx.js进行分析,一个好消息是,我们可以在其中找到所有key ID。

而Winbox协议的二进制编码格式,我们可以从Winbox客户端中分析得到。我们寻找到了一个关键函数,功能是将Winbox消息转换成字符串进行日志输出,四舍五入等于开源了。

Winbox协议二进制格式的大致格式分为6字节的消息头,与依次紧密排列的键值对,大于255字节的消息会插入两字节的长度数据作为消息分片。

其中键值都是以小端序传输,同样采取类似“数据类型-键-值”的格式。

借助Cisco Talos实验室开源的Winbox协议Wireshark解析插件[9]。我们能非常直观的看到数据中各个字段与原始数据的对应关系。

当然这个插件还有一些不足之处,无法解析所有类型的数据包,但对于我们,能够对数据包进行过滤和简单查看一下对应关系就足够了。

3关键漏洞:目录穿越、任意文件读写(CVE-2018-14847)
CVE-2018-14847漏洞发生在漏洞上传的位置。我们如何知道文件上传指令发给谁,文件由谁接收,保存到哪里呢?我们需要先定位哪个程序在RouterOS上负责接收用户上传的文件。

从Winbox消息中,我们注意到不同指令会发送给不同的目的组件,从JSProxy中我们能够得知这个目的地位于SYS_TO字段对应的字段中。这个字段是两个int32组成的数组,第一个数字对应了系统中的一个程序,第二个数字对应了这个程序的第几个功能。Tenable对系统中保存了这个对应关系的数据文件/nova/etc/loader/system.x3进行了分析,并制作了一个名为parse_x3的小工具,我们可以在Tenable的Github仓库中的parse_x3目录找到。

根据这个对应关系,结合抓包我们能够得知,文件上传请求的SYS_TO值为{0x02,0x02},对应的二进制文件位于/nova/bin/mproxy。

我们从RouterOS的ISO文件中提取出对应的二进制文件。文件位于mikrotik-6.41.3.iso\system-6.41.3.npk\nova\bin\mproxy,直接用7-zip打开iso和里面的npk,拖出来即可(很多地方其实用不着binwalk)。

在对mproxy进行分析的过程中,Tenable通过功能(handler)实现对应的基类nv::Handler找到了每个程序里面的所有功能的所有编号,并编写了一个Binary Ninja脚本寻找程序中的所有handler,从而快速定位功能对应的函数,这个小工具位于Github仓库中的find_handlers目录中,有兴趣的同学可以尝试一下。

我们尝试找一个简单点的方法,从另外一个角度定位对应的函数。我们在Winbox客户端中正常上传一个文件,并寻找上传的文件所在的路径。当然,假如现在是黑盒测试的话,我们可以用其他的方法获得文件系统的访问权,例如利用其他漏洞进行提权、直接修改文件系统植入shell等等。我注意到文件系统根目录下有很多的符号链接,其中有一些链接到可写目录中,所以我在这里用find -follow跟随符号链接确保我们能够定位到文件的所有可能的路径。

通过这种方法获得的路径,与mproxy程序中出现的字符串进行交叉比对,我们可以得到结论:mproxy程序中,某个负责文件上传的函数能够上传文件至/var/pckg目录中。

再通过这个字符串的交叉引用,我们就能够定位到我们所需的关键函数,我们命名为ked_handler。

对这个函数进行分析,我们可以得出ked_handler函数大致有7个功能,以传入的第5个参数进行选择,有以下7个取值(以IDA伪代码中出现顺序排序):

功能1:在/var/pckg目录下创建文件,返回一个用于写入数据的session

功能3:打开/var/pckg目录下的一个文件,返回一个用于读取数据的session

功能7:打开/home/web/webcfg目录下的一个文件,返回一个用于读取数据的session

功能2:向一个打开文件的session写入数据

功能4:从一个打开文件的session读取数据

功能5:中止一个session并删除先前写入的文件

功能6:在/var/pckg目录下新建一个目录

通过对抓包数据进行分析,我们发现官方客户端在读取并下载文件时调用的是3号功能,而Tenable的poc调用的是7号功能。

与这两个功能相关的部分逻辑如下。

其中,tokenize函数对传入的字符串按 / 字符进行切分,返回一个字符串数组。ked_check_path函数采用类似状态机的方式有限制的允许“..”字符串,不允许穿越到父目录中。但函数中没有对“.”(即,当前目录)进行验证,所以我们可以构造类似“/./../”的payload,让函数误以为我们进入1级子目录后返回了当前目录,但实际我们进入了父目录,成功实现目录穿越。ked_check_path这个函数可以说是搬起石头砸了自己的脚。

通过伪代码我们能看到这三个功能都没有对输入参数进行正确过滤,应该存在相同的目录穿越漏洞。这两个命令理论上都能利用来进行任意写入,实际情况呢?

关键点在权限上,对ked_handler进行调用回溯,我们能够定位到mproxy的main函数中,向event loop注册handler的代码片段。在添加handler前,程序调用了set_policy对7个命令进行了某种设置,据Tenable的深入研究,这几条函数调用与handler中相关功能的调用权限相关。第三个参数为0的时候,代表调用此功能无需登录。由以上代码可知,功能命令4、5、7的调用无须登录。

到这里,我们已经实现了对系统根目录的任意文件读写。

4关键漏洞:开发者后门(Tenable exp: bytheway)
下一个问题,有了权限,我们能做什么呢?当然是Getshell了!我们前面提到,即使我们有管理员账号密码,ssh登录进去依然是一个功能受限的shell,不能执行Linux指令。Tenable在报告中提出了一种利用厂商开发者后门进行提权的方法。这个后门与上文提到的目录穿越漏洞,在RouterOS 6.42.1与6.40.8版本一并得到修复。

这个漏洞存在于ssh和telnet登录时运行的/nova/bin/login中。命令行登陆时,程序先正常向用户请求输入用户名和密码,然后根据两个判断条件:用户名是否等于devel、hasOptionPackage返回是否为非0值,如果都符合则进入下一个代码块。

其中,hasOptionPackage函数位于/lib/libumsg.so中,它的取值依据为/pckg/option文件是否存在。

确认用户名与OptionPackage后,程序将用户名重置为admin,并设置一个标志位,指示后门的激活状态。

处理一部分其他逻辑之后,程序再次检查这个标志位,如果后门激活,则将shell运行的实际命令设置为bash。根据PATH环境变量的设置,最后命中并执行文件系统中名为/bin/bash(实际是一个busybox)的交互式命令行。

这个后门的触发逻辑在RouterOS的不同版本中略有不同。根据Tenable的研究,在RouterOS 6.40.9以下版本中,依据的文件是/flash/nova/etc/devel-login,在6.41.1以下版本中,判断的是/pckg/option,在6.42以上版本中,对/pckg/option文件类型做了额外的验证,但依然能够通过修改文件系统的方式进行触发。

5利用方式总结
根据上面的两个漏洞的细节,我们已经可以总结出整个漏洞利用的过程:

1.构造payload,调用功能7,打开/flash/rw/store/user.dat文件,获得session id。

2.调用功能4,用这个session id读取文件内容。

3.用其他工具解密管理员账户的明文密码。

4.调用{0x0d,0x04}号handler进行Winbox管理员登录(模拟Winbox客户端登录,协议细节不再赘述),获得管理员权限session id。

5.调用功能1,用管理员session在/pckg/option创建空文件。

6.用管理员明文密码登录ssh/telnet,获得root shell。


整个的利用过程已经被Tenable总结出了一个漏洞利用程序bytheway,在他们的Github中可以找到。

二、漏洞复现
我们在这里使用的是基于Tenable的bytheway进行修改后的exploit。

交互过程如下:

根据RouterOS版本不同,触发后门需要的文件也不同,因此exploit里面将两个文件都创建了,尽量兼容更多的版本。

三、One More Thing
漏洞复现了就结束了?NO。我们在公网上部署了多台测试环境对RouterOS漏洞的利用行为进行捕获,从攻和守两个角度都有一些新的发现。我们接下来将介绍一个帮我们“修复”漏洞的好心攻击者与我们对其修复方法的复现,还有我们发现的另一种漏洞利用方法,同时介绍如何利用这种方法反过来修复这个漏洞。
1“删除”只读rootfs系统文件
挖矿、蠕虫、僵尸网络等攻击行为不再多说,都是常规操作。在其中一个设备上,我们发现了一个好心的攻击者帮助我们“修复”了后门shell。具体表现是,“修复”后的设备能够通过ssh登录管理界面,也能通过exp触发后门,但却无法登录devel用户,输入正确的密码后却被断开连接。估计是某个病毒的作者想要防止其他攻击者利用这个漏洞,所以好心帮我们“修复”了。可以说是滑天下之大稽。

通过对设备文件系统的分析和虚拟机上的复现,我们猜测这个设备上的/bin/bash文件被“删掉”了。但问题是,PATH变量是硬编码的不可能修改,而/bin目录所在的根文件系统是只读挂载的squashfs,所以删除系统文件是怎么实现的呢?

前面我提到了特殊意义的“删掉”,是因为我们重新分析线上环境的文件系统时,发现/bin/bash并没有被删,所以下面的这种漏洞“修复”方式仅是我们能够复现的一种猜想。

首先我们想知道,在系统目录中有哪些是可写入的。

我们选择/ram,并用随便一种方式上传一个完整版busybox进去(因为RouterOS自带的mount进行bind mount时会报错)。随后,在可写目录中新建bin文件夹并将/bin目录中的内容复制进去,并用mount将新目录挂载到/bin目录之上,实现“覆盖”的效果。由下图可见,我们断开连接后再次登录,就会在登录成功后马上断开,提示消息为Connection Closed而不是漏洞修复后提示的Permission denied。

用这种方式实现的修改,并没有真实写入文件系统,在重启之后,后门依然可以访问。病毒作者可以说是用心良苦哇。

这个思路同样可以用于一些敏感的生产环境系统,很多嵌入式设备都采用squashfs或者其他用于Flash芯片的只读文件系统,如果对可用性有较高的要求,不能接受重启、系统升级带来的服务中断的话,可以用类似的目录挂载方式对系统进行热修复。

2CVE-2018-14847的其他用途
在对CVE-2018-14847漏洞进行分析的时候,我产生了一个疑问。既然功能1、3、7共享这个目录穿越漏洞,那理论上我们可以在未授权的情况下向系统任意目录写入任意文件。也许我们不需要开发者后门,也可以实现远程代码执行。

我尝试利用Tenable的协议库winbox_message.hpp编写一些poc,利用ked_handler中功能1提供的session写入文件,在可写的路径上传busybox。但服务器对上传的功能2指令没有任何响应。通过对Winbox客户端上传功能的分析,我发现Tenable的协议库中对长度大于256字节的message处理有一些小bug。

下图以一个文件上传数据包内容为例,以全0数据方便我们观察原始数据格式。方框部分就是Tenable协议库遗漏的部分,我们在winbox_session.cpp中,在每个消息分片之前加入分片长度值与截断标记,修复后的数据包就和Winbox客户端发送的协议格式完全相同了。

进行修复以后,调用mproxy的功能2能够正常上传文件到指定位置,在修复之前,因为其他header的存在,最多只能上传209字节的文件,更大的文件请求就会因为消息分片格式的问题被mproxy丢弃。这个patch我们也提交到了Github上。现在,我们打开了另一扇大门。

>>>>

不依赖开发者后门的漏洞利用方法

最开始我的设想,直接利用上传功能覆盖一个系统可执行文件,并触发对应的功能。但后来发现有两个难点。一是从未授权用户的角度来看,能够调用的程序就那么几个,/nova/bin/login与mproxy、telnet和ssh服务器等,这些程序所在的挂载点都是只读的,root用户也无法覆盖。二是通过功能2创建的文件默认权限都是644,即可读可写不可执行,即使传上去了也会因为权限问题无法启动。

不过对RouterOS有了更多的了解后,我有了一个设想。查看shell登录以后的环境变量:

我们发现,LD_LIBRARY_PATH中包含了多个目录,/lib在根文件系统下,是只读的。/pckg是一个指向/ram/pckg的符号链接,/pckg/security/lib与/pckg/user-manager/lib只在安装了对应的可选包才会出现,也都是只读的。/rw是一个指向/flash/rw的符号链接,这个目录是可写的。虽然/rw/lib目录在默认配置下不存在,但我们可以通过上文的目录穿越漏洞调用ked_handler的功能6进行创建,从而使这个漏洞的利用成为可能。

LD_LIBRARY_PATH是一个危险的东西。利用这个环境变量,我们可以实现在任何程序运行前对程序的行为进行修改,甚至在程序运行时实现进程注入。子进程可以继承父进程的环境变量,而系统中绝大多数应用都是loader进程的子进程(下图PID 276)。

因此,将payload封装为so库,放置在LD_LIBRARY_PATH中的可写路径/rw/lib下,即可实现对接下来任意启动的进程进行注入并实现命令执行——比如我们在命令行登录时,就会启动一个/nova/bin/login。

>>>>

在RouterOS 6.42+植入后门shell

用这个思路,我们还可以在无法触发后门的6.42版本中,使用老版本libumsg.so重新引入这个后门,对RouterOS“越狱”会有帮助。前面我们提到,

这个后门的触发逻辑在RouterOS的不同版本中略有不同。根据Tenable的研究,在RouterOS 6.40.9以下版本中,依据的文件是/flash/nova/etc/devel-login,在6.41.1以下版本中,判断的是/pckg/option,在6.42以上版本中,对/pckg/option文件类型做了额外的验证,但依然能够通过修改文件系统的方式进行触发。
RouterOS 6.42+中,远程触发开发者后门几乎是不可能的,攻击者更不可能跑到别人家里去拆掉Flash芯片往里塞后门。意味着即使攻击者拿下了用户名密码,但他也仅仅能访问GUI中有限的控制选项,不能植入payload利用设备获得更多的经济利益。

我们前面分析过,与后门相关的控制逻辑,有一部分位于/lib/libumsg.so中,准确来讲是nv::hasOptionPackage函数。既然我们已经能够进行任意文件上传,同时我们能够控制LD_LIBRARY_PATH,那么我们简单的将RouterOS 6.41.x中的libumsg.so复制出来,再在6.42.x的系统中利用目录穿越漏洞调用ked_handler的功能6创建/rw/lib目录并将libumsg.so上传上去,因为LD_LIBRARY_PATH的函数加载优先级高于PE文件中定义的加载路径[8],我们用这种方式即可实现后门触发,在6.42.x系统中启用ssh shell。

因为不想再写代码了,为了从另一个角度验证这个思路的可行性,我们在RouterOS 6.41.x上设计了下面的实验。

>>>>

在RouterOS 6.41.x上利用漏洞“修复”漏洞

我们现在有一个后门,我们还控制了LD_LIBRARY_PATH下可写的目录/rw/lib,同时能够创建这个目录并向其中写入文件。假如我现在的设备就是一个骨干路由,需要对漏洞进行紧急修复,我们现在来尝试一下,在不更新、不重启系统的情况下通过LD_LIBRARY_PATH预加载动态链接库关掉这个后门。

首先,我们确认后门可以正常工作,并创建/rw/lib目录。当然,我们也可以用漏洞来创建这个目录,我只是懒得写exp了(笑)

接下来,我们从RouterOS 6.42的ISO中提取出libumsg.so,并利用CVE-2018-14847(或者用你能想到的其他方法)上传到指定目录中。我在这里同时上传了一个busybox。

上传成功后,kill掉所有名为login和sshd的进程。

这样一来,当前所有通过控制台、Telnet与ssh登录的终端都会被关闭,再次连接就会启动新进程,同时加载我们通过LD_LIBRARY_PATH植入的新版本libumsg.so,后门shell失效。

还记得我们在前面对开发者后门的分析吗?如果libumsg.so中的后门函数hasOptionPackage返回true后,登录session的用户名会被重置为admin,从而控制台中显示的登录成功、失败的日志中的用户名都会因此被修改。但后门修复之后,这一段程序逻辑不复存在,因此对后门的登录尝试也会在系统日志中显示出来。

再结合我们前面提到的,用mount挂载魔改只读文件系统的方式,我们还可以用新版本覆盖根文件系统的/nova/bin/mproxy,将目录穿越漏洞一并修复掉。

我们再开个脑洞,整个漏洞修复过程分为三步:创建目录、上传修补程序、重启服务,这个过程完全可以自动化,一个脚本对全网存在漏洞的设备进行扫描修复。真正做出来肯定也会非常有趣吧。

四、后记

攻与防的较量,就是正常程序猿与脑洞程序猿的较量。不知攻,焉知防?知己知彼,方能百战不殆。

参考资料:

[1].Bug Hunting in RouterOS 视频:https://youtu.be/ItclhUF6MnA 讲稿:https://github.com/tenable/routeros/raw/master/slides/bug_hunting_in_routeros_derbycon_2018.pdf

[2].MikroTik blog - CVE-2018-14847 winbox vulnerability https://blog.mikrotik.com/security/winbox-vulnerability.html

[3].MikroTik RouterOS Authenticated Directory Traversal https://zh-cn.tenable.com/security/research/tra-2019-16

[4].GitHub - tenable/routeros: RouterOS Security Research Tooling and Proof of Concepts https://github.com/tenable/routeros

[5].深入分析MikroTik RouterOS CVE-2018-14847 & Get bash shell https://www.anquanke.com/post/id/162457

[6].基于 CVE-2018-14847 的 Mikrotik RouterOS 安全事件分析 http://ith4cker.com/content/uploadfile/201811/aed91542039274.pdf

[7].Fix message length issue with packet bigger than 256 bytes https://github.com/tenable/routeros/pull/7

[8].What is the order that Linux's dynamic linker searches paths in? - Unix & Linux Stack Exchange https://unix.stackexchange.com/a/367682

[9].GitHub - Cisco-Talos/Winbox_Protocol_Dissector https://github.com/Cisco-Talos/Winbox_Protocol_Dissector

内容编辑:格物实验室 张浩然  责任编辑:肖晴

期回顾

本公众号原创文章仅代表作者观点,不代表绿盟科技立场。所有原创内容版权均属绿盟科技研究通讯。未经授权,严禁任何媒体以及微信公众号复制、转载、摘编或以其他方式使用,转载须注明来自绿盟科技研究通讯并附上本文链接。

关于我们


绿盟科技研究通讯由绿盟科技创新中心负责运营,绿盟科技创新中心是绿盟科技的前沿技术研究部门。包括云安全实验室、安全大数据分析实验室和物联网安全实验室。团队成员由来自清华、北大、哈工大、中科院、北邮等多所重点院校的博士和硕士组成。

绿盟科技创新中心作为“中关村科技园区海淀园博士后工作站分站”的重要培养单位之一,与清华大学进行博士后联合培养,科研成果已涵盖各类国家课题项目、国家专利、国家标准、高水平学术论文、出版专业书籍等。

我们持续探索信息安全领域的前沿学术方向,从实践出发,结合公司资源和先进技术,实现概念级的原型系统,进而交付产品线孵化产品并创造巨大的经济价值。

长按上方二维码,即可关注我们

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存