Pretending to Be smbpasswd with impacket
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 a 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!