For me the most interesting aspect of pwning the Fuse machine from HTB was dealing with an expired domain user password. I found no other tools except smbpasswd to invoke such a password change remotely from Linux which seemed odd to me. So I decided to create a simple Python script with impacket which binds to the \samr pipe over SMB (MSRPC-SAMR) with a null session and calls SamrUnicodeChangePasswordUser2 to trigger the password change.
Change Password over SMB
There is this user
tlavel with an expired password
Fabricorp01. When trying to connect to an SMB resource (at FUSE.FABRICORP.LOCAL) STATUS_PASSWORD_MUST_CHANGE (0xC0000224) error is raised:
One way to get out of this situation remotely from Linux is to use this
$ smbpasswd -r fuse.fabricorp.local -U 'tlavel' Old SMB password: Fabricorp01 New SMB password: snovvcrash1! Retype new SMB password: snovvcrash1!
Under the hood it will try to authenticate as
tlavel:Fabricorp01 (red block in the Wireshark capture) and when it fails because of the above mentioned error, it will initiate a null session and call SamrUnicodeChangePasswordUser2 (55) function via DCE/RPC over MS-SAMR protocol to change
tlavel’s password (green block in the Wireshark capture):
So, let’s say I’m annoyed with all this secure-interactive-input stuff like in smbpasswd and I want to be able to do the same thing in one line with password values passed as CLI arguments. When I try to achieve this with rpcclient and null authentication, I shall fail:
$ rpcclient -U'%' -c'chgpasswd2 tlavel Fabricorp01 snovvcrash1!' fuse.fabricorp.local
It’s no secret that rpcclient does a bad job when dealing with null session requests. Long story short, after successfully binding to the
IPC$ share and creating
\samr pipe, it makes a bunch of undesirable requests (red block in the Wireshark capture) which it does not really have permissions for within a null session and eventually dies with
Another way to try a password change over SMB is the impacket’s smbclient.py password functionality, but it will not work when the password is expired either:
$ smbclient.py 'tlavel:Fabricorp01@10.10.10.193'
Same story with Samba’s NetCommand:
$ net rpc password tlavel 'snovvcrash1!' -S FUSE -U 'tlavel'%'Fabricorp01' session setup failed: NT_STATUS_PASSWORD_MUST_CHANGE Failed to set password for 'tlavel' with error: Failed to connect to IPC$ share on FUSE.
That’s not what we want, so we can create a super simple Python script using impacket to initiate a null session and then change
tlavel’s password directly with one single call to hSamrUnicodeChangePasswordUser2:
#!/usr/bin/python2.7 from argparse import ArgumentParser from impacket.dcerpc.v5 import transport, samr def connect(host_name_or_ip): rpctransport = transport.SMBTransport(host_name_or_ip, filename=r'\samr') if hasattr(rpctransport, 'set_credentials'): rpctransport.set_credentials(username='', password='', domain='', lmhash='', nthash='', aesKey='') # null session dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(samr.MSRPC_UUID_SAMR) return dce def hSamrUnicodeChangePasswordUser2(username, oldpass, newpass, target): dce = connect(target) resp = samr.hSamrUnicodeChangePasswordUser2(dce, '\x00', username, oldpass, newpass) resp.dump() parser = ArgumentParser() parser.add_argument('username', help='username to change password for') parser.add_argument('oldpass', help='old password') parser.add_argument('newpass', help='new password') parser.add_argument('target', help='hostname or IP') args = parser.parse_args() hSamrUnicodeChangePasswordUser2(args.username, args.oldpass, args.newpass, args.target)
Now we can trigger password change with a single command insecurely leaving all the sensitive information in
.bash_history… Just as we’ve always wanted
$ ./smbpasswd.py tlavel Fabricorp01 'snovvcrash01!' FUSE.FABRICORP.LOCAL
Later on I groomed up the code a little and made a pull request to the impacket’s master branch.
Password Policies Notes
There is a couple of points to be cleared:
1. From now on both passwords will be valid for approximately one hour after the password change. If I try to authorize with the old
Fabricorp01 password after the change was made, it actually works and the new
snovvcrash01! password is valid too:
2. There is a scheduled task which reverts the password back to
Fabricorp01 every 1 minute:
3. If I try to change the password two times in a row but before it was reverted to default
Fabricorp01 by the scheduler task (
snovvcrash02!), it fails due to the minimum password age policy (1 day):
4. If I try to set the same password again after it was reverted to default
Fabricorp01 by the scheduler task (
snovvcrash01!), it fails due to the enforce password history policy (24 passwords):
Cheers and happy hacking!