New-LocalUser created while password complexity is not met

Viewed 176

Consider the following code:

$params = @{
    Name        = 'testUser1'
    Password    = (ConvertTo-SecureString '3' -AsPlainText -Force)
    ErrorAction = 'Stop'
}
New-LocalUser @params

When the "Local Security Policy" for the password complexity is enabled the code above should fail. This seems to be the case but the user account is still created. Is this a bug in Windows?

1 Answers

TL;DR

Yes, it might be a bug that your user account half-way exists after a failed creation due to an uncomplex password. The source code first creates a user account and then sets its password. Furthermore, it intends to delete a half-way created user account, but it obviously doesn't.

Explanation

Let's have a look at the source code (relative to commit 658837323599ab1c7a81fe66fcd43f7420e4402b):

  1. New-LocalUser is implemented in NewLocalUserCommand.cs. In line 262, CreateLocalUser() from Sam.cs is called:

    user = sam.CreateLocalUser(user, Password, PasswordNeverExpires.IsPresent);
    

    So nothing special here.

  2. CreateLocalUser() from Sam.cs calls CreateUser() on line 819 in the same file:

    internal LocalUser CreateLocalUser(LocalUser user, System.Security.SecureString password, bool setPasswordNeverExpires)
    {
        context = new Context(ContextOperation.New, ContextObjectType.User, user.Name, user);
    
        return CreateUser(user, password, localDomainHandle, setPasswordNeverExpires);
    }
    

    Nothing special here.

  3. Now CreateUser() creates the user first without a password in line 1248 and later sets the password for the already existing user and also additional info in line 1264:

    status = SamApi.SamCreateUser2InDomain(domainHandle,
        ref str,
        (int) SamApi.USER_NORMAL_ACCOUNT,
        Win32.MAXIMUM_ALLOWED,
        out userHandle,
        out grantedAccess,
        out relativeId);
    Marshal.DestroyStructure<UNICODE_STRING>(buffer);
    Marshal.FreeHGlobal(buffer);
    buffer = IntPtr.Zero;
    ThrowOnFailure(status);
    
    // set the various properties of the user. A SID is required because some
    // operations depend on it.
    userInfo.SID = RidToSid(domainHandle, relativeId);
    
    SetUserData(userHandle, userInfo, UserProperties.AllCreateable, password, PasswordExpiredState.NotExpired, setPasswordNeverExpires);
    

    This explains, why a user account is created even if the password complexity is not met: The password gets set later.

If you look further into the source code, you will see a catch block, that deletes the user in case something goes wrong. So it is indeed weird, that your new user persists after being created with an uncomplex password. SetUserData() and SetUserPassword(), both forward exceptions. So, everything fine so far. SetUserPassword() calls SamSetInformationUser() from samlib.dll. I assume that the password complexity will be checked there. Unfortunaltely, samlib.dll is not open source as far as I know and we cannot look into that any further.

So yes, it might be a bug that your user account half-way exists after a failed creation due to an uncomplex password. The code intends to delete a half-way created user account, but it obviously doesn't.

Related