I have written a short demonstration program below. It does the following:
The attempt to write the DACL fails with an access denied error. Why is this when the user has the correct permission to write the DAC?
Demo code. Run as admin and the 3 parameters needed are the service name, the username and the password:
#include <windows.h>
#include <aclapi.h>
#include <iostream>
#include <sddl.h>
using namespace std;
DWORD restrictedAccess =
SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_INTERROGATE |
SERVICE_USER_DEFINED_CONTROL | READ_CONTROL; // The default access for the interactive user and the service user.
DWORD localSystemDefault = restrictedAccess | SERVICE_PAUSE_CONTINUE | SERVICE_START | SERVICE_STOP;
DWORD fullAccess = localSystemDefault | SERVICE_CHANGE_CONFIG | DELETE | WRITE_DAC | WRITE_OWNER; // The same as the default for built-in admin
PSID createWellKnownSid(WELL_KNOWN_SID_TYPE WellKnownSidType);
void createExplicitAccess(EXPLICIT_ACCESS *ea, PSID sid, DWORD AccessPermissions, TRUSTEE_TYPE TrusteeType);
bool addWellKnownExplicitAccess(EXPLICIT_ACCESS *ea, DWORD AccessPermissions, WELL_KNOWN_SID_TYPE WellKnownSidType);
PSID createUserSid(LPCSTR accountName);
void printServiceSd(char *name);
BOOL createDefaultAcl(PACL *ppACL);
BOOL createUserAcl(PACL *ppACL, char *username, DWORD access);
int main(int argc, char *argv[])
{
if (argc != 4){return 1;}
char *servicename = argv[1];
char *username = argv[2];
char *password = argv[3];
cout << "Service SD before change: ";
printServiceSd(servicename);
PACL pACL;
if (!createUserAcl(&pACL, username, fullAccess)){return 1;}
// Step 1: With the admin user give the target user full access to the service (including WRITE_DAC)
DWORD dwRes = SetNamedSecurityInfo(servicename, SE_SERVICE,
DACL_SECURITY_INFORMATION, nullptr, nullptr, pACL, nullptr);
if (ERROR_SUCCESS != dwRes) { return 1; }
cout << "Service SD with new perms: ";
printServiceSd(servicename);
HANDLE hToken;
// Step 2: Log on and impersonate the target user
if(!LogonUser(username, nullptr, password, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &hToken) ||
!ImpersonateLoggedOnUser(hToken)){ return 1; }
// Step 3: Attempt to write the dac. The target user has the WRITE_DAC right on the service, so it should work.
dwRes = SetNamedSecurityInfo(servicename, SE_SERVICE,
DACL_SECURITY_INFORMATION, nullptr, nullptr, pACL, nullptr);
switch(dwRes)
{
case ERROR_ACCESS_DENIED:cout << "Access denied trying to set permissions from a user with WRITE_DAC permission\n";break;
case ERROR_SUCCESS:cout << "Success\n";break;
default: cout << "Other error: " << dwRes << "\n";break;
}
RevertToSelf();
if (!createDefaultAcl(&pACL)){return 1;}
dwRes = SetNamedSecurityInfo(servicename, SE_SERVICE,
DACL_SECURITY_INFORMATION, nullptr, nullptr, pACL, nullptr);
switch(dwRes)
{
case ERROR_ACCESS_DENIED:cout << "Access denied trying to set permissions from a user with WRITE_DAC permission\n";break;
case ERROR_SUCCESS:break;
default: cout << "Other error: " << dwRes << "\n";break;
}
cout << "Service SD restored: ";
printServiceSd(servicename);
}
PSID createWellKnownSid(WELL_KNOWN_SID_TYPE WellKnownSidType)
{
DWORD sizeSid = 0;
if (!CreateWellKnownSid(WellKnownSidType, nullptr, nullptr, &sizeSid)) {
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
PSID sid = new CHAR[sizeSid];
if (CreateWellKnownSid(WellKnownSidType, nullptr, sid, &sizeSid))
{
LPSTR StringSid;
ConvertSidToStringSid(sid, &StringSid);
LocalFree(StringSid);
return sid;
}
}
}
return nullptr;
}
void createExplicitAccess(EXPLICIT_ACCESS *ea, PSID sid, DWORD AccessPermissions, TRUSTEE_TYPE TrusteeType)
{
ea->grfAccessPermissions = AccessPermissions;
ea->grfAccessMode = SET_ACCESS;
ea->Trustee.pMultipleTrustee = nullptr;
ea->Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
ea->Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea->Trustee.TrusteeType = TrusteeType;
ea->Trustee.ptstrName = static_cast<LPCH>(sid);
}
bool addWellKnownExplicitAccess(EXPLICIT_ACCESS *ea, DWORD AccessPermissions, WELL_KNOWN_SID_TYPE WellKnownSidType)
{
auto sid = createWellKnownSid(WellKnownSidType);
if (sid)
{
createExplicitAccess(ea, sid, AccessPermissions, TRUSTEE_IS_GROUP);
return true;
}
return false;
}
PSID createUserSid(LPCSTR accountName) {
SID_NAME_USE snu;
DWORD cbSid = 0;
DWORD cchRD = 0;
PSID sid = nullptr;
BOOL success = LookupAccountName(nullptr, accountName, nullptr, &cbSid, nullptr, &cchRD, &snu);
if (!success) {
sid = new CHAR[cbSid];
CHAR *rd = new CHAR[cchRD];
success = LookupAccountName(nullptr, accountName, sid, &cbSid, rd, &cchRD, &snu);
if (!success)
{
return nullptr;
}
LPSTR StringSid;
ConvertSidToStringSid(sid, &StringSid);
LocalFree(StringSid);
}
return sid;
}
void printServiceSd(char *name)
{
PSID psidOwner;
PSID psidGroup;
PACL pDacl;
PSECURITY_DESCRIPTOR pSecurityDescriptor;
DWORD result = GetNamedSecurityInfo(name, SE_SERVICE, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, &psidOwner,
&psidGroup, &pDacl, nullptr, &pSecurityDescriptor );
if (result == ERROR_SUCCESS) {
LPSTR StringSecurityDescriptor;
ULONG StringSecurityDescriptorLen;
SECURITY_INFORMATION SecurityInformation = DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION;
auto success = ConvertSecurityDescriptorToStringSecurityDescriptor(pSecurityDescriptor, SDDL_REVISION_1, SecurityInformation,
&StringSecurityDescriptor, &StringSecurityDescriptorLen);
if (success)
{
cout << StringSecurityDescriptor << "\n";
LocalFree(pSecurityDescriptor);
}
}
}
BOOL createUserAcl(PACL *ppACL, char *username, DWORD access)
{
EXPLICIT_ACCESS ea_list[5];
ZeroMemory(ea_list, 5 * sizeof(EXPLICIT_ACCESS));
addWellKnownExplicitAccess(&(ea_list[0]), localSystemDefault, WinLocalSystemSid);
addWellKnownExplicitAccess(&(ea_list[1]), fullAccess, WinBuiltinAdministratorsSid);
addWellKnownExplicitAccess(&(ea_list[2]), restrictedAccess, WinInteractiveSid);
addWellKnownExplicitAccess(&(ea_list[3]), restrictedAccess, WinServiceSid);
ea_list[4].grfAccessPermissions = access;
ea_list[4].grfAccessMode = SET_ACCESS;
ea_list[4].Trustee.pMultipleTrustee = nullptr;
ea_list[4].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
ea_list[4].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea_list[4].Trustee.TrusteeType = TRUSTEE_IS_USER;
ea_list[4].Trustee.ptstrName = LPCH(createUserSid(username));
DWORD dwRes = SetEntriesInAcl(5, ea_list, nullptr, ppACL);
switch(dwRes)
{
case ERROR_ACCESS_DENIED:cout << "Access denied trying to set permissions from a user with WRITE_DAC permission\n";break;
case ERROR_SUCCESS:break;
default: cout << "Other error: " << dwRes << "\n";break;
}
return ERROR_SUCCESS == dwRes;
}
BOOL createDefaultAcl(PACL *ppACL)
{
EXPLICIT_ACCESS ea_list[4];
ZeroMemory(ea_list, 4 * sizeof(EXPLICIT_ACCESS));
addWellKnownExplicitAccess(&(ea_list[0]), localSystemDefault, WinLocalSystemSid);
addWellKnownExplicitAccess(&(ea_list[1]), fullAccess, WinBuiltinAdministratorsSid);
addWellKnownExplicitAccess(&(ea_list[2]), restrictedAccess, WinInteractiveSid);
addWellKnownExplicitAccess(&(ea_list[3]), restrictedAccess, WinServiceSid);
DWORD dwRes = SetEntriesInAcl(4, ea_list, nullptr, ppACL);
switch(dwRes)
{
case ERROR_ACCESS_DENIED:cout << "Access denied trying to set permissions from a user with WRITE_DAC permission\n";break;
case ERROR_SUCCESS:break;
default: cout << "Other error: " << dwRes << "\n";break;
}
return ERROR_SUCCESS == dwRes;
}
In the output (shown below) you can see that the user has all permissions but when it attempts to write the DACL using SetNamedSecurityInfo and DACL_SECURITY_INFORMATION, it gets an access denied error:
mrs.exe AAA simple_user password
Service SD before change: O:SYG:SYD:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)
Service SD with new perms: O:SYG:SYD:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-21-894880353-1955504986-2368147855-1004)
Access denied trying to set permissions from a user with WRITE_DAC permission
Service SD restored: O:SYG:SYD:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)
I have tried reordering the entries in the DACL to put the one for the target user first. This did not change the result.
I tried adding the user to the administrator group. This did work but I would like to be able to give the user permission to change one service and on service only.
I have tried doing something similar with the the command line (I am logged in as the target user S-1-5-21-1730272245-1440079960-1637230185-1001):
sc sdshow AAA
D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-21-1730272245-1440079960-1637230185-1001)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWLOCRRC;;;SY)
sc sdset AAA "D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-21-1730272245-1440079960-1637230185-1001)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWLOCRRC;;;SY)"
[SC] OpenSCManager FAILED 5:
Access is denied.
this is bug in SetNamedSecurityInfoW
implementation.
we need next code
if (SC_HANDLE hSCManager = OpenSCManagerW(0, 0, 0))
{
SC_HANDLE hService = OpenServiceW(hSCManager, servicename, WRITE_DAC);
CloseServiceHandle(hSCManager);
if (hService)
{
SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, psd);
CloseServiceHandle(hService);
}
}
but SetNamedSecurityInfoW
by mistake call OpenSCManagerW(0, 0, WRITE_DAC)
and because your user have no WRITE_DAC
on SCM itself - code is fail.