<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    CVE-2022-45315 RouterOS SNMP 越界讀漏洞研究

    一顆小胡椒2023-08-11 09:19:27

    這個漏洞可能導致認證后 RCE。

    [!error] Hyper-V 的影響
    在一些開啟了 Hyper-V 的電腦上,RouterOS 可能無法在 VMWare Workstation 中模擬運行或啟動非常緩慢,如果遇到無法運行的情況,請酌情考慮關閉 Hyper-V,如果能成功運行但是啟動緩慢,可以及時拍攝快照。

    漏洞描述

    Mikrotik RouterOs before stable v7.6 was discovered to contain an out-of-bounds read in the snmp process. This vulnerability allows attackers to execute arbitrary code via a crafted packet.

    目標二進制為 /nova/bin/snmp

    前置知識

    RouterOS 有一些交互方式,這里我們可能會用到 JS 交互和 Winbox 交互。

    JS 交互本質上就是通過 http 協議走 80 端口,RouterOS 中的 www 進程負責解析用戶請求并發給指定的進程 。

    Winbox 交互是通過 8291 端口進行的,RouterOS 中的 mproxy 進程負責接收請求并解析,發送給指定的進程。

    這些協議的加密細節在此不做討論,感興趣的讀者可以自行研究。

    Nova Message

    Nova Message 是 RouterOS 中的自定義消息格式,它被用來進程之間的通信。Nova Message 的格式是 Type Key-Value Pairs,這里舉個栗子:

    {bff0005:1, uff0006:0x1, uff0007:0xfe000d, s1:'admin', Uff0001:[13,7]}
    

    Type

    在上面的例子中,每一個 Key 的首字母是類型(Type),剩下的部分才是真正的 Key。Nova Message 中有若干種類型(Type):

    b: bool
    u: 32bit integer
    q: 64bit integer
    s: string
    r: raw
    a: IPv6
    m: message
    B: bool array
    U: 32bit integer array
    Q: 64bit integer array
    S: string array
    R: raw array
    A: IPv6 array
    M: message array
    

    Key

    根據上面的例子,Key 中的低 24 位才是真正的 Key。而這低 24 位也有自己的說法:

    key = 0xGGVVVV, G=group, V=value
    

    其中 GG 代表的是命令的組,在 RouterOS 中,GG 可能的值包括:

    0xFF - SYS
    0xFE - STD
    0xFD - LOCAL
    0x01 - NET
    0x02 - MODULER
    0x03 - SERMGR
    0x04 - NOTIFY
    0x05 - RADV
    0x06 - SYSTEM
    0x07 - PING
    0x08 - UNDO
    0x09 - LOG
    0x0A - MEPTY
    0x0B - PPPMAN
    0x0C - RADIUS
    0x0D - HOTPLUG
    0x0E - BRIDGE
    0x0F - DISKD
    0x10 - DUDE
    0x11 - CONSOLE
    0x12 - CERM
    0x2C - ROUTE
    

    以 Group 為 0xFF(SYS) 為例,不同的值也有不同的含義:

    SYS_TO: 0xFF0001
    SYS_FROM: 0xFF0002
    SYS_TYPE: 0xFF0003
    SYS_STATUS: 0xFF0004
    SYS_REPLYEXP: 0xFF0005
    SYS_REQID: 0xFF0006
    SYS_CMD: 0xFF0007
    SYS_ERRNO: 0xFF0008
    SYS_ERRSTR: 0xFF0009
    SYS_USER: 0xFF000A
    SYS_POLICY: 0xFF000B;用于表示當前用戶的權限
    SYS_CTRL: 0xFF000D
    SYS_CTRL_ARG: 0xFF000F
    SYS_USER_ID: 0xFF0010
    SYS_NOTIFYCMD: 0xFF0011
    SYS_ORIGINATOR: 0xFF0012
    SYS_RADDR6: 0xFF0013
    SYS_DREASON: 0xFF0016
    

    對于 SYS_CMD,我們可以設置不同的值完成不同的功能:

    0xfe0000 - NOP
    0xfe0001 - getPolicies
    0xfe0002 - getObj
    0xfe0003 - setObj
    0xfe0004 - getAll
    0xfe0005 - addObj
    0xfe0006 - removeObj
    0xfe0007 - moveObj
    0xfe0008 - setForm
    0xfe000b - notify
    0xfe000c - shutdown
    0xfe000d - get
    0xfe000e - set
    0xfe000f - start
    0xfe0010 - poll
    0xfe0011 - cancel
    0xfe0012 - subscribe
    0xfe0013 - unsubscribe
    0xfe0014 - disconnected
    0xfe0015 - getCount
    

    進程關系

    在 RouterOS 中,init 只負責啟動 loader,由 loader 啟動和管理其他進程。

    init-+-busybox-+-ash---pstree
         |         `-ash
         |-loader-+-agent
         |        |-arpd
         |        |-bluetooth
         |        |-bridge2
         |        |-btest
         |        |-cerm-worker
         |        |-console
         |        |-discover
    ...  ...  ...
         |        |-snmp
    ...  ...  ...
    

    RouterOS Namespaces

    那么 loader 具體是怎樣管理進程呢?RouterOS 將子進程的信息寫在了 /nova/etc/loader/system.x3 中,它可以通過 Loader X3 Parser 這個工具解析。一些子進程的信息如下所示:

    ./x3_parse -f ../example/system_6_43_45.x3 
    /nova/bin/log -> 3
    /nova/bin/radius -> 5
    /nova/bin/moduler -> 6
    /nova/bin/user -> 13
    ...
    /nova/bin/snmp -> 34
    ...
    

    環境搭建

    我使用的環境為 MikroTik RouterOS 7.1.1。

    常用指令

    我們先配置一下環境,下面列出了一些參考指令:

    # 查看網卡
    interface print
    # 配置動態 IP
    ip dhcp-client add interface=ether1 disable=no
    # 查看網絡信息
    ip dhcp-client print detail
    # 查看授權
    system licens print
    # 關機
    system shutdown
    # 重啟
    system reboot
    # 重置系統
    system reset
    

    開啟 SNMP

    RouterOS 中的 SNMP 不是默認開啟的,我們需要手動開啟,下面提供了一些參考指令,也可以參考官方文檔

    # 開啟 SNMP
    snmp set enabled=yes
    # 查看當前配置的 SNMP 團體信息
    snmp community/print
    # 更改團體名字
    snmp community set name=<name> <id>
    # 添加新的團體
    snmp community add name=VegetaRocks
    # 設置聯系方式
    snmp set contact="Contact info"
    # 設置地址
    snmp set location="Location"
    # 查看 SNMP 配置信息
    snmp print
    

    獲取 root shell

    這里列舉了一些獲取 root shell 的方法。

    MethodVirtual MachineReal Devicecleaner_wrasse 1√√netboot jailbreak 2×√FOISted 3√√container_mount 4√√execute_milo 5√×memory patch [^2] 6√×

    本文使用了 execute_milo 獲取 root,它來自 tenable/routeros

    我們首先下載并安裝必要的包:

    git clone https://github.com/tenable/routeros.git
    sudo apt install libboost-all-dev cmake libboost-dev
    

    我們通過里面的 Execute Milo 工具獲取 root shell,它利用了 /flash/bin 下的 milo 文件可被覆蓋且啟動時不會做完整性校驗的 feature。

    接下來我們用 FTP 將一些必要文件傳到 RouterOS 上。FTP 傳輸文件支持四種文件類型傳輸,這里我只使用了 binary mode。我寫了一個小工具幫助上傳/下載文件:

    from ftplib import FTP
    import sys
    import argparse
    import os
    
    
    def get_bin(ftp, ftpFileName: str, localFileName=""):
        if localFileName == "":
            localFileName = os.path.basename(ftpFileName)
        ftp.retrbinary("RETR {}".format(ftpFileName),
                       open(localFileName, "wb").write)
    
    
    def put_bin(ftp, localFileName: str, ftpFileName=""):
        if ftpFileName == "":
            ftpFileName = os.path.basename(localFileName)
        print("putting {} to {}".format(localFileName, ftpFileName))
        with open(localFileName, "rb") as f:
            ftp.storbinary("STOR {}".format(ftpFileName), f)
    
    
    all_files = []
    
    
    """
    python mftp.py [--ip remote_ip] [-o operation] [-f file [file ...]] [-r renamed_file [renamed_file ...]]
    """
    
    
    def main():
        parser = argparse.ArgumentParser(
            description='Simple FTP script for RouterOS.')
        parser.add_argument("--ip",
                            action="store",
                            dest="ip",
                            help="Remote IP")
        parser.add_argument("-u", "--user",
                            action="store",
                            dest="user",
                            help="Username")
        parser.add_argument("-p", "--password",
                            action="store",
                            dest="password",
                            help="Password")
        parser.add_argument("-o", "--op",
                            action="store",
                            dest="op",
                            help="Operation")
        parser.add_argument("-f", "--file",
                            nargs='+',
                            dest="file",
                            help="To transferred filename.")
        parser.add_argument("-r", "--rename",
                            nargs='+',
                            dest="rename",
                            help="Renamed filename.")
    
        args = parser.parse_args()
        ip = args.ip
        op = args.op
        file_list = args.file
        rename_list = args.rename
        if op not in ["get", "put"]:
            print(parser.print_help())
            return
        if rename_list and len(file_list) != len(rename_list):
            print("Transfer file number not equal to renamed file number")
            return
    
        user = args.user if args.user else "admin"
        password = args.password if args.password else ""
        ftp = FTP()
        ftp.connect(ip, 21)
        ftp.login(user, password)
    
        if op == "put":
            if rename_list:
                for i in range(len(file_list)):
                    put_bin(ftp, file_list[i], rename_list[i])
            else:
                for f in file_list:
                    put_bin(ftp, f)
            return
    
        if op == "get":
            if rename_list:
                for i in range(len(file_list)):
                    get_bin(ftp, file_list[i], rename_list[i])
            else:
                for f in file_list:
                    get_bin(ftp, f)
            return
    
        ftp.quit()
    
    
    if __name__ == '__main__':
        main()
    

    上傳 vm_bins 目錄下的文件:

    python mftp.py -u <uname> -p <pwd> --ip <ip> -o put -f vm_bins/busybox vm_bins/milo vm_bins/gdb
    

    好,在上傳文件之后,關閉虛擬機,然后隨便用一個 Linux 的 Live CD 附加到虛擬機上,啟動到虛擬機中。(PS:由于這一步需要修改磁盤文件,在實體機上無法做到修改,我想這就是為什么這種方法只能用于虛擬機的原因吧。)

    [!warning] 內存分配
    可能由于虛擬機內存不足不能啟動 LiveCD,如果遇到無法啟動的情況,請考慮增大分配給虛擬機的內存。

    我們要做的事情很簡單:FTP 上傳后的文件沒有可執行權限,因此我們在 LiveCD 中給它們加上可執行權限,再將 /flash/bin 中的 milo 覆蓋即可:

    sudo su
    cd rw/disk/
    chmod +x busybox
    chmod +x gdb
    chmod 755 milo
    mv milo ../../bin/milo
    ln -s /rw/disk/busybox ash
    exit
    

    接下來我們編譯 Execute Milo 中的文件(參考 Readme 中的第六步)我們最終會獲得一個 execute_milo 文件,同時重啟到 RouterOS 中。

    接下來執行我們獲取到的二進制。

    ./execute_milo -i <ip> -p <port> -u <username> --password <password>
    
    [!bug] WinboxSession
    在較新版本的 RouterOS 上(版本可能 >= 6.44.6),由于 Winbox 協議的變化,milo 不應該使用 WinboxSession 登錄而應該使用 JSProxySession。同時,使用的端口也應當修改為 80 端口。

    最后 telnet 到 1270 端口即可獲得 shell:

    telnet <target ip> 1270
    

    漏洞觸發

    上傳 gdb/gdbserver

    hugsy/gdb-static 中有一些編譯好的靜態 gdb 和 gdbserver,各取所需即可。

    wget https://github.com/hugsy/gdb-static/raw/master/gdbserver-7.10.1-x64
    python mftp.py -u <uname> -p <pwd> --ip <ip> -o put -f gdbserver-7.10.1-x64 -r gdbserver
    

    獲取目標程序

    目標程序為 /nova/bin/snmp,我們將它復制到 /rw/disk 目錄下面然后用上面的小腳本獲取即可。

    # in RouterOS root shell
    cp /nova/bin/snmp /rw/disk
    
    # in host shell
    python mftp.py -u <uname> -p <pwd> --ip <ip> -o get -f snmp
    

    調試目標

    ./gdbserver :12345 --attach $(pidof snmp)
    

    觸發漏洞

    這里不提供完整 PoC,只給出觸發漏洞的關鍵部分(這一部分也可以在 pocs_slides/slides/POC2022-MikroTik_RouterOS_Security-The_Forgotten_IPC_Message.pdf 中找到):

    char payload[513];
        memset(payload, 'a', sizeof(char) * 512);
        WinboxMessage msg;
        msg.set_to(34, 0x1);
        msg.set_command(0xfe0005);
        msg.add_u32(0x14, 0xfffffffe);
        msg.add_string(0x5, payload);
        msg.set_request_id(1);
    

    這個 payload 的 JSON 構造為:

    {u14:0xfffffffe,uff0006:1,uff0007:0xfe0005,s5:'a'*512,Uff0001:[34,1]}
    

    根據前置知識,我們可以了解到:

    1. 它要訪問的是 /bin/snmp 的第一個 handler;
    2. 它希望添加對象;
    3. 它定義了一個 u32 數字 0xfffffffe;
    4. 它定義了一個字符串 payload;
    5. 它設置了 SYS_REQID 為 1。

    可以觸發崩潰:

    RouterOS 內部也提供了一個日志工具,可以查看崩潰信息,位于 /flash/rw/logs/

    漏洞分析

    第一層棧幀

    先跟蹤到 0x77f1fbb3,根據 vmmap,可以發現它位于 /lib/libuc++.so 中:

    0x77f18000 0x77f29000 r-xp    11000 0      /lib/libuc++.so
    0x77f29000 0x77f2a000 r-xp     1000 10000  /lib/libuc++.so
    0x77f2a000 0x77f2b000 rwxp     1000 11000  /lib/libuc++.so
    

    之后求得這個位置的偏移為 0x7bb3,我們進入到函數 sub_7BA4@<eax> 中:

    .text:00007BA4                               ; int __usercall sub_7BA4@<eax>(int@<eax>, int@<edx>, int@<ecx>)
    .text:00007BA4                               sub_7BA4 proc near                      ; CODE XREF: 
    ... ...
    .text:00007BA4                               var_4= dword ptr -4
    .text:00007BA4
    .text:00007BA4 55                            push    ebp
    .text:00007BA5 89 E5                         mov     ebp, esp
    .text:00007BA7 53                            push    ebx
    .text:00007BA8 83 EC 08                      sub     esp, 8
    .text:00007BAB 51                            push    ecx
    .text:00007BAC 52                            push    edx
    .text:00007BAD FF 70 18                      push    dword ptr [eax+18h]
    .text:00007BB0 FF 50 10                      call    dword ptr [eax+10h]
    .text:00007BB0
    .text:00007BB3 8B 5D FC                      mov     ebx, [ebp+var_4]
    .text:00007BB6 C9                            leave
    .text:00007BB7 C3                            retn
    .text:00007BB7
    .text:00007BB7                               sub_7BA4 endp
    

    可以看到是在執行到 0x00007BB0 這個位置時觸發了崩潰,調用的地址值為 [eax+10h]。我們往前跟一下 eax。

    第二層棧幀

    再往前就是 0x77f227b9,求得它的偏移為:0xa7b9,位于函數 tree_base::insert_unique 中:

    _DWORD *__userpurge tree_base::insert_unique@<eax>(_DWORD *a1, _DWORD *a2, _DWORD *a3, int a4, void (__cdecl *a5)(int))
    {
    ... ...
      {
        if ( (unsigned __int8)sub_7BA4((int)a2, a2[3] + a2[5], a4) )
        {
    ... ...
        }
    

    可以看到 a2 是被調用的地址,它是 insert_unique 的第二個參數

    第三層棧幀

    再往前追,我們可以追到 Item::regenerateKeys 函數:

    int __cdecl Item::regenerateKeys(Item *this)
    {
    ... ...
    
      v1 = 28 * *((_DWORD *)this + 6);
      v12 = (Item *)((char *)this + 48);
      v11 = (char *)&unk_80859C0 + v1;
    ... ...
        tree_base::insert_unique(&v13, v11, v7, &v18, map_node_move_constr<string,vector<unsigned char>>);
    ... ...
    

    在這里,insert_unique 函數的第二個參數是 v11,再往前追可以追到上面的三條賦值語句,我們調試看一下這里面的賦值情況:

    可以看到,在執行到

    v1 = 28 * *((_DWORD *)this + 6);
    

    時,我們發現 *((_DWORD *)this + 6) 正是我們輸入的 u14:0xfffffffe,這里有我們可以控制的輸入!

    第四層棧幀

    此時我們再往前追一下,可以跟到函數 Item::setConfig 中:

    int __cdecl Item::setConfig(Item *this, const nv::message *message)
    {
      ... ...
      *((_DWORD *)this + 6) = nv::message::get<nv::u32_id>(message, 20);
      ... ...
    

    這條語句會設置 *((_DWORD *)this + 6) 的值:

    分析總結

    由此,這個漏洞的觸發鏈已經非常清晰:

    /nova/bin/snmp 側:

    • 在 Item::setConfig 函數中通過 *((_DWORD *)this + 6) 通過輸入控制該位置的值;
    • 在 Item::regenerateKeys 函數中通過偏移控制函數地址。

    /lib/libuc++.so 側:

    • 進入函數 tree_base::insert_unique 中,滿足條件后調用 sub_7BA4@<eax> 觸發漏洞。

    漏洞利用

    在上面的漏洞分析中,我們可以通過輸入間接地控制執行流,理論上我們可以做到任意地址執行,這是多么令人驚喜啊!

    但很可惜的是,我們也就只能控制執行流了,其他的參數都不可控,因此也許只有比較極限的 ROP 才能完成攻擊。一種可能的利用思路是找到一個合適的 ROPChain,它可以布置寄存器指向堆上我們的輸入地址,做棧遷移后實現完整的攻擊。由于 RouterOS 是通過 RPC 通信的,進程中有很多函數操作 Nova Message,因此我覺得大概率是有滿足條件的 ROP chain 的。

    限于時間關系本文就不在利用上展開深入研究了,感興趣的讀者可以根據上文的思路自行尋找實現完整的利用。

    總結

    雖然這是 RouterOS 上 SNMP 進程的漏洞,但它并不是一個 SNMP 協議漏洞或者 SNMP 協議實現導致的漏洞,而是 RouterOS 自定義的協議不正確導致的漏洞。借助此文我簡單介紹了 RouterOS 協議的相關知識,以及 CVE-2022-45315 的漏洞成因,希望可以幫到大家。

    ftp命令snmp
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    常見端口滲透總結
    2022-01-16 22:32:17
    這樣,客戶端就能命令FTP服務器發一個文件給被攻擊的服務。基于Linux系統,配置方面很簡單。在nfs配置中,有不做任何限制的,有限制用戶,有限制IP,以及在版本2.x中我們還可以使用證書來驗證用戶。當然不同的限制可以采用的攻擊方式也不一樣;就目前而言網上關于nfs的攻擊還是比較少的!但是畢竟主流的攻擊方式仍舊是那些,比如注入,未授權等等;這些問題的出現也都是因為配置不當而造成的。
    SNMP(Simple Network Management Protocol,簡單網絡管理協議)是由一組網絡管理的標準組成,包含一個應用層協議和一組資源對象。該協議能夠支持網絡管理系統,用以監測連接到網絡上的設備是否有任何引起管理上關注的情況。本文介紹利用SNMP服務獲取主機信息的方法
    1.命令簡介 tcpdump 是一款類 Unix/Linux 環境下的抓包工具,允許用戶截獲和顯示發送或收到的網絡數據包。tcpdump 是一個在 BSD 許可證下發布的自由軟件。 2.命令格式
    1.命令簡介 tcpdump 是一款類 Unix/Linux 環境下的抓包工具,允許用戶截獲和顯示發送或收到的網絡數據包。tcpdump 是一個在 BSD 許可證下發布的自由軟件。 2.命令格式 tcpdump [ -AbdDefhlLnNOpqRStuUvxX ] [ -B buffer_size ] [ -c count ]
    一顆小胡椒
    暫無描述
      亚洲 欧美 自拍 唯美 另类