mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-07 09:58:54 +00:00
windows: relax "safe" permissions for binary execution (#3727)
This commit is contained in:
parent
702203086f
commit
c494bc56ae
@ -457,7 +457,7 @@ bool safePermissions(const fs::path& dir,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fd.isOwnerCurrentUser().ok() || fd.isOwnerRoot().ok()) {
|
||||
if (fd.isOwnerRoot().ok() || fd.isOwnerCurrentUser().ok()) {
|
||||
result = fd.isExecutable();
|
||||
|
||||
// Otherwise, require matching or root file ownership.
|
||||
|
@ -256,13 +256,13 @@ static DWORD getNewAclSize(PACL dacl,
|
||||
}
|
||||
|
||||
if (entry->AceType == ACCESS_ALLOWED_ACE_TYPE &&
|
||||
::EqualSid(sid, &((ACCESS_ALLOWED_ACE*)entry)->SidStart)) {
|
||||
EqualSid(sid, &((ACCESS_ALLOWED_ACE*)entry)->SidStart)) {
|
||||
acl_size -=
|
||||
sizeof(ACCESS_ALLOWED_ACE) + ::GetLengthSid(sid) - sizeof(DWORD);
|
||||
}
|
||||
|
||||
if (entry->AceType == ACCESS_DENIED_ACE_TYPE &&
|
||||
::EqualSid(sid, &((ACCESS_DENIED_ACE*)entry)->SidStart)) {
|
||||
EqualSid(sid, &((ACCESS_DENIED_ACE*)entry)->SidStart)) {
|
||||
acl_size -=
|
||||
sizeof(ACCESS_DENIED_ACE) + ::GetLengthSid(sid) - sizeof(DWORD);
|
||||
}
|
||||
@ -289,17 +289,17 @@ static Status checkAccessWithSD(PSECURITY_DESCRIPTOR sd, mode_t mode) {
|
||||
access_rights |= GENERIC_EXECUTE;
|
||||
}
|
||||
|
||||
status = ::OpenProcessToken(
|
||||
::GetCurrentProcess(),
|
||||
status = OpenProcessToken(
|
||||
GetCurrentProcess(),
|
||||
TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ,
|
||||
&process_token);
|
||||
if (!status) {
|
||||
return Status(-1, "OpenProcessToken failed");
|
||||
}
|
||||
|
||||
status = ::DuplicateToken(
|
||||
process_token, SecurityImpersonation, &impersonate_token);
|
||||
::CloseHandle(process_token);
|
||||
status =
|
||||
DuplicateToken(process_token, SecurityImpersonation, &impersonate_token);
|
||||
CloseHandle(process_token);
|
||||
|
||||
if (!status) {
|
||||
return Status(-1, "DuplicateToken failed");
|
||||
@ -320,24 +320,24 @@ static Status checkAccessWithSD(PSECURITY_DESCRIPTOR sd, mode_t mode) {
|
||||
mapping.GenericExecute = FILE_GENERIC_EXECUTE;
|
||||
mapping.GenericAll = FILE_ALL_ACCESS;
|
||||
|
||||
::MapGenericMask(&access_rights, &mapping);
|
||||
MapGenericMask(&access_rights, &mapping);
|
||||
|
||||
status = ::AccessCheck(sd,
|
||||
impersonate_token,
|
||||
access_rights,
|
||||
&mapping,
|
||||
&privileges,
|
||||
&privileges_length,
|
||||
&granted_access,
|
||||
&access_status);
|
||||
::CloseHandle(impersonate_token);
|
||||
status = AccessCheck(sd,
|
||||
impersonate_token,
|
||||
access_rights,
|
||||
&mapping,
|
||||
&privileges,
|
||||
&privileges_length,
|
||||
&granted_access,
|
||||
&access_status);
|
||||
CloseHandle(impersonate_token);
|
||||
|
||||
if (!status) {
|
||||
return Status(-1, "AccessCheck failed");
|
||||
}
|
||||
|
||||
if (access_status) {
|
||||
return Status(0, "OK");
|
||||
return Status();
|
||||
}
|
||||
|
||||
return Status(1, "Bad mode for file");
|
||||
@ -350,20 +350,20 @@ static Status hasAccess(const fs::path& path, mode_t mode) {
|
||||
GROUP_SECURITY_INFORMATION |
|
||||
DACL_SECURITY_INFORMATION;
|
||||
|
||||
result = ::GetNamedSecurityInfoW(path.wstring().c_str(),
|
||||
SE_FILE_OBJECT,
|
||||
security_info,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&sd);
|
||||
result = GetNamedSecurityInfoW(path.wstring().c_str(),
|
||||
SE_FILE_OBJECT,
|
||||
security_info,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&sd);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
return Status(-1, "GetNamedSecurityInfo failed: " + std::to_string(result));
|
||||
}
|
||||
|
||||
auto status = checkAccessWithSD(sd, mode);
|
||||
::LocalFree(sd);
|
||||
LocalFree(sd);
|
||||
|
||||
return status;
|
||||
}
|
||||
@ -375,18 +375,14 @@ static Status hasAccess(HANDLE handle, mode_t mode) {
|
||||
GROUP_SECURITY_INFORMATION |
|
||||
DACL_SECURITY_INFORMATION;
|
||||
|
||||
status =
|
||||
::GetUserObjectSecurity(handle, &security_info, nullptr, 0, &sd_size);
|
||||
if (status || (!status && ::GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
|
||||
status = GetUserObjectSecurity(handle, &security_info, nullptr, 0, &sd_size);
|
||||
if (status || (!status && GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
|
||||
return Status(-1, "GetUserObjectSecurity get SD size error");
|
||||
}
|
||||
|
||||
std::vector<char> sd_buffer;
|
||||
sd_buffer.assign(sd_size, '\0');
|
||||
|
||||
std::vector<char> sd_buffer(sd_size, '\0');
|
||||
PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)sd_buffer.data();
|
||||
status =
|
||||
::GetUserObjectSecurity(handle, &security_info, sd, sd_size, &sd_size);
|
||||
status = GetUserObjectSecurity(handle, &security_info, sd, sd_size, &sd_size);
|
||||
if (!status) {
|
||||
return Status(-1, "GetUserObjectSecurity failed");
|
||||
}
|
||||
@ -400,8 +396,8 @@ static AclObject modifyAcl(PACL acl,
|
||||
bool allow_write,
|
||||
bool allow_exec,
|
||||
bool target_is_owner = false) {
|
||||
if (acl == nullptr || !::IsValidAcl(acl) || target == nullptr ||
|
||||
!::IsValidSid(target)) {
|
||||
if (acl == nullptr || !IsValidAcl(acl) || target == nullptr ||
|
||||
!IsValidSid(target)) {
|
||||
return std::move(AclObject());
|
||||
}
|
||||
|
||||
@ -410,21 +406,20 @@ static AclObject modifyAcl(PACL acl,
|
||||
* To mimic this behavior on Windows, we give READ_CONTROL permissions to
|
||||
* everyone. READ_CONTROL allows for an user to read the target file's DACL.
|
||||
*/
|
||||
DWORD allow_mask = READ_CONTROL;
|
||||
DWORD deny_mask = 0;
|
||||
unsigned long allow_mask = READ_CONTROL;
|
||||
unsigned long deny_mask = 0;
|
||||
|
||||
ACL_SIZE_INFORMATION info = {0};
|
||||
info.AclBytesInUse = sizeof(ACL);
|
||||
|
||||
if (!::GetAclInformation(acl, &info, sizeof(info), AclSizeInformation)) {
|
||||
if (!GetAclInformation(acl, &info, sizeof(info), AclSizeInformation)) {
|
||||
return std::move(AclObject());
|
||||
}
|
||||
|
||||
if (target_is_owner) {
|
||||
/*
|
||||
* Owners should always have the ability to delete the target file and
|
||||
* modify the target file's DACL--at least this appears to be the case for
|
||||
* POSIX.
|
||||
* modify the target file's DACL
|
||||
*/
|
||||
allow_mask |= DELETE | WRITE_DAC;
|
||||
}
|
||||
@ -472,7 +467,7 @@ static AclObject modifyAcl(PACL acl,
|
||||
deny_mask |= FILE_READ_ATTRIBUTES;
|
||||
}
|
||||
|
||||
DWORD new_acl_size = 0;
|
||||
unsigned long new_acl_size = 0;
|
||||
if (allow_read && allow_write && allow_exec) {
|
||||
new_acl_size = getNewAclSize(acl, target, info, true, false);
|
||||
} else {
|
||||
@ -482,7 +477,7 @@ static AclObject modifyAcl(PACL acl,
|
||||
AclObject new_acl_buffer(new unsigned char[new_acl_size]);
|
||||
PACL new_acl = reinterpret_cast<PACL>(new_acl_buffer.get());
|
||||
|
||||
if (!::InitializeAcl(new_acl, new_acl_size, ACL_REVISION)) {
|
||||
if (!InitializeAcl(new_acl, new_acl_size, ACL_REVISION)) {
|
||||
return std::move(AclObject());
|
||||
}
|
||||
|
||||
@ -501,47 +496,49 @@ static AclObject modifyAcl(PACL acl,
|
||||
* that deal with viewing or modifying the ACL (such as File Explorer).
|
||||
*/
|
||||
|
||||
DWORD i = 0;
|
||||
PACE_HEADER entry = nullptr;
|
||||
unsigned long i = 0;
|
||||
LPVOID void_ent = nullptr;
|
||||
for (i = 0; i < info.AceCount; i++) {
|
||||
if (!::GetAce(acl, i, (LPVOID*)&entry)) {
|
||||
if (!GetAce(acl, i, &void_ent)) {
|
||||
return std::move(AclObject());
|
||||
}
|
||||
|
||||
auto entry = static_cast<PACE_HEADER>(void_ent);
|
||||
if ((entry->AceFlags & INHERITED_ACE) == INHERITED_ACE) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto allowed_ace = reinterpret_cast<ACCESS_ALLOWED_ACE*>(entry);
|
||||
auto denied_ace = reinterpret_cast<ACCESS_DENIED_ACE*>(entry);
|
||||
if ((entry->AceType == ACCESS_ALLOWED_ACE_TYPE &&
|
||||
::EqualSid(target, &((ACCESS_ALLOWED_ACE*)entry)->SidStart)) ||
|
||||
EqualSid(target, &allowed_ace->SidStart)) ||
|
||||
(entry->AceType == ACCESS_DENIED_ACE_TYPE &&
|
||||
::EqualSid(target, &((ACCESS_DENIED_ACE*)entry)->SidStart))) {
|
||||
EqualSid(target, &denied_ace->SidStart))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!::AddAce(
|
||||
new_acl, ACL_REVISION, MAXDWORD, (LPVOID)entry, entry->AceSize)) {
|
||||
if (!AddAce(new_acl, ACL_REVISION, MAXDWORD, entry, entry->AceSize)) {
|
||||
return std::move(AclObject());
|
||||
}
|
||||
}
|
||||
|
||||
if (deny_mask != 0 &&
|
||||
!::AddAccessDeniedAce(new_acl, ACL_REVISION, deny_mask, target)) {
|
||||
!AddAccessDeniedAce(new_acl, ACL_REVISION, deny_mask, target)) {
|
||||
return std::move(AclObject());
|
||||
}
|
||||
|
||||
if (allow_mask != 0 &&
|
||||
!::AddAccessAllowedAce(new_acl, ACL_REVISION, allow_mask, target)) {
|
||||
!AddAccessAllowedAce(new_acl, ACL_REVISION, allow_mask, target)) {
|
||||
return std::move(AclObject());
|
||||
}
|
||||
|
||||
for (; i < info.AceCount; i++) {
|
||||
if (!::GetAce(acl, i, (LPVOID*)&entry)) {
|
||||
if (!GetAce(acl, i, &void_ent)) {
|
||||
return std::move(AclObject());
|
||||
}
|
||||
|
||||
if (!::AddAce(
|
||||
new_acl, ACL_REVISION, MAXDWORD, (LPVOID)entry, entry->AceSize)) {
|
||||
auto entry = static_cast<PACE_HEADER>(void_ent);
|
||||
if (!AddAce(new_acl, ACL_REVISION, MAXDWORD, void_ent, entry->AceSize)) {
|
||||
return std::move(AclObject());
|
||||
}
|
||||
}
|
||||
@ -551,9 +548,9 @@ static AclObject modifyAcl(PACL acl,
|
||||
|
||||
PlatformFile::PlatformFile(const fs::path& path, int mode, int perms)
|
||||
: fname_(path) {
|
||||
DWORD access_mask = 0;
|
||||
DWORD flags_and_attrs = 0;
|
||||
DWORD creation_disposition = 0;
|
||||
unsigned long access_mask = 0;
|
||||
unsigned long flags_and_attrs = 0;
|
||||
unsigned long creation_disposition = 0;
|
||||
std::unique_ptr<SECURITY_ATTRIBUTES> security_attrs;
|
||||
|
||||
if ((mode & PF_READ) == PF_READ) {
|
||||
@ -613,54 +610,52 @@ PlatformFile::~PlatformFile() {
|
||||
if (handle_ != kInvalidHandle && handle_ != nullptr) {
|
||||
// Only cancel IO if we are a non-blocking HANDLE
|
||||
if (is_nonblock_) {
|
||||
::CancelIo(handle_);
|
||||
CancelIo(handle_);
|
||||
}
|
||||
|
||||
::CloseHandle(handle_);
|
||||
CloseHandle(handle_);
|
||||
handle_ = kInvalidHandle;
|
||||
}
|
||||
}
|
||||
|
||||
bool PlatformFile::isSpecialFile() const {
|
||||
return (::GetFileType(handle_) != FILE_TYPE_DISK);
|
||||
return (GetFileType(handle_) != FILE_TYPE_DISK);
|
||||
}
|
||||
|
||||
static Status isUserCurrentUser(PSID user) {
|
||||
BOOL ret = FALSE;
|
||||
HANDLE token = INVALID_HANDLE_VALUE;
|
||||
|
||||
if (!::IsValidSid(user)) {
|
||||
if (!IsValidSid(user)) {
|
||||
return Status(-1, "Invalid SID");
|
||||
}
|
||||
|
||||
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_READ, &token)) {
|
||||
HANDLE token = INVALID_HANDLE_VALUE;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token)) {
|
||||
return Status(-1, "OpenProcessToken failed");
|
||||
}
|
||||
|
||||
DWORD size = 0;
|
||||
PTOKEN_USER ptu = nullptr;
|
||||
unsigned long size = 0;
|
||||
LPVOID ptu = nullptr;
|
||||
auto ret = GetTokenInformation(token, TokenUser, ptu, 0, &size);
|
||||
if (ret || (!ret && GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
|
||||
CloseHandle(token);
|
||||
|
||||
ret = ::GetTokenInformation(token, TokenUser, (LPVOID)ptu, 0, &size);
|
||||
if (ret || (!ret && ::GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
|
||||
::CloseHandle(token);
|
||||
|
||||
return Status(-1, "GetTokenInformation failed (1)");
|
||||
return Status(
|
||||
-1,
|
||||
"GetTokenInformation failed (" + std::to_string(GetLastError()) + ")");
|
||||
}
|
||||
|
||||
std::vector<char> buffer(size);
|
||||
ptu = (PTOKEN_USER)buffer.data();
|
||||
|
||||
/// Obtain the user SID behind the token handle
|
||||
ret = ::GetTokenInformation(token, TokenUser, (LPVOID)ptu, size, &size);
|
||||
::CloseHandle(token);
|
||||
ret = GetTokenInformation(token, TokenUser, ptu, size, &size);
|
||||
CloseHandle(token);
|
||||
|
||||
if (!ret) {
|
||||
return Status(-1, "GetTokenInformation failed (2)");
|
||||
return Status(
|
||||
-1,
|
||||
"GetTokenInformation failed (" + std::to_string(GetLastError()) + ")");
|
||||
}
|
||||
|
||||
/// Determine if the current user SID matches that of the specified user
|
||||
if (::EqualSid(user, ptu->User.Sid)) {
|
||||
return Status(0, "OK");
|
||||
if (EqualSid(user, static_cast<PTOKEN_USER>(ptu)->User.Sid)) {
|
||||
return Status();
|
||||
}
|
||||
|
||||
return Status(1, "User not current user");
|
||||
@ -668,64 +663,65 @@ static Status isUserCurrentUser(PSID user) {
|
||||
|
||||
Status PlatformFile::isOwnerRoot() const {
|
||||
if (!isValid()) {
|
||||
return Status(-1, "Invalid handle_");
|
||||
return Status(-1, "Invalid file handle value");
|
||||
}
|
||||
|
||||
PSID owner = nullptr;
|
||||
PSECURITY_DESCRIPTOR sd = nullptr;
|
||||
|
||||
if (::GetSecurityInfo(handle_,
|
||||
SE_FILE_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION,
|
||||
&owner,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&sd) != ERROR_SUCCESS) {
|
||||
return Status(-1, "GetSecurityInfo failed");
|
||||
if (GetSecurityInfo(handle_,
|
||||
SE_FILE_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION,
|
||||
&owner,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&sd) != ERROR_SUCCESS) {
|
||||
return Status(1, "GetSecurityInfo failed");
|
||||
}
|
||||
|
||||
SecurityDescriptor sd_wrapper(sd);
|
||||
DWORD sid_buff_size = SECURITY_MAX_SID_SIZE;
|
||||
|
||||
DWORD admins_buf_size = SECURITY_MAX_SID_SIZE;
|
||||
std::vector<char> admins_buf;
|
||||
admins_buf.assign(admins_buf_size, '\0');
|
||||
|
||||
PSID admins_sid = (PSID)admins_buf.data();
|
||||
|
||||
if (!::CreateWellKnownSid(
|
||||
WinBuiltinAdministratorsSid, nullptr, admins_sid, &admins_buf_size)) {
|
||||
std::vector<char> admins_buf(sid_buff_size, '\0');
|
||||
auto admins_sid = static_cast<PSID>(admins_buf.data());
|
||||
if (!CreateWellKnownSid(
|
||||
WinBuiltinAdministratorsSid, nullptr, admins_sid, &sid_buff_size)) {
|
||||
return Status(-1, "CreateWellKnownSid failed");
|
||||
}
|
||||
|
||||
if (::EqualSid(owner, admins_sid)) {
|
||||
return Status(0, "OK");
|
||||
std::vector<char> system_buf(sid_buff_size, '\0');
|
||||
auto system_sid = static_cast<PSID>(system_buf.data());
|
||||
if (!CreateWellKnownSid(
|
||||
WinLocalSystemSid, nullptr, system_sid, &sid_buff_size)) {
|
||||
return Status(-1, "CreateWellKnownSid failed");
|
||||
}
|
||||
|
||||
return Status(1, "Owner is not Administrators group");
|
||||
if (EqualSid(owner, admins_sid) || EqualSid(owner, system_sid)) {
|
||||
return Status();
|
||||
}
|
||||
|
||||
return Status(1, "Owner is not in Administrators group or Local System");
|
||||
}
|
||||
|
||||
Status PlatformFile::isOwnerCurrentUser() const {
|
||||
if (!isValid()) {
|
||||
return Status(-1, "Invalid handle_");
|
||||
return Status(-1, "Invalid file handle value");
|
||||
}
|
||||
|
||||
PSID owner = nullptr;
|
||||
PSECURITY_DESCRIPTOR sd = nullptr;
|
||||
|
||||
if (::GetSecurityInfo(handle_,
|
||||
SE_FILE_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION,
|
||||
&owner,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&sd) != ERROR_SUCCESS) {
|
||||
if (GetSecurityInfo(handle_,
|
||||
SE_FILE_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION,
|
||||
&owner,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&sd) != ERROR_SUCCESS) {
|
||||
return Status(-1, "GetSecurityInfo failed");
|
||||
}
|
||||
|
||||
SecurityDescriptor sd_wrapper(sd);
|
||||
|
||||
return isUserCurrentUser(owner);
|
||||
}
|
||||
|
||||
@ -733,105 +729,157 @@ Status PlatformFile::isExecutable() const {
|
||||
return hasAccess(handle_, X_OK);
|
||||
}
|
||||
|
||||
static Status isWriteDenied(PACL acl) {
|
||||
/*
|
||||
* We ensure that only the Administrators group and the SYSTEM
|
||||
* account itself have Write privileges on the specified ACL
|
||||
*/
|
||||
static Status lowPrivWriteDenied(PACL acl) {
|
||||
if (acl == nullptr) {
|
||||
return Status(-1, "Invalid ACL pointer");
|
||||
return Status(1, "Invalid ACL pointer");
|
||||
}
|
||||
|
||||
DWORD sid_buffer_size = SECURITY_MAX_SID_SIZE;
|
||||
unsigned long sid_buff_size = SECURITY_MAX_SID_SIZE;
|
||||
|
||||
std::vector<char> sid_buffer;
|
||||
sid_buffer.assign(sid_buffer_size, '\0');
|
||||
std::vector<char> system_buffer(sid_buff_size, '\0');
|
||||
std::vector<char> administrators_buffer(sid_buff_size, '\0');
|
||||
std::vector<char> world_buffer(sid_buff_size, '\0');
|
||||
|
||||
PSID world = (PSID)sid_buffer.data();
|
||||
auto system_sid = static_cast<PSID>(system_buffer.data());
|
||||
auto admins_sid = static_cast<PSID>(administrators_buffer.data());
|
||||
auto world_sid = static_cast<PSID>(world_buffer.data());
|
||||
|
||||
if (!::CreateWellKnownSid(WinWorldSid, nullptr, world, &sid_buffer_size)) {
|
||||
return Status(-1, "CreateWellKnownSid failed");
|
||||
if (!CreateWellKnownSid(
|
||||
WinBuiltinAdministratorsSid, nullptr, system_sid, &sid_buff_size)) {
|
||||
return Status(-1, "CreateWellKnownSid for Administrators failed");
|
||||
}
|
||||
if (!CreateWellKnownSid(
|
||||
WinLocalSystemSid, nullptr, admins_sid, &sid_buff_size)) {
|
||||
return Status(-1, "CreateWellKnownSid for SYSTEM failed");
|
||||
}
|
||||
if (!CreateWellKnownSid(WinWorldSid, nullptr, world_sid, &sid_buff_size)) {
|
||||
return Status(-1, "CreateWellKnownSid for Everyone failed");
|
||||
}
|
||||
|
||||
PACE_HEADER entry = nullptr;
|
||||
for (DWORD i = 0; i < acl->AceCount; i++) {
|
||||
if (!::GetAce(acl, i, (LPVOID*)&entry)) {
|
||||
return Status(-1, "GetAce failed");
|
||||
PVOID void_ent = nullptr;
|
||||
std::set<PSID> denyWriteSids;
|
||||
for (unsigned long i = 0; i < acl->AceCount; i++) {
|
||||
if (!GetAce(acl, i, &void_ent)) {
|
||||
return Status(-1,
|
||||
"Failed to retreive ACE when checking safe permissions");
|
||||
}
|
||||
auto entry = static_cast<PACE_HEADER>(void_ent);
|
||||
|
||||
// If the ACE is a Deny-Write it supercedes subsequent allows, save
|
||||
// for the future potential allow entry
|
||||
if (entry->AceType == ACCESS_DENIED_ACE_TYPE) {
|
||||
auto denied_ace = reinterpret_cast<PACCESS_DENIED_ACE>(entry);
|
||||
|
||||
// We only care about Deny-Write entries, everything else is at the
|
||||
// users discretion
|
||||
if ((denied_ace->Mask & CHMOD_WRITE) != CHMOD_WRITE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// A Deny-Write on Everyone supersedes other allow writes
|
||||
if (EqualSid(&denied_ace->SidStart, world_sid)) {
|
||||
return Status();
|
||||
}
|
||||
|
||||
// Stash the Deny-Write ACE to check against future user Allow ACEs
|
||||
denyWriteSids.insert(&denied_ace->SidStart);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to see if the deny ACE is for Everyone while making sure that there
|
||||
* must be no allow ACE that allow for writes before the denies
|
||||
*/
|
||||
if (entry->AceType == ACCESS_DENIED_ACE_TYPE) {
|
||||
PACCESS_DENIED_ACE denied_ace = (PACCESS_DENIED_ACE)entry;
|
||||
if (entry->AceType == ACCESS_ALLOWED_ACE_TYPE) {
|
||||
auto allowed_ace = reinterpret_cast<PACCESS_ALLOWED_ACE>(entry);
|
||||
|
||||
if (::EqualSid(&denied_ace->SidStart, world) &&
|
||||
(denied_ace->Mask & CHMOD_WRITE) == CHMOD_WRITE) {
|
||||
return Status(0, "OK");
|
||||
// Administrators and SYSTEM are allowed Full access
|
||||
if (EqualSid(&allowed_ace->SidStart, system_sid) ||
|
||||
EqualSid(&allowed_ace->SidStart, admins_sid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Deny-Write ACEs supersede Allow-Write ACEs, however this is
|
||||
* only the case if the Deny ACE appears _before_ the allow. As
|
||||
* such the location of the below equality check is important, and
|
||||
* the check for an allow should only come after the check for a deny
|
||||
* has been processed.
|
||||
*/
|
||||
auto hasDeny = false;
|
||||
for (const auto& p : denyWriteSids) {
|
||||
if (EqualSid(&allowed_ace->SidStart, p)) {
|
||||
hasDeny = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasDeny) {
|
||||
continue;
|
||||
}
|
||||
} else if (entry->AceType == ACCESS_ALLOWED_ACE_TYPE) {
|
||||
// This covers the case where the DACL has been modified by platformChmod
|
||||
PACCESS_ALLOWED_ACE allowed_ace = (PACCESS_ALLOWED_ACE)entry;
|
||||
|
||||
// Check to see if ANY of CHMOD_WRITE rights are set
|
||||
if ((allowed_ace->Mask & CHMOD_WRITE) != 0) {
|
||||
// Fail, since we discovered an access allowed ACE that enables write
|
||||
break;
|
||||
return Status(-1, "Write ACE was found on ACL");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status(1, "No deny ACE for write");
|
||||
return Status();
|
||||
}
|
||||
|
||||
Status PlatformFile::hasSafePermissions() const {
|
||||
// Get the access control list for the file specified
|
||||
PACL file_dacl = nullptr;
|
||||
PSECURITY_DESCRIPTOR file_sd = nullptr;
|
||||
|
||||
if (::GetSecurityInfo(handle_,
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&file_dacl,
|
||||
nullptr,
|
||||
&file_sd) != ERROR_SUCCESS) {
|
||||
if (GetSecurityInfo(handle_,
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&file_dacl,
|
||||
nullptr,
|
||||
&file_sd) != ERROR_SUCCESS) {
|
||||
return Status(-1, "GetSecurityInfo failed");
|
||||
}
|
||||
|
||||
SecurityDescriptor file_sd_wrapper(file_sd);
|
||||
|
||||
std::vector<char> path_buf;
|
||||
path_buf.assign(MAX_PATH + 1, '\0');
|
||||
|
||||
// Derive the parent directory and insure it also has safe permissions
|
||||
if (::GetFinalPathNameByHandleA(
|
||||
// Get the access control list for the parent directory
|
||||
std::vector<char> path_buf(MAX_PATH + 1, '\0');
|
||||
if (GetFinalPathNameByHandleA(
|
||||
handle_, path_buf.data(), MAX_PATH, FILE_NAME_NORMALIZED) == 0) {
|
||||
return Status(-1, "GetFinalPathNameByHandleA failed");
|
||||
}
|
||||
|
||||
if (!::PathRemoveFileSpecA(path_buf.data())) {
|
||||
if (!PathRemoveFileSpecA(path_buf.data())) {
|
||||
return Status(-1, "PathRemoveFileSpec");
|
||||
}
|
||||
|
||||
PACL dir_dacl = nullptr;
|
||||
PSECURITY_DESCRIPTOR dir_sd = nullptr;
|
||||
|
||||
if (::GetNamedSecurityInfoA(path_buf.data(),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&dir_dacl,
|
||||
nullptr,
|
||||
&dir_sd) != ERROR_SUCCESS) {
|
||||
if (GetNamedSecurityInfoA(path_buf.data(),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&dir_dacl,
|
||||
nullptr,
|
||||
&dir_sd) != ERROR_SUCCESS) {
|
||||
return Status(-1, "GetNamedSecurityInfoA failed for dir");
|
||||
}
|
||||
|
||||
SecurityDescriptor dir_sd_wrapper(dir_sd);
|
||||
|
||||
if (isWriteDenied(file_dacl).ok() && isWriteDenied(dir_dacl).ok()) {
|
||||
return Status(0, "OK");
|
||||
/*
|
||||
* Check to ensure that no allow write ACEs are found on either
|
||||
* the daemon or the parent directory
|
||||
*/
|
||||
auto s = lowPrivWriteDenied(file_dacl);
|
||||
if (!s.ok()) {
|
||||
return Status(1, "Write ACE was found on the executable");
|
||||
}
|
||||
|
||||
return Status(1, "Not safe for loading");
|
||||
s = lowPrivWriteDenied(dir_dacl);
|
||||
if (!s.ok()) {
|
||||
return Status(1, "Write ACE was found on the parent directory");
|
||||
}
|
||||
return Status(0, "OK");
|
||||
}
|
||||
|
||||
bool PlatformFile::getFileTimes(PlatformTime& times) {
|
||||
@ -1029,22 +1077,20 @@ size_t PlatformFile::size() const {
|
||||
}
|
||||
|
||||
bool platformChmod(const std::string& path, mode_t perms) {
|
||||
DWORD ret = 0;
|
||||
PACL dacl = nullptr;
|
||||
PSID owner = nullptr;
|
||||
PSID group = nullptr;
|
||||
PSECURITY_DESCRIPTOR sd = nullptr;
|
||||
|
||||
ret = ::GetNamedSecurityInfoA(path.c_str(),
|
||||
SE_FILE_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION |
|
||||
GROUP_SECURITY_INFORMATION |
|
||||
DACL_SECURITY_INFORMATION,
|
||||
&owner,
|
||||
&group,
|
||||
&dacl,
|
||||
nullptr,
|
||||
&sd);
|
||||
auto ret = GetNamedSecurityInfoA(path.c_str(),
|
||||
SE_FILE_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION |
|
||||
GROUP_SECURITY_INFORMATION |
|
||||
DACL_SECURITY_INFORMATION,
|
||||
&owner,
|
||||
nullptr,
|
||||
&dacl,
|
||||
nullptr,
|
||||
&sd);
|
||||
|
||||
if (ret != ERROR_SUCCESS) {
|
||||
return false;
|
||||
@ -1052,18 +1098,18 @@ bool platformChmod(const std::string& path, mode_t perms) {
|
||||
|
||||
SecurityDescriptor sd_wrapper(sd);
|
||||
|
||||
if (owner == nullptr || group == nullptr || dacl == nullptr) {
|
||||
if (owner == nullptr || dacl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD sid_size = SECURITY_MAX_SID_SIZE;
|
||||
unsigned long sid_size = SECURITY_MAX_SID_SIZE;
|
||||
std::vector<char> world_buf(sid_size);
|
||||
PSID world = (PSID)world_buf.data();
|
||||
|
||||
if (!::CreateWellKnownSid(WinWorldSid, nullptr, world, &sid_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Modify the 'user' permissions
|
||||
PACL acl = nullptr;
|
||||
AclObject acl_buffer = modifyAcl(dacl,
|
||||
owner,
|
||||
@ -1072,47 +1118,73 @@ bool platformChmod(const std::string& path, mode_t perms) {
|
||||
(perms & S_IXUSR) == S_IXUSR,
|
||||
true);
|
||||
acl = reinterpret_cast<PACL>(acl_buffer.get());
|
||||
|
||||
if (acl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
acl_buffer = modifyAcl(acl,
|
||||
group,
|
||||
(perms & S_IRGRP) == S_IRGRP,
|
||||
(perms & S_IWGRP) == S_IWGRP,
|
||||
(perms & S_IXGRP) == S_IXGRP);
|
||||
acl = reinterpret_cast<PACL>(acl_buffer.get());
|
||||
// Modify the 'group' permissions
|
||||
PVOID void_ent = nullptr;
|
||||
for (unsigned long i = 0; i < dacl->AceCount; i++) {
|
||||
if (!GetAce(dacl, i, &void_ent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (acl == nullptr) {
|
||||
return false;
|
||||
auto ace = static_cast<PACE_HEADER>(void_ent);
|
||||
PSID gsid = nullptr;
|
||||
if (ace->AceType == ACCESS_ALLOWED_ACE_TYPE) {
|
||||
auto allowed_ace = reinterpret_cast<PACCESS_ALLOWED_ACE>(ace);
|
||||
gsid = &allowed_ace->SidStart;
|
||||
}
|
||||
if (ace->AceType == ACCESS_DENIED_ACE_TYPE) {
|
||||
auto denied_ace = reinterpret_cast<PACCESS_DENIED_ACE>(ace);
|
||||
gsid = &denied_ace->SidStart;
|
||||
}
|
||||
|
||||
// We only modify allow or deny ACEs
|
||||
if (gsid == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We process the user and other permissions above and below
|
||||
if (EqualSid(gsid, owner) || EqualSid(gsid, world)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
acl_buffer = modifyAcl(acl,
|
||||
gsid,
|
||||
(perms & S_IRGRP) == S_IRGRP,
|
||||
(perms & S_IWGRP) == S_IWGRP,
|
||||
(perms & S_IXGRP) == S_IXGRP);
|
||||
acl = reinterpret_cast<PACL>(acl_buffer.get());
|
||||
if (acl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Modify the 'other' permissions
|
||||
acl_buffer = modifyAcl(acl,
|
||||
world,
|
||||
(perms & S_IROTH) == S_IROTH,
|
||||
(perms & S_IWOTH) == S_IWOTH,
|
||||
(perms & S_IXOTH) == S_IXOTH);
|
||||
acl = reinterpret_cast<PACL>(acl_buffer.get());
|
||||
|
||||
if (acl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lastly, apply the permissions to the object
|
||||
// SetNamedSecurityInfoA takes a mutable string for the path parameter
|
||||
std::vector<char> mutable_path(path.begin(), path.end());
|
||||
mutable_path.push_back('\0');
|
||||
|
||||
if (::SetNamedSecurityInfoA(mutable_path.data(),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr,
|
||||
nullptr,
|
||||
acl,
|
||||
nullptr) != ERROR_SUCCESS) {
|
||||
if (SetNamedSecurityInfoA(mutable_path.data(),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr,
|
||||
nullptr,
|
||||
acl,
|
||||
nullptr) != ERROR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1330,14 +1402,13 @@ Status platformIsTmpDir(const fs::path& dir) {
|
||||
if (!dirPathsAreEqual(dir, fs::temp_directory_path(ec))) {
|
||||
return Status(1, "Not temp directory");
|
||||
}
|
||||
|
||||
return Status(0, "OK");
|
||||
return Status();
|
||||
}
|
||||
|
||||
Status platformIsFileAccessible(const fs::path& path) {
|
||||
boost::system::error_code ec;
|
||||
if (fs::is_regular_file(path, ec) && ec.value() == errc::success) {
|
||||
return Status(0, "OK");
|
||||
return Status();
|
||||
}
|
||||
return Status(1, "Not accessible file");
|
||||
}
|
||||
|
@ -456,7 +456,7 @@ void createMockFileStructure() {
|
||||
fs::create_directories(kFakeDirectory + "/deep11/deep2/deep3/");
|
||||
fs::create_directories(kFakeDirectory + "/deep1/deep2/");
|
||||
writeTextFile(kFakeDirectory + "/root.txt", "root");
|
||||
writeTextFile(kFakeDirectory + "/door.txt", "toor");
|
||||
writeTextFile(kFakeDirectory + "/door.txt", "toor", 0550);
|
||||
writeTextFile(kFakeDirectory + "/roto.txt", "roto");
|
||||
writeTextFile(kFakeDirectory + "/deep1/level1.txt", "l1");
|
||||
writeTextFile(kFakeDirectory + "/deep11/not_bash", "l1");
|
||||
|
@ -68,7 +68,7 @@ Get-ChocolateyUnzip -FileFullPath $packagePath -Destination $targetFolder
|
||||
# In order to run osqueryd as a service, we need to have a folder that has a
|
||||
# Deny Write ACL to everyone.
|
||||
Move-Item -Force -Path $targetDaemonBin -Destination $destDaemonBin
|
||||
Set-DenyWriteAcl $daemonFolder 'Add'
|
||||
Set-SafePermissions $daemonFolder
|
||||
|
||||
if ($installService) {
|
||||
if (-not (Get-Service $serviceName -ErrorAction SilentlyContinue)) {
|
||||
@ -95,4 +95,4 @@ if (-not ($oldPath -imatch [regex]::escape($targetFolder))) {
|
||||
$newPath = $newPath + ';' + $targetFolder
|
||||
}
|
||||
[System.Environment]::SetEnvironmentVariable('Path', $newPath, 'Machine')
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,7 @@
|
||||
# LICENSE file in the root directory of this source tree. An additional grant
|
||||
# of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
# Helper function to toggle the Deny-Write ACL placed on the
|
||||
# osqueryd parent folder for 'safe' execution on Windows.
|
||||
# Helper function to add an explicit Deny-Write ACE for the Everyone group
|
||||
function Set-DenyWriteAcl {
|
||||
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")]
|
||||
[OutputType('System.Boolean')]
|
||||
@ -25,7 +24,7 @@ function Set-DenyWriteAcl {
|
||||
$permType = [System.Security.AccessControl.AccessControlType]::Deny
|
||||
|
||||
$worldSIDObj = New-Object System.Security.Principal.SecurityIdentifier ('S-1-1-0')
|
||||
$worldUser = $worldSIDObj.Translate( [System.Security.Principal.NTAccount])
|
||||
$worldUser = $worldSIDObj.Translate([System.Security.Principal.NTAccount])
|
||||
$permission = $worldUser.Value, "write", $inheritanceFlag, $propagationFlag, $permType
|
||||
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
|
||||
# We only support adding or removing the ACL
|
||||
@ -34,7 +33,69 @@ function Set-DenyWriteAcl {
|
||||
} else {
|
||||
$acl.RemoveAccessRule($accessRule)
|
||||
}
|
||||
$acl | Set-Acl $targetDir
|
||||
Set-Acl $targetDir $acl
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
# A helper function to set "safe" permissions for osquery binaries
|
||||
function Set-SafePermissions {
|
||||
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")]
|
||||
[OutputType('System.Boolean')]
|
||||
param(
|
||||
[string] $target = ''
|
||||
)
|
||||
if ($PSCmdlet.ShouldProcess($target)) {
|
||||
$acl = Get-Acl $target
|
||||
|
||||
# First, to ensure success, we remove the entirety of the ACL
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
foreach ($access in $acl.Access) {
|
||||
$acl.RemoveAccessRule($access)
|
||||
}
|
||||
Set-Acl $target $acl
|
||||
|
||||
$acl = Get-Acl $target
|
||||
$inheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
|
||||
$propagationFlag = [System.Security.AccessControl.PropagationFlags]::None
|
||||
$permType = [System.Security.AccessControl.AccessControlType]::Allow
|
||||
|
||||
# "Safe" permissions in osquery entail the containing folder and binary both
|
||||
# are owned by the Administrators group, as well as no account has Write
|
||||
# permissions except for the Administrators group and SYSTEM account
|
||||
$systemSid = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
|
||||
$systemUser = $systemSid.Translate([System.Security.Principal.NTAccount])
|
||||
|
||||
$adminsSid = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
|
||||
$adminsGroup = $adminsSid.Translate([System.Security.Principal.NTAccount])
|
||||
|
||||
$usersSid = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-545')
|
||||
$usersGroup = $usersSid.Translate([System.Security.Principal.NTAccount])
|
||||
|
||||
$permGroups = @($systemUser, $adminsGroup, $usersGroup)
|
||||
foreach ($accnt in $permGroups) {
|
||||
$grantedPerm = ''
|
||||
if ($accnt -eq $usersGroup) {
|
||||
$grantedPerm = 'ReadAndExecute'
|
||||
} else {
|
||||
$grantedPerm = 'FullControl'
|
||||
}
|
||||
$permission = $accnt.Value, $grantedPerm, $inheritanceFlag, $propagationFlag, $permType
|
||||
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
|
||||
$acl.SetAccessRule($accessRule)
|
||||
}
|
||||
$acl.SetOwner($adminsGroup)
|
||||
Set-Acl $target $acl
|
||||
|
||||
# Finally set the Administrators group as the owner for all items
|
||||
$items = Get-ChildItem -Recurse -Path $target
|
||||
foreach ($item in $items) {
|
||||
$acl = Get-Acl -Path $item.FullName
|
||||
$acl.SetOwner($adminsGroup)
|
||||
Set-Acl $item.FullName $acl
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
|
Loading…
Reference in New Issue
Block a user