Open m-oleary opened 7 years ago
Independently ran into this same issue today. The password "Mau’dib" triggered this bug from the common_roots.txt word list (not an ASCII apostrophe).
I confirmed that removing this password from the word list no longer triggered this bug.
For funzies, a few friends and I decided to look into this issue to see if we could come up with an answer and submit a PR that fixes the problem. What ensued was a painful trip down the Rabbit Hole, and I'm not sure I came out with my sanity.
There are two issues going on here.
The scanner code makes use of the Ruby SMB client to make an SMB connection to the endpoint. The code in question can be found here:
begin
realm = credential.realm || ""
username = credential.public || ""
password = credential.private || ""
client = RubySMB::Client.new(self.dispatcher, username: username, password: password, domain: realm)
status_code = client.login
if status_code == WindowsError::NTStatus::STATUS_SUCCESS
# Windows SMB will return an error code during Session
# Setup, but nix Samba requires a Tree Connect. Try admin$
# first, since that will tell us if this user has local
# admin access. Fall back to IPC$ which should be accessible
# to any user with valid creds.
begin
tree = client.tree_connect("\\\\#{host}\\admin$")
# Check to make sure we can write a file to this dir
if tree.permissions.add_file == 1
access_level = AccessLevels::ADMINISTRATOR
end
rescue Exception => e
client.tree_connect("\\\\#{host}\\IPC$")
end
end
case status_code.name
when *StatusCodes::CORRECT_CREDENTIAL_STATUS_CODES
status = Metasploit::Model::Login::Status::DENIED_ACCESS
when 'STATUS_SUCCESS'
status = Metasploit::Model::Login::Status::SUCCESSFUL
when 'STATUS_ACCOUNT_LOCKED_OUT'
status = Metasploit::Model::Login::Status::LOCKED_OUT
when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED'
status = Metasploit::Model::Login::Status::INCORRECT
else
status = Metasploit::Model::Login::Status::INCORRECT
end
rescue ::Rex::ConnectionError, Errno::EINVAL => e
status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
proof = e
rescue RubySMB::Error::UnexpectedStatusCode => e
status = Metasploit::Model::Login::Status::INCORRECT
ensure
client.disconnect!
end
The problem is that an exception is being thrown during the construction of the RubySMB::Client
object due to incorrect/invalid handling of character encoding, and the client
variable remains nil
. As a result the ensure
case is invoked, and client.disconnect!
is called when client
is still nil
. This is what's causing the stack trace that we're looking at, and so the disconnect line should be changed to this:
client.disconnect! if client
Easy done.
The PASS_FILE
datastore item references a file on disk that's used to load all the passwords into credential instances. While parsing this file, the following code is invoked:
if pass_file.present?
pass_fd = File.open(pass_file, 'r:binary')
end
prepended_creds.each { |c| yield c }
if username.present?
if password.present?
yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm, private_type: private_type(password))
end
if user_as_pass
yield Metasploit::Framework::Credential.new(public: username, private: username, realm: realm, private_type: :password)
end
if blank_passwords
yield Metasploit::Framework::Credential.new(public: username, private: "", realm: realm, private_type: :password)
end
if pass_fd
pass_fd.each_line do |pass_from_file|
pass_from_file.chomp!
yield Metasploit::Framework::Credential.new(public: username, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file))
end
pass_fd.seek(0)
end
additional_privates.each do |add_private|
yield Metasploit::Framework::Credential.new(public: username, private: add_private, realm: realm, private_type: private_type(add_private))
end
end
Note that the file in question is being opened as r:binary
, which is the equivalent of 8bit ASCII. If the file contains UTF8 characters (as is the case with the reports listed above) then later on we see the code fail in the ruby_smb
gem, at this location:
@password = password.encode("utf-8") || ''.encode("utf-8")
This is because the characters in the password can't be converted to UTF8 from ASCII. Exceptions about, everyone becomes sad.
The first thing I tried was to change the file mode from r:binary
to r:utf-8
, and that did indeed bypass the first issue. However, the inverse of this probablem (ie. the inability encode the UTF8 string to 8bit ASCII) appeared as soon as this line was hit:
pass_fd.each_line do |pass_from_file|
The use of each_line
here for some reasons forces the underling string to ASCII, and causes an exception. I have no idea why this is happening, so perhaps someone with more Ruby chops can get in here and explain (perhaps @egypt?).
After thinking about it for a while I came up with the following:
The way I ended up "fixing" this in my local branch was to change the above line in ruby_smb
to this:
@password = password.force_encoding("utf-8") || ''.encode("utf-8")
This meant that the password was forced to be UTF8 and didn't actually go through an encoding process from ASCII 8bit.
I do not like this solution as it's really only hitting the problem in one location. Needless to say this issue could (and probably will) manifest itself in other spots and in other ways. One example would be that the user names can suffer the same problem.
So we're at the point where we should figure out the best option and apply it as close to the 'bottom' of the chain as possible so that it has impact in all the places it needs to.
Please let me know what you think of this rambling, and what you think we should do to resolve it.
Thanks to Pipes, Rick, Multi and Lockyc for putting up with my ramblings while we tried to figure this out!
Hi!
This issue has been left open with no activity for a while now.
We get a lot of issues, so we currently close issues after 60 days of inactivity. It’s been at least 30 days since the last update here. If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open!
As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request.
[*] 192.168.1.104:445 - Authenticating to 192.168.1.104:445 as user 'Администратор'... [-] 192.168.1.104:445 - Exploit failed [no-access]: Rex::Proto::SMB::Exceptions::LoginError Login Failed: "\xD0" from ASCII-8BIT to UTF-8
Attempting to run auxiliary/scanner/smb/smb_login against a target fails with the error Auxiliary failed: NoMethodError undefined method `disconnect!' for nil:NilClass if the the tested password is something other than ASCII.
Steps to reproduce
Create a file with a password with non-ASCII characters; for example create the file abbe.lst with the single password
abbé
(This word appears in the file /usr/share/wordlists/metasploit/password.lst on the current version of Kali, which is how I found the issue)
Running the command with LogLevel 5 yields the following data from /root/.msf4/logs/framework.log
Expected behavior
The module should proceed through the entire password list without crashing
Current behavior
The module stops after the first non-ASCII word and crashes as above.
System stuff
Kali VM updated as of 5-20-2017 running on VirtualBox
Metasploit version
msf auxiliary(smb_login) > version Framework: 4.14.17-dev Console : 4.14.17-dev
I installed Metasploit with:
OS
What OS are you running Metasploit on? Kali