namedpipe安全属性的实现
首先给出结论:命名管道(NamedPipe)是可以实现安全通信的。
通信安全的技术保证来自于拒绝访问技术,管道在创建的时候即带有安全属性,属性包含了访问权限以及相应权限对应的用户(进程名,具体为SID),若该用户权限没有达到要求则无法实现访问。
命名管道的安全性功能的具体实现来自Windows的底层库,如果不额外添加加密等功能的话,我们在编程的时候只能达到对使用的用户进行筛选和设置其相应权限的功能。下面通过github上的程序为例详细介绍。
示例程序
该程序来自https://gek0n.github.io/blog/named_pipe.html
是一段完整的程序,我还没有编译确认可执行性,但是此程序向我指明了很多在安全设置上可调用的本地库函数,允许我继续以该程序为指引继续深入研究下去,因此也是很有意义的。
#include <stdio.h>
#include <stdint.h>
#include <windows.h>
#include <AccCtrl.h>
#include <Aclapi.h>
#define BUFFSIZE (512)
int main(int argc, char *argv[])
{
HANDLE pipe;
SID_IDENTIFIER_AUTHORITY SIDAuthWorld;
PSID pEveryoneSID;
if (!AllocateAndInitializeSid(&SIDAuthWorld, 1,
SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0,
&pEveryoneSID)) {
printf("[-] AllocateAndInitializeSid error: %#X\n", GetLastError());
return FALSE;
}
printf("[>] SID has been allocated at %p\n", pEveryoneSID);
EXPLICIT_ACCESS_A ea;
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = FILE_READ_DATA | FILE_WRITE_DATA;
ea.grfAccessMode = SET_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea.Trustee.ptstrName = (LPSTR)pEveryoneSID;
PACL pACL;
DWORD dwRes;
dwRes = SetEntriesInAclA(2, &ea, NULL, &pACL);
PSECURITY_DESCRIPTOR pSD;
pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
if (NULL == pSD) {
printf("[-] Alloc memory for SECURITY_DESCRIPTOR error: %#X\n", GetLastError());
}
printf("[>] Security descriptor has been allocated at %p\n", pSD);
if (!InitializeSecurityDescriptor(pSD,
SECURITY_DESCRIPTOR_REVISION)) {
printf("[-] InitializeSecurityDescriptor error: %#X\n", GetLastError());
}
printf("[>] Security descriptor has been initialized\n");
if (!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE))
{
printf("[-] SetSecurityDescriptorDacl error: %#X\n", GetLastError());
}
printf("[>] Set DACL to security descriptor\n");
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = pSD;
sa.bInheritHandle = FALSE;
printf("[>] Creating named pipe with specified security attributes...\n");
pipe = CreateNamedPipeA(
"\\\\.\\pipe\\g3k0nPipe",
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE |
PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFFSIZE,
BUFFSIZE,
0,
&sa);
if (pipe == INVALID_HANDLE_VALUE)
{
printf("[-] Failure\n");
return -1;
}
printf("[+] Created\n");
printf("[>] Waiting connection to pipe...\n");
const uint32_t connected = ConnectNamedPipe(pipe, NULL);
if (!connected) {
CloseHandle(pipe);
printf("[-] Failed to connect to named pipe\n");
return -2;
}
printf("[+] Connected\n");
const uint32_t bufSz = BUFFSIZE;
char *buf = (char *)malloc(bufSz);
uint32_t success;
uint32_t read = 0;
success = ReadFile(
pipe,
buf,
bufSz,
&read,
NULL);
if (!success || read == 0) {
printf("[-] ReadFile failed with %#X\n", GetLastError());
return -3;
}
printf("[+] Read from pipe [%d]: %s\n", read, buf);
uint32_t written = 0;
success = WriteFile(
pipe,
buf,
read,
&written,
NULL);
if (!success || written != read) {
printf("[-] WriteFile failed with %d\n", GetLastError());
return -4;
}
printf("[+] Write to pipe [%d]: %s\n", written, buf);
CloseHandle(pipe);
printf("[+] Close named pipe\n");
return 0;
}
作者对该程序的描述为:
创建安全描述符的应用程序,允许任何创建安全描述符的应用程序,允许系统中注册的用户完全访问描述该描述的对象。使用描述符创建一个指定的管道,允许所有用户访问。
具体解释
管道在创建的时候便附带开发人员定义好的属性参数(SAdefault security attributes),该属性参数在库函数中定义:
typedef struct _SECURITY_ATTRIBUTES
{
DWORD nLength;
/* [size_is] */ LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;
其中lpsecurityDescriptor和bInheritHandle参数较为重要,lpSecurityDescriptor即安全参数,而bInheritHandle指的是所创建的管道文件的子进程是否可以继承父进程的属性,在这里意义不大,可以直接设置为False。
接下来研究lpSecurityDescriptor(安全参数)。
该参数也是C中库函数定义的结构体:
typedef struct _SECURITY_DESCRIPTOR {
BYTE Revision;
BYTE Sbz1;
SECURITY_DESCRIPTOR_CONTROL Control;
PSID Owner;
PSID Group;
PACL Sacl;
PACL Dacl;
} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
该结构体中重要的是SECURITY_DESCRIPTOR_CONTROL类型的参数Control,该结构体的定义可以使Windows对象成为安全对象。
所有的被命名的Windows的对象都是安全对象。一些没有命名的对象是安全对象,如:进程和线程,也有安全描述符SD。安全对象Securable Object是拥有SD的Windows的对象。
在 Windows系统中用安全描述符(Security Descriptors)的结构来保存其权限的设置信息,简称为SD,其在Windows SDK中的结构名是“SECURITY_DESCRIPTOR”,这是包括了安全设置信息的结构体。一个安全描述符包含以下信息:
1) 一个安全标识符(Security identifiers),其标识了该信息是哪个对象的,也就是用于记录安全对象的ID。简称为:SID(该参数很重要,之后会讲到)。
2) 一个DACL(Discretionary Access Control List),其指出了允许和拒绝某用户或用户组的存取控制列表。 当一个进程需要访问安全对象,系统就会检查DACL来决定进程的访问权。如果一个对象没有DACL,那么就是说这个对象是任何人都可以拥有完全的访问权限。
3) 一个SACL(System Access Control List),其指出了在该对象上的一组存取方式(如,读、写、运行等)的存取控制权限细节的列表。还有其自身的一些控制位。
4) DACL和SACL构成了整个存取控制列表Access Control List,简称ACL,ACL中的每一项,我们叫做ACE(Access Control Entry)。
具体到程序中,对ACL的设置使用SetEntriesInAclA函数:
WINADVAPI
DWORD
WINAPI
SetEntriesInAclA(
IN ULONG cCountOfExplicitEntries,
IN PEXPLICIT_ACCESS_A pListOfExplicitEntries,
IN PACL OldAcl,
OUT PACL * NewAcl
);
其传入cCountOfExplicitEntries参数是ACL中ACE的数量,pListOfExplicitEntries则是具体的ACE值,在微软官网的api手册上指出,该结构体:
typedef struct _EXPLICIT_ACCESS_A {
DWORD grfAccessPermissions;
ACCESS_MODE grfAccessMode;
DWORD grfInheritance;
TRUSTEE_A Trustee;
} EXPLICIT_ACCESS_A, *PEXPLICIT_ACCESS_A, EXPLICIT_ACCESSA, *PEXPLICIT_ACCESSA;
**grfAccessPermissions **参数值定义管道的具体动作类型,如读,写,擦除等:
Bits Meaning
0,15 Specific rights. Contains the access mask specific to the object type associated with the mask.
16, 23 Standard rights. Contains the object’s standard access rights.
…
可以在
https://docs.microsoft.com/zh-cn/windows/win32/secauthz/access-mask
找到其余参数描述。
grfAccessMode 参数值决定相应动作类型的权限,如允许,拒绝,取消等:
NOT_USED_ACCESS Value not used.
GRANT_ACCESS Indicates an ACCESS_ALLOWED_ACE structure. The new ACE combines the specified rights with any existing allowed or denied rights of the trustee.…
可以在
https://docs.microsoft.com/zh-cn/windows/win32/api/accctrl/ne-accctrl-access_mode
找到其余参数描述。
说完ACL,回到 SECURITY_DESCRIPTOR 结构体,注意到里面还有一个参数SID,对于SID,其全称是“安全标识符(Security Identify)”,是为域或本地计算机中创建的每个帐户分配的唯一 ID 字符串(例如,S-1-5-21-1454471165-1004336348-1606980848-5555)。每个计算机的每个账户拥有唯一的SID,因此管道的使用权是以账户为单位的,如果拥有登录到电脑的密码即拥有使用相应管道的权力。