c++:让用户进程写入 LOCAL_SYSTEM 命名管道 - 自定义安全描述符
c++: Let user process write to LOCAL_SYSTEM named pipe - Custom Security Descriptor
我有一个服务 运行 作为 LocalSystem,它在登录用户的会话中创建一个进程。
然后该服务创建一个命名管道,客户端连接到该管道进行读写。
根据 https://msdn.microsoft.com/en-us/library/aa365600%28v=vs.85%29.aspx 客户端只能从管道读取(它不是 Admin,不是 Creator,也不是 LocalSystem )。
我创建了一个安全描述符来授予用户读写访问权限。但这没有用。所以我尝试为 Everyone-Group 提供读写访问权限。但这也行不通。
我的客户端 returns 的错误代码总是 ACCESS_DENIED (5).
我很高兴知道我做错了什么。
编辑: 如果我不创建自定义安全描述符而只是用 GENERIC_READ
打开管道它就可以工作(但只能读取)。
EDIT2 我想学习如何正确地做到这一点。而且我仍然只想成为能够写入的登录用户。不是每个人(这只是为了测试)。
服务代码(注释掉我获取用户 sid 的代码):
PSID EveryoneSID = nullptr;
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
if(!AllocateAndInitializeSid(&SIDAuthWorld, 1,
SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0,
&EveryoneSID))
{
throw(std::runtime_error("Failed to initialize group sid: " + std::to_string(GetLastError())));
}
//TOKEN_USER* tokeninfo = nullptr;
//DWORD tokeninfolen = 0;
//DWORD outlen = 0;
//// Query token info size
//if(!GetTokenInformation(UserToken,
// TokenUser,
// tokeninfo,
// tokeninfolen,
// &outlen))
//{
// throw(std::runtime_error("Failed to obtain user token size: " + std::to_string(GetLastError())));
//}
//// Allocate enough space to hold token user information
//tokeninfo = (TOKEN_USER*) LocalAlloc(LPTR, outlen);
//tokeninfolen = outlen;
//// Get SID from user token
//if(!GetTokenInformation(UserToken,
// TokenUser,
// tokeninfo,
// tokeninfolen,
// &outlen))
//{
// throw(std::runtime_error("Failed to obtain user token info: " + std::to_string(GetLastError())));
//}
//auto UserSID = tokeninfo->User.Sid;
//LocalFree(tokeninfo);
SECURITY_ATTRIBUTES sa = {0};
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = FALSE;
// Set up ACE
EXPLICIT_ACCESS ace = {0};
ace.grfAccessMode = SET_ACCESS;
ace.grfAccessPermissions = PIPE_ACCESS_DUPLEX; // GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE
ace.grfInheritance = NO_INHERITANCE;
ace.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ace.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ace.Trustee.ptstrName = (LPTSTR) EveryoneSID;
PACL acl = nullptr;
if(ERROR_SUCCESS != SetEntriesInAcl(1, &ace, nullptr, &acl))
throw(std::runtime_error("Failed to set acl entries: " + std::to_string(GetLastError())));
// Create security descriptor.
auto sd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
if(!InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION))
throw(std::runtime_error("Failed to initialize security descriptor: " + std::to_string(GetLastError())));
if(!SetSecurityDescriptorDacl(sd, TRUE, acl, FALSE))
throw(std::runtime_error("Failed to set DACL: " + std::to_string(GetLastError())));
// Set security descriptor in security attributes
sa.lpSecurityDescriptor = sd;
// Create a named pipe to which the user-session application
// connects.
auto pipe = CreateNamedPipe(LOCAL_PIPE_NAME,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS | PIPE_WAIT,
1,
256,
256,
NULL,
&sa);
if(pipe == INVALID_HANDLE_VALUE)
throw std::runtime_error("Failed to create named pipe");
LocalFree(acl);
LocalFree(sd);
用户进程代码:
auto pipe = CreateFile(LOCAL_PIPE_NAME,
GENERIC_READ | GENERIC_WRITE, // read access
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
if (pipe == INVALID_HANDLE_VALUE)
throw(std::runtime_error("Failed to open local pipe: " + std::to_string(GetLastError())));
有一个更简单的选项 - 将安全描述符;s dacl 设置为 NULL:
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = FALSE;
这是第一个问题:
ace.grfAccessPermissions = PIPE_ACCESS_DUPLEX;
PIPE_ACCESS_DUPLEX
常量仅用作 CreateNamedPipe() 的参数,它不是有效的访问权限。 (巧合的是,它等于 FILE_READ_DATA|FILE_WRITE_DATA
但这些访问权限本身并不允许您连接到管道。)
根据Named Pipe Security and Access Rights,以下访问权限分配给双工管道的服务器端,这意味着它们也足以打开客户端:
ace.grfAccessPermissions = FILE_GENERIC_READ | FILE_GENERIC_WRITE | SYNCHRONIZE;
但是,FILE_GENERIC_WRITE
过于宽泛,无法授予客户;特别是,它允许客户端创建管道服务器端的新实例。这不太可能是理想的。相反,对于双工管道,您应该使用
ace.grfAccessPermissions = FILE_GENERIC_READ | FILE_WRITE_DATA;
当然,你打开客户端时请求的权限必须一致:
auto pipe = CreateFile(LOCAL_PIPE_NAME,
GENERIC_READ | FILE_WRITE_DATA, // read-write access
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
详情
根据实验,在 Windows 7 SP1 x64 上,为了连接到管道(即使您在调用 CreateFile 时请求无访问权),您必须具有 READ_ATTRIBUTES
和 SYNCHRONIZE
权利。请注意,FILE_GENERIC_READ
常量包含这两个。
要从管道中读取数据,您必须拥有(并请求)FILE_READ_DATA
。 (这已纳入 FILE_GENERIC_READ
。)
要将数据写入管道,您必须拥有(并请求)FILE_WRITE_DATA
。
我有一个服务 运行 作为 LocalSystem,它在登录用户的会话中创建一个进程。 然后该服务创建一个命名管道,客户端连接到该管道进行读写。 根据 https://msdn.microsoft.com/en-us/library/aa365600%28v=vs.85%29.aspx 客户端只能从管道读取(它不是 Admin,不是 Creator,也不是 LocalSystem )。
我创建了一个安全描述符来授予用户读写访问权限。但这没有用。所以我尝试为 Everyone-Group 提供读写访问权限。但这也行不通。 我的客户端 returns 的错误代码总是 ACCESS_DENIED (5).
我很高兴知道我做错了什么。
编辑: 如果我不创建自定义安全描述符而只是用 GENERIC_READ
打开管道它就可以工作(但只能读取)。
EDIT2 我想学习如何正确地做到这一点。而且我仍然只想成为能够写入的登录用户。不是每个人(这只是为了测试)。
服务代码(注释掉我获取用户 sid 的代码):
PSID EveryoneSID = nullptr;
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
if(!AllocateAndInitializeSid(&SIDAuthWorld, 1,
SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0,
&EveryoneSID))
{
throw(std::runtime_error("Failed to initialize group sid: " + std::to_string(GetLastError())));
}
//TOKEN_USER* tokeninfo = nullptr;
//DWORD tokeninfolen = 0;
//DWORD outlen = 0;
//// Query token info size
//if(!GetTokenInformation(UserToken,
// TokenUser,
// tokeninfo,
// tokeninfolen,
// &outlen))
//{
// throw(std::runtime_error("Failed to obtain user token size: " + std::to_string(GetLastError())));
//}
//// Allocate enough space to hold token user information
//tokeninfo = (TOKEN_USER*) LocalAlloc(LPTR, outlen);
//tokeninfolen = outlen;
//// Get SID from user token
//if(!GetTokenInformation(UserToken,
// TokenUser,
// tokeninfo,
// tokeninfolen,
// &outlen))
//{
// throw(std::runtime_error("Failed to obtain user token info: " + std::to_string(GetLastError())));
//}
//auto UserSID = tokeninfo->User.Sid;
//LocalFree(tokeninfo);
SECURITY_ATTRIBUTES sa = {0};
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = FALSE;
// Set up ACE
EXPLICIT_ACCESS ace = {0};
ace.grfAccessMode = SET_ACCESS;
ace.grfAccessPermissions = PIPE_ACCESS_DUPLEX; // GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE
ace.grfInheritance = NO_INHERITANCE;
ace.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ace.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ace.Trustee.ptstrName = (LPTSTR) EveryoneSID;
PACL acl = nullptr;
if(ERROR_SUCCESS != SetEntriesInAcl(1, &ace, nullptr, &acl))
throw(std::runtime_error("Failed to set acl entries: " + std::to_string(GetLastError())));
// Create security descriptor.
auto sd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
if(!InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION))
throw(std::runtime_error("Failed to initialize security descriptor: " + std::to_string(GetLastError())));
if(!SetSecurityDescriptorDacl(sd, TRUE, acl, FALSE))
throw(std::runtime_error("Failed to set DACL: " + std::to_string(GetLastError())));
// Set security descriptor in security attributes
sa.lpSecurityDescriptor = sd;
// Create a named pipe to which the user-session application
// connects.
auto pipe = CreateNamedPipe(LOCAL_PIPE_NAME,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS | PIPE_WAIT,
1,
256,
256,
NULL,
&sa);
if(pipe == INVALID_HANDLE_VALUE)
throw std::runtime_error("Failed to create named pipe");
LocalFree(acl);
LocalFree(sd);
用户进程代码:
auto pipe = CreateFile(LOCAL_PIPE_NAME,
GENERIC_READ | GENERIC_WRITE, // read access
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
if (pipe == INVALID_HANDLE_VALUE)
throw(std::runtime_error("Failed to open local pipe: " + std::to_string(GetLastError())));
有一个更简单的选项 - 将安全描述符;s dacl 设置为 NULL:
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = FALSE;
这是第一个问题:
ace.grfAccessPermissions = PIPE_ACCESS_DUPLEX;
PIPE_ACCESS_DUPLEX
常量仅用作 CreateNamedPipe() 的参数,它不是有效的访问权限。 (巧合的是,它等于 FILE_READ_DATA|FILE_WRITE_DATA
但这些访问权限本身并不允许您连接到管道。)
根据Named Pipe Security and Access Rights,以下访问权限分配给双工管道的服务器端,这意味着它们也足以打开客户端:
ace.grfAccessPermissions = FILE_GENERIC_READ | FILE_GENERIC_WRITE | SYNCHRONIZE;
但是,FILE_GENERIC_WRITE
过于宽泛,无法授予客户;特别是,它允许客户端创建管道服务器端的新实例。这不太可能是理想的。相反,对于双工管道,您应该使用
ace.grfAccessPermissions = FILE_GENERIC_READ | FILE_WRITE_DATA;
当然,你打开客户端时请求的权限必须一致:
auto pipe = CreateFile(LOCAL_PIPE_NAME,
GENERIC_READ | FILE_WRITE_DATA, // read-write access
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
详情
根据实验,在 Windows 7 SP1 x64 上,为了连接到管道(即使您在调用 CreateFile 时请求无访问权),您必须具有 READ_ATTRIBUTES
和 SYNCHRONIZE
权利。请注意,FILE_GENERIC_READ
常量包含这两个。
要从管道中读取数据,您必须拥有(并请求)FILE_READ_DATA
。 (这已纳入 FILE_GENERIC_READ
。)
要将数据写入管道,您必须拥有(并请求)FILE_WRITE_DATA
。