locked
WNetAddConnection[2|3]() can increment the badPwdCounter by more then 1? RRS feed

  • Question

  • I have been looking into the behavior of some test cases which require that a valid domain account be used with a known bad password.  During these tests it was found that when using UPN credentials the badPwdCounter for the user would be incremented more then the expected value of 1.  For example: Using john@example.com, before the call to WNetAddConnection2() the badPwdCounter is 0, after the call to WNetAddConnection2() badPwdCounter is 2.  The same result is seen using the non spec UPN format: john@example

    Capturing network traffic at both the source where the API call is made and on the target system shows the following:

    1. Calling System
    2.  There are 2 Kerberos AS-REQs sent to the domain controller
    3.   Both respond with PREAUTH_REQUIRED
    4. Target System
    5.    The RPC_NETLOGON EP is mapped and NetrLogonSamLogonWithFlags is called.

    I don't think the Kerberos AS-REQ are contributing to the extra increments since no information regarding the credential as been provided.

    If I change the credential format from UPN to DLLN (Down Level Logon Name) then the badPwdCounter will only be incremented by the expected value. Example: john@example.com -> example.com\john

    Has anyone else seen this behavior? and/or is it expected behavior?


    Friday, November 15, 2019 8:41 PM

All replies

  • Hi,

    Thanks for posting here.

    With the following sample, I cannot repro the same issue:

    #include <windows.h>
    #include <iostream>
    int main()
    {
    	DWORD dwResult;
    	NETRESOURCE nr;
    	nr.dwType = RESOURCETYPE_DISK;
    	char disk[] = "";
    	char remote[] = "\\\\172.17.12.89\\Share";
    	nr.lpLocalName = disk;
    	nr.lpRemoteName = remote;
    	nr.lpProvider = NULL;
    	dwResult = WNetAddConnection2(&nr,"test","drakew@example", CONNECT_TEMPORARY); //dwResult = 86
    	
    	if (dwResult == ERROR_ALREADY_ASSIGNED)
    	{
    		printf("Already connected to specified resource.\n");
    		return dwResult;
    	}
    
    	//  An entry for the local device already exists in the user profile.
    	//
    	else if (dwResult == ERROR_DEVICE_ALREADY_REMEMBERED)
    	{
    		printf("Attempted reassignment of remembered device.\n");
    		return dwResult;
    	}
    	else if (dwResult != NO_ERROR)
    	{
    		printf("WNetAddConnection2 failed.\n");
    		return dwResult;
    	}
    
    	printf("Connected to the specified resource.\n");
    	dwResult = WNetCancelConnection2(nr.lpRemoteName, 0, TRUE);
    }

    With the tool LockoutStatus.exe to check the Bad Pwd Count, only 1 Bad Pwd Count was been set.

    Could you provide your sample and SDK&OS version to reproduce? Thanks.

    Best Regards,

    Drake


    MSDN Community Support Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, November 18, 2019 3:44 AM
  • Thank you for taking the time to look into this.

    I have run your code, converting it to support Unicode, and I get the same result.  The tests I am running are on Windows 10 1903 x64 systems.  The test domain has 2 DC's, both of which are Windows 2016 Server 1607.

    I get the same result testing from within the test domain and from a system external to the domain.  I have also seen the badPwdCounter increment by 4 when trying to connect to a user published share using a bad password.

    I am using the following test code, built in VS 2019 SDK 10.0.18362.0

    #include <windows.h>
    #include <stdio.h>
    #include <winnetwk.h>
    
    #include <cassert>
    #include <iostream>
    #include <string>
    
    // https://sourceforge.net/projects/tclap/files/tclap-1.2.2.tar.gz/download
    #include "tclap\CmdLine.h"
    
    #pragma comment(lib, "mpr.lib")
    
    #define URI_DEFAULT_TARGET TEXT("\\ADMIN$")
    
    #if defined(_UNICODE)
    using string_t = std::wstring;
    using char_t = wchar_t;
    #else
    using string_t = std::string;
    using char_t = char;
    #define L(s) s
    #endif
    
    static bool gUseDefaultTarget = true;
    static string_t gShare = URI_DEFAULT_TARGET;
    static string_t gUsername{};
    static string_t gPassword;
    
    static std::wstring convertFromString(const char *str, const size_t lenstr){
    	std::wstring ret;
    	if(str && lenstr>0){
    		wchar_t *wtemp=new wchar_t[lenstr+2];
    		assert(MultiByteToWideChar(CP_UTF8,0,str,(int)lenstr,NULL,0)<(int)(lenstr+1));
    		int count=MultiByteToWideChar(CP_UTF8,0,str,(int)lenstr,wtemp,(int)lenstr+1);
    		if(str[lenstr-1]==0){
    			// if the string was null terminated, we don't want the null in the final string
    			count--;
    		}
    		if(count>0){
    			ret.assign(wtemp,count);
    		}
    		delete [] wtemp;
    	}
    	return ret;
    }
    
    inline string_t buildRemoteURI(const string_t& remoteHostname, bool useDefaultTarget = true) {
    	return { TEXT("\\\\") + remoteHostname + TEXT("\\") + gShare };
    }
    
    DWORD AddConnection(const string_t& hostname, bool useCredentials, DWORD flags)
    {
    	const string_t remoteURI = buildRemoteURI(hostname, gUseDefaultTarget);
    
    	std::wcout << TEXT("Attempting connection to: ") << remoteURI << std::endl;
    
    	NETRESOURCE nr = {
    		0,
    		RESOURCETYPE_ANY,
    		0,
    		0,
    		NULL,
    		(LPTSTR)remoteURI.c_str(),
    		NULL,
    		NULL
    	};
    
    	if (useCredentials) {
    		return WNetAddConnection2(&nr, gPassword.c_str(), gUsername.c_str(), flags);
    	}
    	return WNetAddConnection2(&nr, NULL, NULL, flags);
    }
    
    inline void Disconnect(const string_t& hostname)
    {
    	std::cout << "Disconnecting\n";
    	const string_t remoteURI = buildRemoteURI(hostname, gUseDefaultTarget);
    	const DWORD err = WNetCancelConnection2(remoteURI.c_str(), 0, true);
    	if ((err != ERROR_SUCCESS) && (err != ERROR_NOT_CONNECTED)) {
    		std::cout << "ERROR: WNetCancelConnection2 [" << err << "]\n";
    	}
    }
    
    int main(int argc, const char* argv[])
    {
    	TCLAP::CmdLine cmd("WNetAddConnection2 test", ' ', "0.0.1");
    
    	TCLAP::ValueArg<std::string> target("t", "target", "Host to connect to", true, "", "string");
    	cmd.add(target);
    
    	TCLAP::ValueArg<std::string> share("s", "share", "Connect to specified share", false, "", "string");
    	cmd.add(share);
    
    	TCLAP::ValueArg<std::string> username("u", "user", "Username", true, "", "string");
    	cmd.add(username);
    
    	TCLAP::ValueArg<std::string> password("p", "password", "Password", false, "bad-password", "string");
    	cmd.add(password);
    
    	TCLAP::SwitchArg useIPC("", "ipc", "Use default IPC target");
    	cmd.add(useIPC);
    
    	cmd.parse(argc, argv);
    
    	gUseDefaultTarget = !useIPC.getValue();
    	#if defined(UNICODE)
    	string_t const remoteHost = convertFromString(target.getValue().c_str(), target.getValue().size() + 1);
    	if (share.isSet()) {
    		gShare = convertFromString(share.getValue().c_str(), share.getValue().size() + 1);
    	}
    	gUsername = convertFromString(username.getValue().c_str(), username.getValue().size() + 1);
    	gPassword = convertFromString(password.getValue().c_str(), password.getValue().size() + 1);
    	#else
    	string_t& remoteHost = target.getValue();
    	gUsername = username.getValue();
    	gPassword = password.getValue();
    	#endif
    
    	DWORD rc = AddConnection(remoteHost, true, CONNECT_TEMPORARY);
    	if (NO_ERROR != rc) {
    		std::cout << "AddConnection failed: 0x" << std::hex << std::uppercase << rc << std::endl;
    	} else {
    		Disconnect(remoteHost);
    	}
    }
    


    Monday, November 18, 2019 5:04 PM
  • This may be related to the default network provider,
    For example:
    https://support.secureauth.com/hc/en-us/articles/360020947652-badPwdCount-is-incremented-by-2-each-time-a-bad-password-is-entered-via-an-IdP-realm

    https://support.secureauth.com/hc/en-us/articles/360020735892-LDAP-Provider-Bind-versus-Search

    When using Search mode, the LDAP provider uses the search filter to find the user, and binds using UPN.
    When set this way it will make 2 logon attempts as the user each time the user tries to authenticate with UPN against the IdP realm.

    Similarly, the network provider you use may do the same things.

    I will continue to follow up this case.

    Best Regards,

    Drake


    MSDN Community Support Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Tuesday, November 19, 2019 3:34 AM
  • This is fantastic information and gives me not only some sanity but something to look into.  When allowing the WNetAddConnection2() API call to perform the authentication, I do not see any means of controlling how it will perform its binding.  Anyone know of any options?
    Wednesday, November 20, 2019 1:55 PM
  • This may not be related to the WNetAddConnection2 API. The lpProvider in the API specifies the network provider to be used, if lpprovider is NULL, or if it points to an empty string, the operating system attempts to determine the correct provider by parsing the string pointed to by the lpRemoteName member.

    Best Regards,

    Drake


    MSDN Community Support Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Thursday, November 21, 2019 8:19 AM