QueryServiceObjectSecurity fails with access denied error - windows

QueryServiceObjectSecurity call is failing with access denied error, i am not quite able to figure out why. I create service and then try to update the permissions for it. Interestingly once the call fails service is created and if i reexecute code, it detects existing service and attaches handle and then this call works fine, then why it fails for the first time? I am new to windows services, is there like during first time execution, service is created but SCM db is not updated before i query object security?
Code snippet is below
Service creation:
managerHandle.Attach(::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS));
serviceHandle.Attach(::CreateService(managerHandle, serviceName, serviceDisplayName,
SERVICE_CHANGE_CONFIG | SERVICE_START | SERVICE_QUERY_STATUS | SERVICE_INTERROGATE,
serviceType, startCode, errorControl, path,
groupName, NULL, NULL, NULL, NULL));
::ChangeServiceConfig2(m_serviceHandle, SERVICE_CONFIG_DESCRIPTION, &serviceDesc);
service.Detach();
now after this i call function which updates the dacl for the service
ENSURE_STATE(!!m_serviceHandle)
CAutoPtr<PSECURITY_DESCRIPTOR *> pSecurityDescriptor;
DWORD bytesNeeded = 0;
if(::QueryServiceObjectSecurity(serviceHandle, DACL_SECURITY_INFORMATION, &pSecurityDescriptor, 0, &bytesNeeded) == FALSE)
{
Any help greatly appreciated

The access rights for system services are described in the MSDN article Service Security and Access Rights.
The relevant right is
READ_CONTROL Required to call the QueryServiceObjectSecurity function to query the security descriptor of the service object.
The call to CreateService returns a handle with the access rights indicated by the dwDesiredAccess parameter, which you've set to
SERVICE_CHANGE_CONFIG |
SERVICE_START |
SERVICE_QUERY_STATUS |
SERVICE_INTERROGATE
That gives the handle the right to change the service configuration, to start the service, query the service's status and interrogate the service - but not the right to query the security descriptor.
Add READ_CONTROL to dwDesiredAccess and the problem will go away. Better still, set dwDesiredAccess to SERVICE_ALL_ACCESS.

The documentation for Service Security and Access Rights explains that the access right READ_CONTROL is required to call QueryServiceObjectSecurity.
In the call to CreateService add READ_CONTROL to the list of access rights you request for the handle.

Related

MAPI_E_FAILONEPROVIDER error is thrown when the user is logged off and Outlook 2016/2019 is accessed in a windows application using MAPI

In a Windows application, calling OpenMsgStore function is throwing error when I am trying to open the default primary store for 'Outlook' 2016 Outlook profile only when the user is logged off. When the user is logged in, all the MAPI functions work successfully.
HRESULT hr = MAPILogonEx(0,
const_cast<LPTSTR>((LPCTSTR)m_strProfileTemplate), NULL,
MAPI_EXTENDED | MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_UNICODE,
&m_pSession);
SRestriction sres;
SPropValue spv;
LPSRowSet pStores = NULL;
hr = m_pSession->GetMsgStoresTable(0, &pStoresTable);
sres.rt = RES_PROPERTY;
sres.res.resProperty.relop = RELOP_EQ;
sres.res.resProperty.ulPropTag = PR_DEFAULT_STORE;
sres.res.resProperty.lpProp = &spv;
spv.ulPropTag = PR_DEFAULT_STORE;
spv.Value.b = TRUE;
hr = HrQueryAllRows(pStoresTable,(LPSPropTagArray) &sptCols,&sres,NULL,0,&pStores);
ULONG flags = MDB_WRITE| MDB_NO_MAIL;
hr = m_pSession->OpenMsgStore(NULL,pStores->aRow[0].lpProps[0].Value.bin.cb,
(LPENTRYID)pStores->aRow[0].lpProps[0].Value.bin.lpb,
NULL,
flags | MDB_ONLINE,
&pPrimaryMessageStore);
The call for OpenMsgStore fails with the MAPI_E_FAILONEPROVIDER error when the user is logged off from the system and the service executes the above code.
However this function is successful when user is logged in to system.
How does the MAPI work if the user, trying to access outlook as a scheduled service is logged off from the system?
Try including the MAPI_NT_SERVICE flag to the MAPILogonEx call.
That means the caller is running as a Windows service. Callers that are not running as a Windows service should not set this flag; callers that are running as a service must set this flag.
AFAIK if OAuth is used, MAPI system cannot find the cached credentials and opening the message store fails.

Access violation on Server 2012R2 during UAC with custom credential provider

I have a custom credential provider and the credential implements IConnectableCredentialProviderCredential. It is written using Visual C++ 2019 and ATL. It works on Server 2016/2019 and Windows 10/11, if I use it on Server 2012R2, then the UI crashes during UAC--trying to elevate a non-admin user to use an Administrator PowerShell session for example.
I attached a remote debugger and the debugger intercepts the exception consistently as: Exception thrown at 0x00007FFE9E627EC3 (authui.dll) in consent.exe: 0xC0000005: Access violation reading location 0x0000000000000008.
I had some logging/debugging code and I could see that the system never calls my Connect() or GetSerialization() method on the credential when it crashes. My logging showed the username and password getting set, but then after keying the Enter key or clicking on OK, the process crashes without ever asking the credential to serialize its credentials. The logging showed that the last call into my credential provider was to ICredentialProviderCredential::SetStringValue() which was corresponding to entering the password before keying the Enter key.
Since it was throwing an access violation, I assumed that I must be passing bad data back to the system when it was calling the ICredentialProvder::GetFieldDescriptorAt() or possibly the ICredentialProviderCredential::GetStringValue() or some other CredentialProvider::Get...() function. I've gone over the methods with fine tooth comb as well as all other methods and have found nothing. For the GetStringValue() implementation, I am calling SHStrDupW() like in the SDK examples, and for GetFieldDescriptorAt() I am calling the same functions as the SDK examples. In fact, I copied the SDK code.
I decided to distill my credential provider down to a minimal reproducible example. That minimal example is at https://github.com/willcoxson/CredDemo
If anyone could see where I might be passing bad data back in one of the methods, I'd sure be grateful.
IConnectableCredentialProviderCredential have sense only for CPUS_LOGONcase. so for other scenarion your QueryInterface must return E_NOINTERFACE when system ask for IConnectableCredentialProviderCredential
in your concrete case we have CPUS_CREDUI scenario. inside function
void CGetSerializationJob::Do(CREDENTIAL_PROVIDER_THREAD_JOB_CONTEXT const &) system query you for IConnectableCredentialProviderCredential ("9387928b-ac75-4bf9-8ab2-2b93c4a55290" )
and if you return this interface - run next code
system try access some object by pointer - CPLAPCallback::_pSingleton
but pointer is 0. crash exactly at yellow line ( 0xC0000005: Access violation reading location 0x0000000000000008. )

Stop Auto Logon after failure - Custom Credential Provider Windows

I've taken up Widows Samples on Credential Providers and have built one using them as a reference. I'm able to log in seamlessly, wither by giving username and password manually. I've set
CustomCredential::SetSelected (__out BOOL* pbAutoLogon) {
*pbAutoLogon = TRUE; // FALSE;
return S_OK;
}
Now, AutoLogon, when the Tile is selected, is happening seamlessly.
As a test case, I changed the password, and as expected the Login fails. After failure, an error message appears and when I click OK ( this is the only option ), the credentials are resubmitted for a retry. How do can we stop this behavior? Which method gets called after the authentication failure?
I've handled ReportResult() but that did not help.
Thanks in advance.
It's been a while - but I believe GetSerialization() is called to return serialized credentials to LogonUI. You need to implement this as well. The credential provider samples I think have working code for KERB_INTERACTIVE_UNLOCK_LOGON.
You can also change *pbAutoLogon conditionally in SetSelected() - I do this in my credential provider depending on certain results.
ReportResult() would be called after GetSerialization returns its result to LogonUI. Inside ReportResult() you could do things like clear the password box (which is done in the samples code.)
If you're not returning serialized credentials in GetSerialization then I think you might get the kind of error you listed in your original post. In the credential samples KerbInteractiveUnlockLogonPack() is called in GetSerialization() and this is what 'logs' the user in effectively.
The error seems specific - 'the user has not been granted the requested logon type' so maybe it has something to do with the rights of the user you're testing with.
If you are using remote desktop, make sure your users are members of the right groups to be able to login (https://support.jumpdesktop.com/hc/en-us/articles/216424183-General-RDP-You-must-be-granted-the-Allow-log-on-through-the-Terminal-or-Remote-Desktop-Services-Right-) or if it is a regular user that it is allowed interactive login.
Also - SetSelected() Gets called when your credential provider is clicked on - I'm not sure if it gets called after every logon attempt or not (my guess is that it doesn't.) In my credential provider I am using a custom logon dialog that I show using SetSelected().

What is the native equivalent of PrincipalContext.ValidateCredentials?

What is the native way to validate a set of user domain credentials (username, password, domain) against that domain?
In other words, i am looking for the native equivalent of:
Boolean ValidateCredentials(String username, String password, String domain)
{
// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain))
{
// validate the credentials
return pc.ValidateCredentials(username, password)
}
}
ValidateCredentials("iboyd", "Tr0ub4dor&3", "contoso");
Hasn't this been asked, and answered, to death?
No! This question is asked, a lot. Three of those times by me. But the more you dig into it, the more you realize the accepted answers are incorrect.
Microsoft managed to solve it in .NET with the PrincipalContext class added in .NET 3.5. And the PrincipalContext is nothing magical. Underneath it uses the flat C-style ldap API. But trying to reverse engineer the code from ILSpy is not working out. And referencesource notwithstanding, Microsoft still keeps large portions of the .NET Framework class library source code secret.
What have you tried?
Method 1: Just use LogonUser
I cannot use LogonUser. LogonUser works only when your machine is either on the domain you're validating (e.g. contoso) or your domain trusts the domain you're validating. In other words, if there is a domain controller out there for the contoso.test domain then:
LogonUser("iboyd", "contoso.test", "Tr0ub4dor&3",
LOGON32_LOGON_NETWORK, "Negotiate", ref token);
will fail with error:
1326 (Logon failure: unknown user name or bad password)
That's because this domain i specify is not my own domain, or a domain i trust.
C# PrincipalContext doesn't suffer from this problem.
Method 2: Just use the SSPI (Security Support Provider Interface)
The SSPI is what LogonUser uses internally. The short answer is that it fails for the same reason that LogonUser does: Windows will not trust credentials from an untrusted domain.
The code is pretty long to provide an example of. The psuedo-code jist is:
QuerySecurityPackageInfo("Negotiate");
// Prepare client message (negotiate)
AcquireCredentialsHandle(....); //for the client
InitializeSecurityContext(...); //on the returned client handle
CompleteAuthToken(...); //on the client context
// Prepare server message (challenge).
AcquireCredentialsHandle(...); //for the server
AcceptSecurityContext(...); //on the returned server handle
CompleteAuthToken(...); //on the server context
// Prepare client message (authenticate).
AcquireCredentialsHandle(....); //for the client
InitializeSecurityContext(...); //on the returned client handle
CompleteAuthToken(...); //on the client context
// Prepare server message (authentication).
AcquireCredentialsHandle(...); //for the server
AcceptSecurityContext(...); //on the returned server handle
CompleteAuthToken(...); //on the server context
This code works great if your machine is joined to the domain you're validating credentials. But as soon as you try to validate a set of domain credentails from a foreign domain: it fails.
C# PrincipalContext doesn't suffer from this problem.
Method 3: Just use LDAP's AdsGetObject
Some might suggest using AdsGetObject.
AdsGetObject("LDAP://CN=iboyd,DC=contoso,DC=test");
That's a red-herring, because AdsGetObject supports no way to pass username/password:
HRESULT ADsGetObject(
_In_ LPCWSTR lpszPathName,
_In_ REFIID riid,
_Out_ VOID **ppObject
);
Instead you will be simply asking about a user.
Perhaps you meant AdsOpenObject:
HRESULT ADsOpenObject(
_In_ LPCWSTR lpszPathName,
_In_ LPCWSTR lpszUserName,
_In_ LPCWSTR lpszPassword,
_In_ DWORD dwReserved,
_In_ REFIID riid,
_Out_ VOID **ppObject
);
where you can specify credentials to connect as.
C# PrincipalContext doesn't suffer from this problem.
Method 4: Just use AdsOpenObject
Some might suggest using AdsOpenObject:
String path = "LDAP://CN=iboyd,DC=contoso,DC=test"
AdsOpenObject(path, "iboyd", "Tr0ub4dor&3", 0, IADs, ref ads);
Setting aside the fact that the path i constructed is invalid, setting aside the fact that there is no way to construct a valid LDAP path when you only know:
{username}
{password}
{domain}
that is because
LDAP://CN={username},DC={domain}
is not a valid LDAP path for any user.
Notwithstanding the LDAP path conundrum, the fundamental issue is that trying to query LDAP. That is wrong.
We want to validate a user's AD credentials.
Any time we attempt to query an LDAP server, we will fail if the user does not have permission to query LDAP - even though their credentials are valid.
When you pass credentials to AdsOpenObject they are used to specify who you want to connect as. Once you connect, you will then perform a query against LDAP. When you don't have permission to query LDAP, the AdsOpenObject will fail.
What's even more maddening is that even if you do have permission to query for users in LDAP, you're still needlessly performing a query of LDAP - an expensive operation.
C# PrincipalContext doesn't suffer from this problem.
Method 5: Use ADO with the ADsDSOObject provider
There are many who simply use the ADsDSOObject OLEDB provider with ADO to query LDAP. This solves the issue of having to come up with the correct LDAP path - you don't have to know the LDAP path for the user
String sql =
'SELECT userAccountControl FROM "LDAP://DC=contoso,DC=test"
'WHERE objectClass="user"
'and sAMAccountName = "iboyd"';
String connectionString = "Provider=ADsDSOObject;Password=Tr0ub4dor&3;
User ID=iboyd;Encrypt Password=True;Mode=Read;
Bind Flags=0;ADSI Flag=-2147483648";
Connection conn = new ADODB.Connection();
conn.ConnectionString = connectionString;
conn.Open();
IRecordset rs = conn.Execute(sql);
That works; it solves the problem of not knowing a user's LDAP path. But it doesn't solve the issue of if you don't have permission to query AD, then it fails.
Plus there's the issue that it is querying Active Directory, when it should be validating credentials.
C# PrincipalContext doesn't suffer from this problem.
Method 6: Just use PrincipalContext.ValidateCredentials
The .NET 3.5 class PrincipalContext has that lets you validate credentials knowing only:
Username
Password
Domain Name
You don't need to know the name or IP of the AD server. You don't need to construct any LDAP paths. And most importantly, you don't need permission to query Active Directory - it just works.
I tried digging down into the source code using ILSpy, but it gets harry fast:
ValidateCredentials
CredentialValidator.Validate
BindLdap
new LdapDirectoryIdentifier
new LdapConnection
ldapConnection.SessionOptions.FastConcurrentBind();
lockedLdapBind
Bind
With a lot of presumably important code around it. There's a lot of ups, and downs, with dependency injection, and functions too little - all the normal difficulties you get with overly complicated code structure. The complexity is on par with programming the SSPI. Nobody understands SSPI code, and i already wrote code that calls it!
Note: This question doesn't ask how to validate local credentials, as opposed to local credentials. Nor does it ask how to do both. In this case i'm simply asking how to do what is already available in the .NET world but in the native world.
Unfortunately:
System.Security.DirectoryServices.AccountManagement.PrincipalContext
was not exposed though a COM-callable wrapper:
And now that i've spent two and a half hours typing in this question: it is time to go home. Lets see if i get closed between now and tomorrow morning.
What about the LogonUser functions from advapi.dll, e.g. LogonUserA:
BOOL LogonUserA(
[in] LPCSTR lpszUsername,
[in, optional] LPCSTR lpszDomain,
[in, optional] LPCSTR lpszPassword,
[in] DWORD dwLogonType,
[in] DWORD dwLogonProvider,
[out] PHANDLE phToken
);
LogonUser(L"LocalService", L"NT AUTHORITY", NULL, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &hToken)
This logs in against the AD on the local machine.

Why doesn't LogonUser(...) work for domain accounts?

I've been trying to use LogonUser(...) to get an access token for a user account, as in this MSDN sample.
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(userName, domainName, Console.ReadLine(),
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
When I run the sample (with Administrator privileges) it works fine when given a domain of . and a local user account name and password, but no matter what I do I get error code 1326 (Logon failure: unknown user name or bad password) if I try to use a domain account. I get the same result if I enter garbage for the domain, which makes me wonder if it's actually contacting the DC at all.
What could be stopping this from working?
In my case the issue, similar to the question asker, was that the account I was trying to authenticate to was in a domain that my current machine did not belong to. Unlike the original poster, my machine should not and could not be part of this other domain. I wanted the login to perform action on a resource on this domain though.
The answer was the following
bool success = LogonUser(
userName,
domain,
password,
(int)LOGON32_LOGON_NEW_CREDENTIALS, //9
(int)LOGON32_PROVIDER_DEFAULT, //0
out userToken);
with the following constants defined:
public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
public const int LOGON32_PROVIDER_DEFAULT = 0;
Hopefully this will help others who are lost in a similar situation.
Edit: As mentioned in the comments below, this logon type allows the caller to clone its current token and specify new credentials for outbound connections. The new logon session has the same local identifier but uses different credentials for other network connections. As a result of that fact, "success" will return true even if the password is bad. You will need an additional check beyond "success" to confirm that the credentials are actually good.
This was not a concern in my initial use case as we used the current network user's credential in another function to pull the plaintext password from secure storage. So it would have never been wrong unless there was an inconsistency between that system and active directory in which case we had bigger problems.
In my case it was the fact that, although I was logged in to my computer as a domain user, my computer was not itself part of the domain. Once added to the domain the sample started to work.
Use DOMAIN\LOGIN with an empty domainname for that case...

Resources