标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-2832] 作者: 闲云野鸡 发表于: [2021-02-26] [2021-02-26]被用户:大猪 修改过
本文共 [323] 位读者顶过
文章说明
此文是翻译文章。另外加上了自己学习过程中的一些笔记,方便自己以后查阅。[出自:jiwo.org] 这篇文章标志着一系列关于基于 Windows进程间通信(IPC)技术组件的内部和有趣位的帖子的开始。最初,本系列将涵盖以下主题: 命名管道 Lpc ALpc Rpc 因此,一些 IPC 技术组件被排除在外,但我可能会在某个时候附加此系列,并包括其中的一些内容: windows 消息 油槽 windows套接字 DDE(动态数据交换) 好了,让我们开始使用命名管道。
尽管名称听起来有些奇怪,但管道是在两个进程之间实现通信和共享数据的非常基础和简单的技术,其中术语管道只是描述了这两个进程使用的共享内存的一部分。 命名管道 匿名管道 在大多数情况下,谈论管道时,您可能会提到“命名管道”,因为它们提供了完整的功能集,其中匿名管道主要用于子进程和父进程之间的通信。 这也意味着:管道通信可以在同一系统上的两个进程之间(具有命名管道和匿名管道),但是也可以跨机器边界进行(只有命名管道可以跨机器边界进行通信)。 由于命名管道最相关,并且支持完整的功能集,因此本文仅关注命名管道。
关于匿名管道的知识 和匿名管道的主要区别在于,命名管道有一个名字,命名管道的名字对应于一个磁盘索引节点,有了这个文件名,任何进程如果有相应的权限都可以对它进行访问,另外命名管道可以是双向的,可以跨机器的,而匿名管道是单向的,只能通过父子进程通信。 在深入研究命名管道内部之前,请注意,下面将摘录一些公开于我的命名管道示例实现的代码片段。 每当您觉得想要更多代码片段的上下文时,请转到代码存储库并查看大图。 https://github.com/csandker/InterProcessCommunication-Samples/tree/master/NamedPipes/CPP-NamedPipe-Basic-Client-Server 如果您之前从未听说过“命名管道”,您可以将此通信技术想象成一个钢管,一个人从一端大声喊叫,另一个人会在另一端听到您的话。
而无名管道却不同,进程只能访问自己或祖先创建的管道,而不能访任意访问已经存在的管道——因为没有名字。
命名管道只是一个对象,是围绕Windows文件系统而设计的一种机制,更具体地说是FILE_OBJECT,由一个特殊的文件系统命名管道文件系统(NPFS)管理: 假设您创建了一个命名管道,假设我们将其称为“ fpipe”,那么您将在一个名为“ pipe”的特殊设备驱动器上创建一个名为“ fpipe”的FILE_OBJECT。 让我们将其包装成实际的东西。 通过调用WinAPI函数CreateNamedPipe创建命名管道,例如使用以下代码: HANDLE CreateNamedPipe( LPCTSTR lpName, // 指向管道名称的指针 DWORD dwOpenMode, // 管道打开模式 DWORD dwPipeMode, // 管道模式 DWORD nMaxInstances, // 最大实例数 DWORD nOutBufferSize, // 输出缓存大小 DWORD nInBufferSize, // 输入缓存大小 DWORD nDefaultTimeOut, // 超时设置 LPSECURITY_ATTRIBUTES lpSecurityAttributes // 安全属性指针 ); 现在,此调用中最有趣的部分是\\.\pipe\fpipe。
C ++需要转义斜杠,因此与语言无关,这等于\.\pipe\fpipe。\.是指您计算机的全局根目录,其中“pipe”是指向NamedPipe设备的对象管理器符号链接。 HANDLE hPipeFile = CreateFile(L"\\\\127.0.0.1\\pipe\\fpipe", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); 从管道读取数据,只需调用ReadFile 。 ReadFile(hPipeFile, pReadBuf, MESSAGE_SIZE, pdwBytesRead, NULL); 向管道写入数据,只需调用WriteFile 。 WriteFile(serverPipe, message, messageLenght, &bytesWritten, NULL);
但是,当您“写入”管道时实际发生了什么? 最后,如引言中所述,命名管道也可以在跨系统边界的网络连接上使用。 调用远程命名管道服务器不需要任何其他实现,只需确保对CreateFile的调用指定了IP或主机名即可(如上面的CreateFile示例所示)。 让我们猜测一下:调用远程管道服务器时将使用哪种网络协议? 绝对毫不奇怪,是SMB。 建立到远程服务器的SMB连接,默认情况下,该连接由一个协议请求初始化,以确定网络身份验证协议。与其他IPC机制(如RPC)不同,作为服务器开发人员的您不能控制网络身份验证协议,因为这始终是通过SMB协商的。由于Kerberos自Windows 2000以来一直是首选的身份验证方案,因此Kerberos将是首选。
一旦认证解决了,客户端和服务器想要执行的操作再次只是经典的文件操作,这些操作由SMB处理,就像任何其他文件操作一样,例如通过启动Create Request File请求,如下所示: 命名管道提供两种基本的通信模式:字节模式和消息模式。 在字节模式下,消息在客户端和服务器之间作为连续的字节流传输。 这意味着在任何给定时刻,客户端应用程序和服务器应用程序都不确切知道正在从管道读取或向管道写入多少字节。 因此,在一侧进行写操作并不一定总是在另一侧进行相同大小的读操作。 这允许客户端和服务器传输数据而无需关心数据的大小。 在消息模式下,客户端和服务器以离散单位发送和接收数据。 每次在管道上发送消息时,必须将其作为完整的消息阅读。 如果您以消息方式从服务器管道读取数据,但是读取的缓冲区太小而无法容纳所有数据,那么适合缓冲区的那部分数据将被复制到该缓冲区中,其余数据将保留在服务器的共享内存中 部分,您将收到错误234(0xEA,ERROR_MORE_DATA),表明还有更多数据需要提取。 管道客户端和服务器均可以通过SetNamedPipeHandleState 改变管道句柄的读模式(PIPE_READMODE_MESSAGE, PIPE_READMODE_BYTE ) 及 等待模式(PIPE_NOWAIT, PIPE_WAIT)。
关于这部分知识,我还在另一篇文章找到不错的解释
消息模式的直观比较如下所示,取自“ Microsoft Windows的网络编程”(1999): 从安全角度来看,重叠的I / O,阻塞模式和输入/输出缓冲区并不是很重要,但是要知道它们的存在及其含义可以帮助理解,通信,构建和调试命名管道。 因此,我将在这里简单地添加这些概念。 几个命名管道相关的函数,例如 ReadFile, WriteFile, TransactNamedPipe, ConnectNamedPipe可以同步执行管道操作,这意味着执行线程正在等待操作完成后才能继续或异步运行。当一个函数异步运行时,即使操作尚未完成,它也会立即返回,而无需等待其完成。 请务必注意,仅可在管道(服务器)上进行异步管操作,该管道(服务器)允许通过在CreateNamedPipe中设置FILE_FLAG_OVERLAPPED来实现重叠的 I/O。. 可以通过指定一个重叠OVERLAPPED结构(https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-overlapped)作为上述每个"标准"管道操作的最后参数来进行异步呼叫。如ReadFile,或指定COMPLETION_ROUTINE作为"扩展"管道操作的最后参数,如ReadFileEx。 前者,OVERLAPPED结构,方法是基于事件的,这意味着必须创建事件对象,并在操作完成后发出信号,而COMPLETION_ROUTINE方法基于回调,这意味着回调程序传递给执行线程,该线程在发出信号后会排队执行。有关此的更多详细信息,请点击此处(https://docs.microsoft.com/en-gb/windows/win32/ipc/synchronous-and-overlapped-input-and-output)找到微软的示例实施。 当通过设置CreateNamedPipe的参数dwPipeMode为PIPE_WAIT时或者不设置这个参数时,阻塞模式将开启。 以下两个dwPipe模式标志定义了服务器的阻止模式: PIPE_WAIT(默认):启用阻止模式。当使用指定的管道操作时,例如在启用阻塞模式的管道上的ReadFile,操作等待完成。这意味着,在这样的管道上的读取操作将等待,直到有数据读取,或者写操作将等到所有数据被写入。这当然会导致在某些情况下无限期地等待。 PIPE_NOWAIT:已禁用阻止模式。命名的管道操作,如ReadFile会立即返回。您需要重叠的 I/O以确保所有数据都读取。 我指的是在调用CreateNamedPipe时创建的命名管道服务器的输入和输出缓冲区,更确切地说,是指nInBufferSize和nOutBufferSize参数中这些缓冲区的大小。 在执行读写操作时,命名管道服务器将使用非分页内存(即物理内存)临时存储要读取或写入的数据。 允许影响已创建服务器的这些值的攻击者可以通过选择较大的缓冲区来滥用这些值,从而可能导致系统崩溃,或者通过选择较小的缓冲区(例如0)来延迟管道操作: 大型缓冲区:由于未对输入/输出缓冲区进行分页,因此如果选择太大,服务器将耗尽内存。但是,系统不会“盲目”接受nInBufferSize和nOutBufferSize参数。上限由系统常数决定。我找不到有关此常量的超准确信息(也没有挖掘标题);此信息表明,对于x64 Windows7系统,该容量约为4GB。 小缓冲区:对于nInBufferSize和nOutBufferSize,缓冲区大小0绝对有效。如果系统严格执行告诉您将无法向管道中写入任何内容的操作,则导致大小为0的缓冲区是……嗯,这是不存在的缓冲区。很高兴,该系统足够聪明,可以理解您要的是最小缓冲区,因此可以将实际缓冲区分配为所接收的大小,但这会影响性能。缓冲区大小为0意味着在将新数据写入缓冲区之前,管道另一侧的进程必须读取每个字节(从而清除缓冲区)。对于nInBufferSize和nOutBufferSize都是如此。大小为0的缓冲区可能会导致服务器延迟。 我们可以再次简化本章,以介绍如何设置和控制命名管道的安全性,但是重要的是要意识到这是如何完成的。 要保护命名管道设置时,您唯一可以采取的措施是将命名管道服务器的安全描述符设置为CreateNamedPipe调用的最后一个参数(lpSecurityAttributes)。 这篇文章中作者主要说明命名管道安全其实是基于安全描述符实现的,我之前在编写过滤文件驱动时对于安全描述符的印象深刻,并且有相当多的网站描述它,所以就不在描述安全描述符。 如果您未在您的命名管道服务器中设置任何安全描述符,则任何客户端都有权限(GENERIC_READ )该命名管道。 模拟是一个简单的概念,我们将需要在以下部分谈论攻击载体与命名的管道。 如果您熟悉模拟,请随时跳过此部分:因为模拟不是特定于命名管道。 这篇文章作者描述的感觉对新手不太友好,我这里写上自己对模拟的见解,从安全的角度来看,实际上模拟就是指进程的Token模拟,往往客户端的token权限比较低的,如果客户端想做的事请请求发送过来了服务端都去照做了,那么安全问题就显而易见了,但是模拟客户端之后后,我的服务端实际上替客户端做动作的时的权限就是低权限也就是客户端进程的权限。 客户端进程(每个进程)token里有这样四个模拟级别Anonymous,Identification,Impersonation,Delegation
Anonymous:服务器无法模拟或识别客户端。
好吧,所以当我们在讨论这个话题时,以防万一您还没有完全厌倦。 让我们快速了解一下如果服务器模拟了客户端的实际情况。 https://github.com/csandker/InterProcessCommunication-Samples/blob/master/NamedPipes/CPP-NamedPipe-Basic-Client-Server/CPP-Basic-PipeServer/CPP-Basic-PipeServer.cpp#L21
好吧,我有点蠢,这里没读懂作者的意思。我找到了另两篇利用服务端模拟客户端命名管道进行提权的文章。 最后我发现作者的文章和上面两篇文章都是从管理员到system而不是从普通用户到system,。
经过试验发现,只能从高权限到任意权限,而服务端如果没有模拟权限(中等权限进程或者低权限进程),那么则无法进行模拟任何权限的客户端令牌。 为了防止滥用模仿机制,Windows不允许服务器在没有得到客户同意的情况下执行模拟。 客户进程在连接到服务器的时候可以指定一个SQOS(security quality of service),以此限制服务器进程可以执行的模拟等级。 终于,我们正在谈论攻击面。 基于命名管道的最重要的攻击媒介是模拟。 幸运的是,我们已经在上一节中介绍并理解了模拟的概念,因此我们可以直接进行研究。 当您获得允许您指定或控制访问文件的服务,程序或例程时(最好是允许您进行READ或WRITE访问或两者兼而有之),使用命名管道的模拟最容易被滥用。 由于命名管道基本上是FILE_OBJECT,并且与常规文件(ReadFile,WriteFile,CreateFile等)一起使用相同的访问功能,因此您可以指定命名管道而不是常规文件名,并使受害进程连接到您控制下的命名管道。 尝试模拟客户端时,需要检查两个重要方面。 第一个是检查客户端如何实现文件访问,更具体地说,客户端在调用CreateFile时是否指定SECURITY_SQOS_PRESENT标志? 易受攻击的对CreateFile的调用如下所示: hFile = CreateFile(pipeName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); 默认调用CreateFile函数访问命名管道时采用的权限就是Impersonation级别,用于模拟本地权限。 而安全的CreateFile调用是这样的: // calling with explicit SECURITY_IMPERSONATION_LEVEL 设置权限为Identification hFile = CreateFile(pipeName, GENERIC_READ, 0, NULL, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION , NULL); // calling without explicit SECURITY_IMPERSONATION_LEVEL 只设置SECURITY_SQOS_PRESENT 那么默认权限为Anonymous hFile = CreateFile(pipeName, GENERIC_READ, 0, NULL, OPEN_EXISTING, SECURITY_SQOS_PRESENT, NULL); Anonymous 和Identification不能模拟客户端。 利用命名管道模拟机制可以实现特权升级,但是这个特权升级是从SeImpersonatePrivilege到system,但是SeImpersonatePrivilege权限只有是默认管理员用户启动的进程有的权限,要利用这个漏洞还需要让当前用户获得SeImpersonatePrivilege权限。
如果账户拥有SeImpersonatePrivilege权限,那么从SeImpersonatePrivilege权限到system微软认为不算漏洞。因为普通用户根本没有SeImpersonatePrivilege权限。
其他关于命名管道的文章 http://www.myzaker.com/article/5ed3414cb15ec07e8b1200dd/ https://blog.csdn.net/weixin_39860849/article/details/111089488 |