I wrote a windows service (and it runs fine). Now i have a separate app where I want to start this service from, but it seems this is not possible without administrator rights.
How would a proper solution look like that a user can start/stop the service (e.g. from a tray or application)
IMHO its bad that the application must always be started with administrator rights.
You just need to change the permissions on the service object, preferably at the same time you install it.
wchar_t sddl[] = L"D:"
L"(A;;CCLCSWRPWPDTLOCRRC;;;SY)" // default permissions for local system
L"(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)" // default permissions for administrators
L"(A;;CCLCSWLOCRRC;;;AU)" // default permissions for authenticated users
L"(A;;CCLCSWRPWPDTLOCRRC;;;PU)" // default permissions for power users
L"(A;;RP;;;IU)" // added permission: start service for interactive users
;
PSECURITY_DESCRIPTOR sd;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL))
{
fail();
}
if (!SetServiceObjectSecurity(service, DACL_SECURITY_INFORMATION, sd))
{
fail();
}
I'm assuming here you've already opened the service handle. You need WRITE_DAC permission.
If you also want non-admin users to be able to stop the service, add the WP right, i.e.,
L"(A;;RPWP;;;IU)"
// added permissions: start service, stop service for interactive users
SDDL codes for service rights can be found in Wayne Martin's blog entry, Service Control Manager Security for non-admins.
#Harry Johnston, in addition to response.
Here is c++ builder example.
void __fastcall TService1::ServiceAfterInstall(TService *Sender)
{
wchar_t lpBuffer[256];
long errorCode;
SC_HANDLE hSCManager,hService;
hSCManager = OpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (hSCManager == NULL)
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("OpenSCManager Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
return;
}
hService = OpenService(hSCManager, this->Name.c_str(), READ_CONTROL | WRITE_DAC);
if (hService == NULL)
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("OpenService Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
CloseServiceHandle(hSCManager);
}
wchar_t sddl[] = L"D:"
L"(A;;CCLCSWRPWPDTLOCRRC;;;SY)" // default permissions for local system
L"(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)" // default permissions for administrators
L"(A;;CCLCSWLOCRRC;;;AU)" // default permissions for authenticated users
L"(A;;CCLCSWRPWPDTLOCRRC;;;PU)" // default permissions for power users
L"(A;;RP;;;IU)" // added permission: start service for interactive users
;
PSECURITY_DESCRIPTOR sd;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(AnsiString(sddl).c_str(), SDDL_REVISION_1, &sd, NULL))
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("ConvertStringSecurityDescriptorToSecurityDescriptor Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
}
if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, sd))
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("SetServiceObjectSecurity Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
}
Starting a service programmatically is done with the StartService function. There is a comprehensive usage example also given under the title starting a service, which also shows how to:
detect that the service is for some reason shutting down
wait until the service is in a stable state (started/stopped)
start the service programmatically
As for administrator rights, this is necessary because if just about any application could shut services down (or, more importantly, install and start new services) there would be very real and very serious security issues.
#Harry Johnston 's worked fine for me, in case someone wants to do this in C#:
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool SetServiceObjectSecurity(SafeHandle serviceHandle,
UInt32 secInfos,
IntPtr lpSecDesrBuf);
[DllImport("advapi32.dll", EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean ConvertStringSecurityDescriptorToSecurityDescriptor(
[MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
UInt32 sDRevision,
ref IntPtr securityDescriptor,
ref UInt32 securityDescriptorSize);
public static void SetServicePermissions(string service)
{
System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController(service);
bool ok;
IntPtr pSD = IntPtr.Zero;
uint securityDescriptorSize = 0;
string secDesc = "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)(A;;RPWP;;;IU)";
ok = ConvertStringSecurityDescriptorToSecurityDescriptor(secDesc, 1, ref pSD, ref securityDescriptorSize);
if (!ok)
{
throw new ApplicationException("error calling ConvertStringSecurityDescriptorToSecurityDescriptor(): error code=" + Marshal.GetLastWin32Error());
}
ok = SetServiceObjectSecurity(sc.ServiceHandle, 4 , pSD);
if (!ok)
{
throw new ApplicationException("error calling SetServiceObjectSecurity(); error code=" + Marshal.GetLastWin32Error());
}
}
Related
How do I add my code after click submit button and check user password?
I add my code in GetSerialization function after successfully calling KerbInteractiveUnlockLogonPack and RetrieveNegotiateAuthPackage. But in this state first run my code and later check user password.
I want to first check user password and if that correct then run my code. How do I do?
if (_fIsLocalUser)
{
PWSTR pwzProtectedPassword;
hr = ProtectIfNecessaryAndCopyPassword(_rgFieldStrings[SFI_PASSWORD], _cpus, &pwzProtectedPassword);
if (SUCCEEDED(hr))
{
PWSTR pszDomain;
PWSTR pszUsername;
hr = SplitDomainAndUsername(_pszQualifiedUserName, &pszDomain, &pszUsername);
if (SUCCEEDED(hr))
{
KERB_INTERACTIVE_UNLOCK_LOGON kiul;
hr = KerbInteractiveUnlockLogonInit(pszDomain, pszUsername, pwzProtectedPassword, _cpus, &kiul);
if (SUCCEEDED(hr))
{
// We use KERB_INTERACTIVE_UNLOCK_LOGON in both unlock and logon scenarios. It contains a
// KERB_INTERACTIVE_LOGON to hold the creds plus a LUID that is filled in for us by Winlogon
// as necessary.
hr = KerbInteractiveUnlockLogonPack(kiul, &pcpcs->rgbSerialization, &pcpcs->cbSerialization);
if (SUCCEEDED(hr))
{
ULONG ulAuthPackage;
hr = RetrieveNegotiateAuthPackage(&ulAuthPackage);
if (SUCCEEDED(hr))
{
pcpcs->ulAuthenticationPackage = ulAuthPackage;
pcpcs->clsidCredentialProvider = CLSID_CSample;
// At this point the credential has created the serialized credential used for logon
// By setting this to CPGSR_RETURN_CREDENTIAL_FINISHED we are letting logonUI know
// that we have all the information we need and it should attempt to submit the
// serialized credential.
*pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;
}
}
}
CoTaskMemFree(pszDomain);
CoTaskMemFree(pszUsername);
}
CoTaskMemFree(pwzProtectedPassword);
}
}
I have found the following line in my code to check username/password pair:
bRet = LogonUserExA(lpszUsername, NULL, lpszPassword, LOGON32_LOGON_NETWORK,
LOGON32_PROVIDER_DEFAULT, NULL, NULL, NULL, NULL, NULL);
lpszUsername us the full UPN in one of forms domain\user or user#domain.
The key constant is LOGON32_LOGON_NETWORK:
This logon type is intended for high performance servers to
authenticate plaintext passwords. The LogonUserEx function does not
cache credentials for this logon type.
See MS Docs for details on parameters and constant values and also check the remarks section.
In your case it can looks like this:
if(LogonUserEx(_pszQualifiedUserName, NULL, _rgFieldStrings[SFI_PASSWORD],
LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, NULL, NULL, NULL, NULL, NULL))
{
...
}
I am trying to create a Windows Service that is able to log on my user programatically into my local windows 10 pc the same as if i was typing directly in front of my keyboard. (I want to be able to initialize my session remotely)
I am using LogonUser and it returns properly but i will never see the user to appear as loged in in the Task Manager for example.
This is what i have tried now:
BSINFO(L"Login User...");
HANDLE sessToken = nullptr;
BOOL bRes = LogonUserW(
L"TestUser",
L".",
L"1234",
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&sessToken);
if (bRes)
{
PROFILEINFO profInfo;
ZeroMemory(&profInfo,sizeof(PROFILEINFO));
profInfo.dwSize = sizeof(PROFILEINFO);
profInfo.lpUserName = L"TestUser";
profInfo.lpServerName = L"";
bRes = LoadUserProfileW(sessToken,&profInfo);
if (!bRes)
{
DWORD dwError = GetLastError();
BSERROR(L"Error LoadUserProfileW. %d '%ls'", dwError, GetLastErrorW(dwError).c_str());
}
}
else
{
DWORD dwError = GetLastError();
BSERROR(L"Error login user. %d '%ls'", dwError, GetLastErrorW(dwError).c_str());
}
Is it something possible to do?
Are there any alternatives to LsaLogonUser for impersonating given account in order to access network resources? I'm looking for the method of impersonation which would let me call a remote machine via UNC in the same domain.
For initial data I have: domain\username.
I have no password, because this I am using LsaRegisterLogonProcess and LsaLogonUser with Kerberos package. Locally everything is working and I can impersonate and call applications using a different username, however, when trying to access a remote machine using.. \\remotemachine\shared\somefile.bat I have Access Denied.
Basically I call cmd.exe as command.. and then a Console opens with a new user impersonated, but if a Try to call the UNC path, Access Denied.
If I open a basic console with my own user, I execute this UNC path successfully. Is not Folder permissions because is a public Share.
This is a part of my working code:
...
NTSTATUS Status = STATUS_SUCCESS;
LSA_OPERATIONAL_MODE unused;
LSA_STRING lsaString;
lsaString.Buffer = "User Impersonated LogonProcess";
lsaString.Length = strlen(lsaString.Buffer);
lsaString.MaximumLength = lsaString.Length + 1;
Status = LsaRegisterLogonProcess(&lsaString, &lsa, &unused);
if (Status != STATUS_SUCCESS) {
printf("LsaRegisterLogonProcess failed: %x\n", Status);
}
...
NTSTATUS status = LsaLogonUser(
lsa,
&origin,
Network,
packageId,
authInfo,
authInfoSize,
0,
&source,
&profileBuffer,
&profileBufLen,
&luid,
&token,
&qlimits,
&subStatus
);
if (status != ERROR_SUCCESS)
{
ULONG err = LsaNtStatusToWinError(status);
printf("LsaLogonUser failed: %x\n", status);
return 1;
}
...
DuplicateTokenEx(token, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &tokenDuplicate);
if (CreateProcessWithTokenW(
tokenDuplicate,
LOGON_WITH_PROFILE,
command,
command_arguments,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi))
{
pi.hThread = NULL;
pi.hProcess = NULL;
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
I wrote a windows service (and it runs fine). Now i have a separate app where I want to start this service from, but it seems this is not possible without administrator rights.
How would a proper solution look like that a user can start/stop the service (e.g. from a tray or application)
IMHO its bad that the application must always be started with administrator rights.
You just need to change the permissions on the service object, preferably at the same time you install it.
wchar_t sddl[] = L"D:"
L"(A;;CCLCSWRPWPDTLOCRRC;;;SY)" // default permissions for local system
L"(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)" // default permissions for administrators
L"(A;;CCLCSWLOCRRC;;;AU)" // default permissions for authenticated users
L"(A;;CCLCSWRPWPDTLOCRRC;;;PU)" // default permissions for power users
L"(A;;RP;;;IU)" // added permission: start service for interactive users
;
PSECURITY_DESCRIPTOR sd;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL))
{
fail();
}
if (!SetServiceObjectSecurity(service, DACL_SECURITY_INFORMATION, sd))
{
fail();
}
I'm assuming here you've already opened the service handle. You need WRITE_DAC permission.
If you also want non-admin users to be able to stop the service, add the WP right, i.e.,
L"(A;;RPWP;;;IU)"
// added permissions: start service, stop service for interactive users
SDDL codes for service rights can be found in Wayne Martin's blog entry, Service Control Manager Security for non-admins.
#Harry Johnston, in addition to response.
Here is c++ builder example.
void __fastcall TService1::ServiceAfterInstall(TService *Sender)
{
wchar_t lpBuffer[256];
long errorCode;
SC_HANDLE hSCManager,hService;
hSCManager = OpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (hSCManager == NULL)
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("OpenSCManager Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
return;
}
hService = OpenService(hSCManager, this->Name.c_str(), READ_CONTROL | WRITE_DAC);
if (hService == NULL)
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("OpenService Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
CloseServiceHandle(hSCManager);
}
wchar_t sddl[] = L"D:"
L"(A;;CCLCSWRPWPDTLOCRRC;;;SY)" // default permissions for local system
L"(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)" // default permissions for administrators
L"(A;;CCLCSWLOCRRC;;;AU)" // default permissions for authenticated users
L"(A;;CCLCSWRPWPDTLOCRRC;;;PU)" // default permissions for power users
L"(A;;RP;;;IU)" // added permission: start service for interactive users
;
PSECURITY_DESCRIPTOR sd;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(AnsiString(sddl).c_str(), SDDL_REVISION_1, &sd, NULL))
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("ConvertStringSecurityDescriptorToSecurityDescriptor Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
}
if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, sd))
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("SetServiceObjectSecurity Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
}
Starting a service programmatically is done with the StartService function. There is a comprehensive usage example also given under the title starting a service, which also shows how to:
detect that the service is for some reason shutting down
wait until the service is in a stable state (started/stopped)
start the service programmatically
As for administrator rights, this is necessary because if just about any application could shut services down (or, more importantly, install and start new services) there would be very real and very serious security issues.
#Harry Johnston 's worked fine for me, in case someone wants to do this in C#:
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool SetServiceObjectSecurity(SafeHandle serviceHandle,
UInt32 secInfos,
IntPtr lpSecDesrBuf);
[DllImport("advapi32.dll", EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean ConvertStringSecurityDescriptorToSecurityDescriptor(
[MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
UInt32 sDRevision,
ref IntPtr securityDescriptor,
ref UInt32 securityDescriptorSize);
public static void SetServicePermissions(string service)
{
System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController(service);
bool ok;
IntPtr pSD = IntPtr.Zero;
uint securityDescriptorSize = 0;
string secDesc = "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)(A;;RPWP;;;IU)";
ok = ConvertStringSecurityDescriptorToSecurityDescriptor(secDesc, 1, ref pSD, ref securityDescriptorSize);
if (!ok)
{
throw new ApplicationException("error calling ConvertStringSecurityDescriptorToSecurityDescriptor(): error code=" + Marshal.GetLastWin32Error());
}
ok = SetServiceObjectSecurity(sc.ServiceHandle, 4 , pSD);
if (!ok)
{
throw new ApplicationException("error calling SetServiceObjectSecurity(); error code=" + Marshal.GetLastWin32Error());
}
}
My goal is to copy privileges from one primary user token to another before I start a user mode process with the destination token. I created a sample pseudo code to illustrate what I need to accomplish.
The following will be run from a local system service:
//dwSessionId = user session ID to run process in
HANDLE hToken1 = NULL; //Source user token
WTSQueryUserToken(dwSessionId, &hToken1);
HANDLE hSelfToken = NULL; //User token for system service
HANDLE hToken2 = NULL; //Adjusted self-token
OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
NULL, SecurityIdentification, TokenPrimary, &hToken2);
//Specify user session to run in
SetTokenInformation(hToken2, TokenSessionId, &dwSessionId, sizeof(dwSessionId));
//Now I need to set privileges in 'hToken2' as they are in 'hToken1'
...
//Then use 'hToken2' in CreateProcessAsUser() to start a process
Any idea how to copy privileges from hToken1 to hToken2?
Use the following code to obtain hToken2 from hToken, and then use it in CreateProcessAsUser. There is no need to use hSelfToken, actually.
HANDLE GetAdjustedToken(HANDLE hSrcToken)
{
TOKEN_LINKED_TOKEN admin = {};
HANDLE hTarToken = 0;
DWORD dw = 0;
if (GetTokenInformation(hSrcToken, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, &admin, sizeof(TOKEN_LINKED_TOKEN), &dw))
{
hTarToken = admin.LinkedToken;
}
else
{
DuplicateTokenEx(hSrcToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTarToken);
}
return hTarToken;
}
And if you want to create the new process at a low mandatory integrity level, see this MSDN article.
First you have to call GetTokenInformation using TokenPrivileges class for your original token to obtain TOKEN_PRIVILEGES structure. Then call AdjustTokenPrivileges with it to update your cloned token. You will have to call GetTokenInformation twice - first one to obtain buffer length and second - to get actual data.
HANDLE hSelfToken = NULL; //User token for system service
HANDLE hToken2 = NULL; //Adjusted self-token
OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
NULL, SecurityIdentification, TokenPrimary, &hToken2);
PTOKEN_PRIVILEGES pPriv = NULL;
DWORD dwLen = 0;
GetTokenInformation(hSelfToken, TokenPrivileges, (LPVOID)pPriv, 0, &dwLen);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return(0);
pPriv = (PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLen);
if (!GetTokenInformation(hSelfToken, TokenPrivileges, (LPVOID)pPriv, dwLen, &dwLen))
return(0);
AdjustTokenPrivileges(hToken2, FALSE, pPriv, dwLen, NULL, NULL);
OK. I might have gotten something close to what I needed. It's not really copying privileges, but for the purpose of "stripping out" privileges I can create a restricted token that will have almost all privileges removed (except SeChangeNotifyPrivilege and SeSystemtimePrivilege.)
//Call the following instead of DuplicateTokenEx() in my example above
CreateRestrictedToken(hSelfToken, DISABLE_MAX_PRIVILEGE,
0, NULL, 0, NULL, 0, NULL,
&hToken2);
FYI: Just a curious fact. For some reason I was not able to remove the SeSystemtimePrivilege from the token. Not with this method, nor using AdjustTokenPrivileges with its attribute set to SE_PRIVILEGE_REMOVED. All other privileges could be removed just fine, except this one. So if anyone have any idea why, I'd be glad to learn it?