MikroTik路由器在4月份被发现存在目录遍历漏洞CVE-2018-14847,危险程度为中。Tenable Research的专家于10月7日在DerbyCon上发表了题为“Bug Hunting inRouterOS”的演讲,介绍了这项新技术,就是利用该漏洞。目前结合该漏洞的黑客工具已放出,运行RouterOS的MikroTik设备成为恶意代码的目标。
本文从MikroTik RouterOS与客户端的通信协议入手,辅以逆向分析,深入解读了CVE-2018-14847以及内置开发者后门的漏洞原理,最后进行完整的组合拳进阶利用,达到Get bash shell的目的。整体流程图如下:
0×02. 通信协议分析MikroTik RouterOS是一款基于Linux核心开发,兼容Arm,mips,x86 PC等多种架构网络设备操作系统。通过安装该操作系统可以将标准的PC电脑变成专业路由器,也就是平时常说的软路由。同时,RouterOS提供了丰富的管理配置接口:1)winbox:GUI软件管理; 2)cli:命令配置;3)webfig :网页图形化管理。而Winbox for MikroTikRouterOS是一个用于管理MikroTik RouterOS系统的 GUI客户端应用程序。
RouterOS官方提供了相应的ISO系统镜像包,所以可以像安装正常操作系统一样安装RouterOS,直接在vm中安装一个虚拟机。
通过CliTelnet的方式对RouterOS进行配置,但是要知道以下两点:
•这不是一个osbash shell,不能访问到底层的linux的操作系统
•只能利用内置的一些命令集对RouterOS进行配置和管理
从下图的nmap扫描结果可以看可以看到RouterOS专门提供了8291端口跟winbox通信。
漏洞原理是怎么的呢?要理解漏洞的原理,首先得需要理解Routeros与winbox的通信过程,本文先从webfig和Routeros的通信过程入手。 漏洞原理是怎么的呢?要理解漏洞的原理,首先得需要理解Routeros与winbox的通信过程,本文先从webfig和Routeros的通信过程入手。 2.1 通信协议识别访问webfig,首先请求的一个js文件,下载来美化后看看代码:
代码很多,17000行左右。大致浏览了一下,可以看到webfig与RouterOS之间所有的通信消息都是由这个js文件产生和处理。 初始化相关代码: 代码很多,17000行左右。大致浏览了一下,可以看到webfig与RouterOS之间所有的通信消息都是由这个js文件产生和处理。 初始化相关代码:登录、认证相关功能,相关的POST数据包都发送到RouterOS的jsproxy处理, 这里的jsproxy就相当于jsp中servlet一样。
但当查看这些数据包的时候,发现POST是加密的,同样返回的数据也是。 但当查看这些数据包的时候,发现POST是加密的,同样返回的数据也是。 而其中的加密算法在js文件中可以找到: 而其中的加密算法在js文件中可以找到: #p#分页标题#e#其中的产生56位key的算法采用的是RFC 3079 MS-CHAP-2 。 其中的产生56位key的算法采用的是RFC 3079 MS-CHAP-2 。 这是一个很老的PPP协议(存在离线碰撞破解漏洞) 这是一个很老的PPP协议(存在离线碰撞破解漏洞) 从js代码和数据包可以看到采用的身份认证方式是:提问/应答(Challenge/Response)方式。 从js代码和数据包可以看到采用的身份认证方式是:提问/应答(Challenge/Response)方式。 2.2 认证过程梳理至此,笔者来完整地梳理一下整个认证的流程:
客户端首先发送一个空的POST请求给服务器。 客户端首先发送一个空的POST请求给服务器。 服务器收到后发出Challenge(提问): 服务器收到后发出Challenge(提问): 客户端利用输入的账号密码采用MS-CHAP-2算法生成通信key,再利用这个key使用RC4加密生成Reponse(应答),发给服务器。 客户端利用输入的账号密码采用MS-CHAP-2算法生成通信key,再利用这个key使用RC4加密生成Reponse(应答),发给服务器。服务器将客户端的应答利用自己计算的出的key解密,能解出来则认证成功。接下的通信数据包就是全是用这个认证成功的key加密的Content-type为msg的POST数据包。
2.3 数据包解密理清楚认证过程,就可以来考虑下数据包怎么解密?
思路总结成两步:
1. 离线暴力从登录数据包中碰撞出账号密码。
2. 利用碰撞出的账号密码生成key解密其余通信数据包。
2.4 Json数据含义解析至此我们已经拿到解密之后的数据包了,可以看到是json格式的。但是这些数据的含义还是一脸懵逼的。虽然js文件存在一些映射关系,但是要理解这些数据的含义还得加上逆向+仔细看代码+可能存在的文档:
#p#分页标题#e#这里笔者直接就把Tenable的分析报告当文档,可以到每一个key-value键值对都是由如下几部分组成: 这里笔者直接就把Tenable的分析报告当文档,可以到每一个key-value键值对都是由如下几部分组成: 而其中type可以取如下值: 而其中type可以取如下值: 而其中一些常见变量名的含义如下: 而其中一些常见变量名的含义如下: OK,结合上面的文档继续分析,发现Uff0001 system_to这个变量的值是一个数组[13, 7],当RouterOS接到这条消息后jsproxy会做一次映射,最终找到/nova/bin/下的对应可执行程序来处理这条消息。 OK,结合上面的文档继续分析,发现Uff0001 system_to这个变量的值是一个数组[13, 7],当RouterOS接到这条消息后jsproxy会做一次映射,最终找到/nova/bin/下的对应可执行程序来处理这条消息。 具体怎么映射的呢? 在RouterOS中有一个叫/nova/etc/loader/system.x3的二进制文件,我们直接cat查看下: 具体怎么映射的呢? 在RouterOS中有一个叫/nova/etc/loader/system.x3的二进制文件,我们直接cat查看下: 虽然找不到具体的映射关系,但是可以知道映射关系就存储在这个文件中。Tenable通过分析对应的文件格式和内容,得到了对应的映射方式: 虽然找不到具体的映射关系,但是可以知道映射关系就存储在这个文件中。Tenable通过分析对应的文件格式和内容,得到了对应的映射方式: 并开发了一个提取数字到可执行文件对应关系的程序,笔者利用该程序得到对应的映射结果: 并开发了一个提取数字到可执行文件对应关系的程序,笔者利用该程序得到对应的映射结果:也就是说数组[13,7]中的13最终调用的是/nova/bin/user,那么剩下的7代表什么?继续往下看:
因为没有用livecd的方式将RouterOS的文件系统挂载出来,所以只有利用binwalk把官网的iso镜像包中的文件系统提取出来:
用ida打开nova/bin/user,通过逆向分析,发现calls to nv::Looper::addHandler是调用handler的关键代码,总共有八处这样的调用: 用ida打开nova/bin/user,通过逆向分析,发现calls to nv::Looper::addHandler是调用handler的关键代码,总共有八处这样的调用: #p#分页标题#e#并且其传入的参数可为3,2,1,4,5,6,7,8,数组第二项的值正好在其中:
部分汇编代码:
也就是说[13,7]中两项分别对应:[/nove/bin/下的一个二进制程序,二进制程序中对应的handler]。 继续跟进,发现7对应的handler中有如下具体的方法: 也就是说[13,7]中两项分别对应:[/nove/bin/下的一个二进制程序,二进制程序中对应的handler]。 继续跟进,发现7对应的handler中有如下具体的方法: 至此,{Uff0001:[13,7],uff0007:16646157,s1:’admin’} 这条json消息中第一个键值对的值就搞清楚了,接着看第二个uff0007:16646157。通过前面的变量名对应关系,可以知道uff0007代表的是命令,也就是对应上面handler中具体的分支。 至此,{Uff0001:[13,7],uff0007:16646157,s1:’admin’} 这条json消息中第一个键值对的值就搞清楚了,接着看第二个uff0007:16646157。通过前面的变量名对应关系,可以知道uff0007代表的是命令,也就是对应上面handler中具体的分支。 这里Tenable也给出了一份具体的映射关系: 这里Tenable也给出了一份具体的映射关系: 16646157对应着Get命令,跟进看看Get command: 16646157对应着Get命令,跟进看看Get command: 其中进了很多函数,这里就不一一细致分析了,但可以看到此函数实现的最主要功能点就是:获取json消息中传入的用户名字符串admin的对应用户信息。 其中进了很多函数,这里就不一一细致分析了,但可以看到此函数实现的最主要功能点就是:获取json消息中传入的用户名字符串admin的对应用户信息。 0×03. 漏洞分析 3.1 初步利用分析理清楚整个通信过程以及json消息的含义之后,正式开始漏洞分析。 漏洞发生在winbox和RouterOS的通信中,经过分析发现其数据包的本质还是与webfig与RouterOS之间的通信一样,采用的是json消息的形式,对应的参数含义都是一致的,甚至winbox的数据包没有加密,只是将json按照一定规则转化成了二进制形式利用TCP进行传输。
所以同样可以将winbox的二进制数据包转化成JSON形式:
转化出来的第一条json消息就对应着exp中list a 中的二进制payload: 转化出来的第一条json消息就对应着exp中list a 中的二进制payload: #p#分页标题#e#ok,这下就是熟悉的json格式了,重点关注以下几个变量的值: ok,这下就是熟悉的json格式了,重点关注以下几个变量的值: 找到Uff0001:[2,2]对应的二进制程序和对应的handler: 找到Uff0001:[2,2]对应的二进制程序和对应的handler:继续跟进uff0007:7, 由前面可知这键值对的含义是command变量值为7。ok,继续找handler 2中的怎么处理7命令的,首先先找到handler 2,其中箭头所指函数就是处理command 7的函数:
跟进这个函数,是一个switch分支,找到case 7,可以看到当变量值为7时打开了一个/home/web/webfig下的文件,并将这个文件的大小和一个session id作为返回值(由于代码太长了笔者只截取了关键部分): 跟进这个函数,是一个switch分支,找到case 7,可以看到当变量值为7时打开了一个/home/web/webfig下的文件,并将这个文件的大小和一个session id作为返回值(由于代码太长了笔者只截取了关键部分): 对应着RouterOS回应的第一条json消息: 对应着RouterOS回应的第一条json消息: 就按这样的思路继续逆向分析,发现commoad可以取以下七个值,分别实现了不同的功能(由于篇幅原因就只截取了部分功能的关键代码): 就按这样的思路继续逆向分析,发现commoad可以取以下七个值,分别实现了不同的功能(由于篇幅原因就只截取了部分功能的关键代码):1. Opens a file in /var/pckg/ for writing.
2. Writes to the open file. 2. Writes to the open file.3. Opens a file in /var/pckg/ for reading.
4. Reads the open file.
5. Cancel transfer (and deletes the file). 5. Cancel transfer (and deletes the file). 6. Creates a directory in /var/pckg/. 6. Creates a directory in /var/pckg/.7. Opens a file in /home/web/webfig/ for reading.
#p#分页标题#e#这样exp中发送的第二条二进制payload就好理解了,含义就是:带上RouterOS返回的SessionID和文件大小(PS: 至于为啥要带上这两个,上面的代码已经很清楚,RouterOS对这两个值做了验证),发给mproxyhandler 2中的command4:
此外,在handler 2调用前可以看到对这七个command做了策略限制的: 此外,在handler 2调用前可以看到对这七个command做了策略限制的: 同样,RouterOS提供了一个叫Get Policies的命令,可以用来获取system_to数组对应command的权限策略,也就是能获取某个command的执行是否需要认证,利Tenable的自动化工具获取看下: 同样,RouterOS提供了一个叫Get Policies的命令,可以用来获取system_to数组对应command的权限策略,也就是能获取某个command的执行是否需要认证,利Tenable的自动化工具获取看下:结果和上面的汇编代码一致,可以看到command 4 和 7的policy值为0,是不需要认证的,也就是说网上一些关于该漏洞是基于权限绕过的描述是不完全正确的,这里不存在权限认证bypass,上面已经提到了,第二条利用代码中替换Session id是为了通过Command 5中的IF条件,让exp继续执行下去。
至此漏洞原理就理清楚了,整个漏洞总结来其实就是一个未授权的任意文件读取漏洞。来看看具体的利用过程:
1. 构造数据包去读取/flash/rw/store/user.dat文件,该文件中存储RouterOS的用户名密码。
2. 由于存储的是:password xormd5(user+”283i4jfkai3389″), 所以可逆,得到密码明文。
3. 登录RouterOS,控制路由器。
以上就是CVE-2018-14847的漏洞分析和利用。这时虽然进入到了RouterOS,但还是不能访问底层linux那部分,所以就有了更进阶的利用。
3.2 进阶利用分析在Tenable的报告中,发现在6.42 stable版本以前很多版本存在开发者后门。笔者以6.41 statle版本为例分析。
定位到后门关键点:
可以看到除了管理员admin用户,还有一个内置用户devel。要满足if条件,不仅需要用户为devel以及后面函数的返回值为真。跟进nv::hasOptionPackage()函数,发现其是/lib/libumsg.so动态库中的函数: 可以看到除了管理员admin用户,还有一个内置用户devel。要满足if条件,不仅需要用户为devel以及后面函数的返回值为真。跟进nv::hasOptionPackage()函数,发现其是/lib/libumsg.so动态库中的函数: 继续跟进nv::hasPackage()函数: 继续跟进nv::hasPackage()函数:此函数只对/pckg/option这个文件做了存在与否的简单判断,存在的话,返回为真,加上用户为devel就满足了关键点处的if条件,byte_8053D15变量的值就被置为1。
当byte_8053D15变量值为1,且/pckg/option文件存在时,RouterOS直接调用了/bin/bash,此时获得的就是一个 Root shell,到达了linux系统层。
#p#分页标题#e#至此,整个后门原理梳理清楚了,但这些后门文件默认是不存在,要想利用需要先写入,此时不妨回头看看mproxy handler 2的几个command。command 1执行了创建文件的操作,似乎刚好切合需求,但是需要认证,也就是说要想写入后门文件,必须得先登录成功。笔者思考这个设定也确实合理,要进开发者模式,首先得获得认证,一般情况下账号密码也只有开发者或者用户自己知道,只是恰好有了上面的未授权的任意文件读取,来了一个组合拳。
0×04. 漏洞整体利用这样整体思路就很明确了:
1. 先通过command4,7任意文件读取获得用户名密码。
2. 登录后再通过command1写入后门文件。
3. 利用内置用户devel+admin用户密码登录获取Rootshell。
整个利用过程如果用python通过socket直接发二进制数据跟winbox通信,是可行,但是数据包的构造很复杂麻烦。所以这里可以直接利用RouterOS官方提供的 c++的winboxapi库大大简化了代码(具体exp来自Tenable):
利用结果:
1、正常登录,返回登录失败:
2、执行BTW后,再次登录,返回登录成功。 2、执行BTW后,再次登录,返回登录成功。 0×05. 参考链接https://github.com/tenable/routeros/