Developing an auxiliary—the SSH brute force module
For checking weak login credentials, we need to perform an authentication brute force attack. The agenda of such tests is not only to test an application against weak credentials but to ensure proper authorization and access controls as well. These tests ensure that attackers cannot simply bypass the security paradigm by trying a non-exhaustive brute force attack, and are locked out after a certain number of random guesses.
Designing the next module for authentication testing on the SSH service, we will look at how easy it is to design authentication-based checks in Metasploit, and perform tests that attack authentication. Let's now jump into the coding part and begin designing a module, as follows:
require 'metasploit/framework/credential_collection'
require 'metasploit/framework/login_scanner/ssh'
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
include Msf::Auxiliary::AuthBrute
def initialize super(
'Name' => 'SSH Scanner',
'Description' => %q{My Module.},
'Author' => 'Nipun Jaswal',
'License' => MSF_LICENSE
)
register_options([
Opt::RPORT(22)
])
end
In the previous examples, we have already seen the importance of using Msf::Auxiliary::Scanner and Msf::Auxiliary::Report. Let's see the other included libraries and understand their usage in the following table:
In the preceding code, we also included two files, which are metasploit/framework/login_scanner/ssh and metasploit/framework/credential_collection. The metasploit/framework/login_scanner/ssh file includes the SSH login scanner library that eliminates all manual operations and provides an underlying API to SSH scanning.
The metasploit/framework/credential_collection file helps to create multiple credentials based on user inputs from datastore. Next, we simply define the type of the module we are building.
In the initialize section, we define the basic information for this module. Let's see the next section:
def run_host(ip)
cred_collection = Metasploit::Framework::CredentialCollection.new(
blank_passwords: datastore['BLANK_PASSWORDS'],
pass_file: datastore['PASS_FILE'],
password: datastore['PASSWORD'],
user_file: datastore['USER_FILE'],
userpass_file: datastore['USERPASS_FILE'],
username: datastore['USERNAME'],
user_as_pass: datastore['USER_AS_PASS'],)
scanner = Metasploit::Framework::LoginScanner::SSH.new(
host: ip,
port: datastore['RPORT'],
cred_details: cred_collection,
proxies: datastore['Proxies'],
stop_on_success: datastore['STOP_ON_SUCCESS'],
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
connection_timeout: datastore['SSH_TIMEOUT'],
framework: framework,
framework_module: self,
)
We can see that we have two objects in the preceding code, which are cred_collection and scanner. An important point to make a note of here is that we do not require any manual methods of logging into the SSH service because the login scanner does everything for us. Therefore, cred_collection is doing nothing but yielding sets of credentials based on the datastore options set on a module. The beauty of the CredentialCollection class lies in the fact that it can take a single username/password combination, wordlists, and blank credentials all at once, or one of them at a time.
All login scanner modules require credential objects for their login attempts. The scanner object defined in the preceding code initializes an object for the SSH class. This object stores the address of the target, port, and credentials as generated by the CredentialCollection class, and other data-like proxy information. stop_on_success, which will stop the scanning on the successful credential match, brute force speed, and the value of the attempted timeout.
Up to this point in the module, we have created two objects: cred_collection, which will generate credentials based on the user input, and the scanner object, which will use those credentials to scan the target. Next, we need to define a mechanism so that all the credentials from a wordlist are defined as single parameters and are tested against the target.
We have already seen the usage of run_host in previous examples. Let's see what other vital functions from various libraries we are going to use in this module:
Let's move on to the next piece of code, as follows:
scanner.scan! do |result|
credential_data = result.to_h
credential_data.merge!(
module_fullname: self.fullname,
workspace_id: myworkspace_id
)
if result.success?
credential_core = create_credential(credential_data)
credential_data[:core] = credential_core
create_credential_login(credential_data)
print_good "#{ip} - LOGIN SUCCESSFUL: #{result.credential}"
else
invalidate_login(credential_data)
print_status "#{ip} - LOGIN FAILED: #{result.credential}(#{result.status}: #{result.proof})"
end
end
end
end
It can be observed that we used .scan to initialize the scan, and this will perform all the login attempts by itself, which means we do not need to specify any other mechanism explicitly. The .scan instruction is exactly like an each loop in Ruby.
In the next statement, the results get saved in the result object and are assigned to the credential_data variable using the to_h method, which will convert the data to a hash format. In the next line, we merge the module name and workspace ID into the credential_data variable. Next, we run an if-else check on the result object using the .success variable, which denotes successful login attempts into the target. If result.success? returns true, we mark the credential as a successful login attempt and store it in the database. However, if the condition is not satisfied, we pass the credential_data variable to the invalidate_login method, which denotes a failed login.
It is advisable to run all the modules in this chapter and all the later chapters only after performing a consistency check through msftidy. Let's try running the module, as follows:
We can see that we were able to log in with root and qwerty as the username and password. Let's see if we were able to log the credentials into the database using the creds command:
We can see that we have the details logged into the database, and they can be used to carry out advanced attacks, or for reporting.
Rephrasing the equation
If you are scratching your head after working on the module listed previously, let's understand the module in a step-by-step fashion:
- We've created a CredentialCollection object that takes any user as input and yields credentials, which means that if we provide USERNAME as root and PASSWORD as root, it will yield those as a single credential. However, if we use USER_FILE and PASS_FILE as dictionaries, then it will take each username and password from the dictionary file and will generate credentials for each combination of username and password from the files, respectively.
- We've created a scanner object for SSH, which will eliminate any manual command usage and will simply check all the combinations we supplied one after the other.
- We've run our scanner using the .scan method, which will initialize the authentication of brute force on the target.
- The .scan method will scan all credentials one after the other and, based on the result, will store it in the database and display it with print_good, else it will show it using print_status without saving it.