Mirror of metasploit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

auth_brute.rb 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. # -*- coding: binary -*-
  2. module Msf
  3. ###
  4. #
  5. # This module provides methods for brute forcing authentication
  6. #
  7. ###
  8. module Auxiliary::AuthBrute
  9. def initialize(info = {})
  10. super
  11. register_options([
  12. OptString.new('USERNAME', [ false, 'A specific username to authenticate as' ]),
  13. OptString.new('PASSWORD', [ false, 'A specific password to authenticate with' ]),
  14. OptPath.new('USER_FILE', [ false, "File containing usernames, one per line" ]),
  15. OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line" ]),
  16. OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line" ]),
  17. OptInt.new('BRUTEFORCE_SPEED', [ true, "How fast to bruteforce, from 0 to 5", 5]),
  18. OptBool.new('VERBOSE', [ true, "Whether to print output for all attempts", true]),
  19. OptBool.new('BLANK_PASSWORDS', [ false, "Try blank passwords for all users", false]),
  20. OptBool.new('USER_AS_PASS', [ false, "Try the username as the password for all users", false]),
  21. OptBool.new('DB_ALL_CREDS', [false,"Try each user/password couple stored in the current database",false]),
  22. OptBool.new('DB_ALL_USERS', [false,"Add all users in the current database to the list",false]),
  23. OptBool.new('DB_ALL_PASS', [false,"Add all passwords in the current database to the list",false]),
  24. OptBool.new('STOP_ON_SUCCESS', [ true, "Stop guessing when a credential works for a host", false]),
  25. ], Auxiliary::AuthBrute)
  26. register_advanced_options([
  27. OptBool.new('REMOVE_USER_FILE', [ true, "Automatically delete the USER_FILE on module completion", false]),
  28. OptBool.new('REMOVE_PASS_FILE', [ true, "Automatically delete the PASS_FILE on module completion", false]),
  29. OptBool.new('REMOVE_USERPASS_FILE', [ true, "Automatically delete the USERPASS_FILE on module completion", false]),
  30. OptInt.new('MaxGuessesPerService', [ false, "Maximum number of credentials to try per service instance. If set to zero or a non-number, this option will not be used.", 0]), # Tracked in @@guesses_per_service
  31. OptInt.new('MaxMinutesPerService', [ false, "Maximum time in minutes to bruteforce the service instance. If set to zero or a non-number, this option will not be used.", 0]), # Tracked in @@brute_start_time
  32. OptInt.new('MaxGuessesPerUser', [ false, %q{
  33. Maximum guesses for a particular username for the service instance.
  34. Note that users are considered unique among different services, so a
  35. user at 10.1.1.1:22 is different from one at 10.2.2.2:22, and both will
  36. be tried up to the MaxGuessesPerUser limit. If set to zero or a non-number,
  37. this option will not be used.}.gsub(/[\t\r\n\s]+/nm,"\s"), 0]) # Tracked in @@brute_start_time
  38. ], Auxiliary::AuthBrute)
  39. end
  40. def setup
  41. @@max_per_service = nil
  42. end
  43. # Yields each Metasploit::Credential::Core in the Mdm::Workspace with
  44. # a private type of 'ntlm_hash'
  45. #
  46. # @yieldparam [Metasploit::Credential::Core]
  47. def each_ntlm_cred
  48. creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' }, workspace_id: myworkspace.id)
  49. creds.each do |cred|
  50. yield cred
  51. end
  52. end
  53. # Yields each Metasploit::Credential::Core in the Mdm::Workspace with
  54. # a private type of 'password'
  55. #
  56. # @yieldparam [Metasploit::Credential::Core]
  57. def each_password_cred
  58. creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::Password' }, workspace_id: myworkspace.id)
  59. creds.each do |cred|
  60. yield cred
  61. end
  62. end
  63. # Yields each Metasploit::Credential::Core in the Mdm::Workspace with
  64. # a private type of 'ssh_key'
  65. #
  66. # @yieldparam [Metasploit::Credential::Core]
  67. def each_ssh_cred
  68. creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::SSHKey' }, workspace_id: myworkspace.id)
  69. creds.each do |cred|
  70. yield cred
  71. end
  72. end
  73. # Checks whether we should be adding creds from the DB to a CredCollection
  74. #
  75. # @return [TrueClass] if any of the datastore options for db creds are selected and the db is active
  76. # @return [FalseClass] if none of the datastore options are selected OR the db is not active
  77. def prepend_db_creds?
  78. (datastore['DB_ALL_CREDS'] || datastore['DB_ALL_PASS'] || datastore['DB_ALL_USERS']) && framework.db.active
  79. end
  80. # This method takes a Metasploit::Framework::CredentialCollection and prepends existing NTLMHashes
  81. # from the database. This allows the users to use the DB_ALL_CREDS option.
  82. #
  83. # @param cred_collection [Metasploit::Framework::CredentialCollection]
  84. # the credential collection to add to
  85. # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection
  86. def prepend_db_hashes(cred_collection)
  87. if prepend_db_creds?
  88. each_ntlm_cred do |cred|
  89. process_cred_for_collection(cred_collection,cred)
  90. end
  91. end
  92. cred_collection
  93. end
  94. # This method takes a Metasploit::Framework::CredentialCollection and prepends existing SSHKeys
  95. # from the database. This allows the users to use the DB_ALL_CREDS option.
  96. #
  97. # @param [Metasploit::Framework::CredentialCollection] cred_collection
  98. # the credential collection to add to
  99. # @return [Metasploit::Framework::CredentialCollection] cred_collection the modified Credentialcollection
  100. def prepend_db_keys(cred_collection)
  101. if prepend_db_creds?
  102. each_ssh_cred do |cred|
  103. process_cred_for_collection(cred_collection,cred)
  104. end
  105. end
  106. cred_collection
  107. end
  108. # This method takes a Metasploit::Framework::CredentialCollection and prepends existing Password Credentials
  109. # from the database. This allows the users to use the DB_ALL_CREDS option.
  110. #
  111. # @param cred_collection [Metasploit::Framework::CredentialCollection]
  112. # the credential collection to add to
  113. # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection
  114. def prepend_db_passwords(cred_collection)
  115. if prepend_db_creds?
  116. each_password_cred do |cred|
  117. process_cred_for_collection(cred_collection,cred)
  118. end
  119. end
  120. cred_collection
  121. end
  122. # Takes a Metasploit::Credential::Core and converts it into a
  123. # Metasploit::Framework::Credential and processes it into the
  124. # Metasploit::Framework::CredentialCollection as dictated by the
  125. # selected datastore options.
  126. #
  127. # @param [Metasploit::Framework::CredentialCollection] cred_collection the credential collection to add to
  128. # @param [Metasploit::Credential::Core] cred the credential to process
  129. def process_cred_for_collection(cred_collection, cred)
  130. msf_cred = cred.to_credential
  131. cred_collection.prepend_cred(msf_cred) if datastore['DB_ALL_CREDS']
  132. cred_collection.add_private(msf_cred.private) if datastore['DB_ALL_PASS']
  133. cred_collection.add_public(msf_cred.public) if datastore['DB_ALL_USERS']
  134. end
  135. # Checks all three files for usernames and passwords, and combines them into
  136. # one credential list to apply against the supplied block. The block (usually
  137. # something like do_login(user,pass) ) is responsible for actually recording
  138. # success and failure in its own way; each_user_pass() will only respond to
  139. # a return value of :done (which will signal to end all processing) and
  140. # to :next_user (which will cause that username to be skipped for subsequent
  141. # password guesses). Other return values won't affect the processing of the
  142. # list.
  143. #
  144. # The 'noconn' argument should be set to true if each_user_pass is merely
  145. # iterating over the usernames and passwords and should not respect
  146. # bruteforce_speed as a delaying factor.
  147. def each_user_pass(noconn=false,&block)
  148. this_service = [datastore['RHOST'],datastore['RPORT']].join(":")
  149. fq_rest = [this_service,"all remaining users"].join(":")
  150. # This should kinda halfway be in setup, halfway in run... need to
  151. # revisit this.
  152. unless credentials ||= false # Assignment and comparison!
  153. credentials ||= build_credentials_array()
  154. credentials = adjust_credentials_by_max_user(credentials)
  155. this_service = [datastore['RHOST'],datastore['RPORT']].join(":")
  156. initialize_class_variables(this_service,credentials)
  157. end
  158. credentials.each do |u, p|
  159. # Explicitly be able to set a blank (zero-byte) username by setting the
  160. # username to <BLANK>. It's up to the caller to handle this if it's not
  161. # allowed or if there's any special handling needed (such as smb_login).
  162. u = "" if u =~ /^<BLANK>$/i
  163. break if @@credentials_skipped[fq_rest]
  164. fq_user = [this_service,u].join(":")
  165. # Set noconn to indicate that in this case, each_user_pass
  166. # is not actually kicking off a connection, so the
  167. # bruteforce_speed datastore should be ignored.
  168. if not noconn
  169. userpass_sleep_interval unless @@credentials_tried.empty?
  170. end
  171. next if @@credentials_skipped[fq_user]
  172. next if @@credentials_tried[fq_user] == p
  173. ret = block.call(u, p)
  174. case ret
  175. when :abort # Skip the current host entirely.
  176. abort_msg = {
  177. :level => :error,
  178. :ip => datastore['RHOST'],
  179. :port => datastore['RPORT'],
  180. :msg => "Bruteforce cancelled against this service."
  181. }
  182. unless datastore['VERBOSE']
  183. abort_msg[:msg] << " Enable verbose output for service-specific details."
  184. end
  185. print_brute abort_msg
  186. break
  187. when :next_user # This means success for that user.
  188. @@credentials_skipped[fq_user] = p
  189. if datastore['STOP_ON_SUCCESS'] # See?
  190. @@credentials_skipped[fq_rest] = true
  191. end
  192. when :skip_user # Skip the user in non-success cases.
  193. @@credentials_skipped[fq_user] = p
  194. when :connection_error # Report an error, skip this cred, but don't neccisarily abort.
  195. print_brute(
  196. :level => :verror,
  197. :ip => datastore['RHOST'],
  198. :port => datastore['RPORT'],
  199. :msg => "Connection error, skipping '#{u}':'#{p}'")
  200. end
  201. @@guesses_per_service[this_service] ||= 1
  202. @@credentials_tried[fq_user] = p
  203. if counters_expired? this_service,credentials
  204. break
  205. else
  206. @@guesses_per_service[this_service] += 1
  207. end
  208. end
  209. end
  210. def counters_expired?(this_service,credentials)
  211. expired_cred = false
  212. expired_time = false
  213. # Workaround for cases where multiple auth_brute modules are running concurrently and
  214. # someone stomps on the @max_per_service class variable during setup.
  215. current_max_per_service = self.class.class_variable_get("@@max_per_service") rescue nil
  216. return false unless current_max_per_service
  217. if @@guesses_per_service[this_service] >= (@@max_per_service)
  218. if @@max_per_service < credentials.size
  219. print_brute(
  220. :level => :vstatus,
  221. :ip => datastore['RHOST'],
  222. :port => datastore['RPORT'],
  223. :msg => "Hit maximum guesses for this service (#{@@max_per_service}).")
  224. expired_cred = true
  225. end
  226. end
  227. seconds_to_run = datastore['MaxMinutesPerService'].to_i.abs * 60
  228. if seconds_to_run > 0
  229. if Time.now.utc.to_i > @@brute_start_time.to_i + seconds_to_run
  230. print_brute(
  231. :level => :vstatus,
  232. :ip => datastore['RHOST'],
  233. :port => datastore['RPORT'],
  234. :msg => "Hit timeout for this service at #{seconds_to_run / 60}m.")
  235. expired_time = true
  236. end
  237. end
  238. expired_cred || expired_time
  239. end
  240. # If the user passed a memory location for credential gen, assume
  241. # that that's precisely what's desired -- no other transforms or
  242. # additions or uniqueness should be done. Otherwise, perform
  243. # the usual alterations.
  244. def build_credentials_array
  245. credentials = extract_word_pair(datastore['USERPASS_FILE'])
  246. translate_proto_datastores()
  247. return credentials if datastore['USERPASS_FILE'] =~ /^memory:/
  248. users = load_user_vars(credentials)
  249. passwords = load_password_vars(credentials)
  250. cleanup_files()
  251. if datastore['USER_AS_PASS']
  252. credentials = gen_user_as_password(users, credentials)
  253. end
  254. if datastore['BLANK_PASSWORDS']
  255. credentials = gen_blank_passwords(users, credentials)
  256. end
  257. if framework.db.active
  258. if datastore['DB_ALL_CREDS']
  259. myworkspace.creds.each do |o|
  260. credentials << [o.user, o.pass] if o.ptype =~ /password/
  261. end
  262. end
  263. if datastore['DB_ALL_USERS']
  264. myworkspace.creds.each do |o|
  265. users << o.user
  266. end
  267. end
  268. if datastore['DB_ALL_PASS']
  269. myworkspace.creds.each do |o|
  270. passwords << o.pass if o.ptype =~ /password/
  271. end
  272. end
  273. end
  274. credentials.concat(combine_users_and_passwords(users, passwords))
  275. credentials.uniq!
  276. credentials = just_uniq_users(credentials) if @strip_passwords
  277. credentials = just_uniq_passwords(credentials) if @strip_usernames
  278. return credentials
  279. end
  280. # Class variables to track credential use. They need
  281. # to be class variables due to threading.
  282. def initialize_class_variables(this_service,credentials)
  283. @@guesses_per_service ||= {}
  284. @@guesses_per_service[this_service] = nil
  285. @@credentials_skipped = {}
  286. @@credentials_tried = {}
  287. @@guesses_per_service = {}
  288. if datastore['MaxGuessesPerService'].to_i.abs == 0
  289. @@max_per_service = credentials.size
  290. else
  291. if datastore['MaxGuessesPerService'].to_i.abs >= credentials.size
  292. @@max_per_service = credentials.size
  293. print_brute(
  294. :level => :vstatus,
  295. :ip => datastore['RHOST'],
  296. :port => datastore['RPORT'],
  297. :msg => "Adjusting MaxGuessesPerService to the actual total number of credentials")
  298. else
  299. @@max_per_service = datastore['MaxGuessesPerService'].to_i.abs
  300. end
  301. end
  302. unless datastore['MaxMinutesPerService'].to_i.abs == 0
  303. @@brute_start_time = Time.now.utc
  304. end
  305. end
  306. def load_user_vars(credentials = nil)
  307. users = extract_words(datastore['USER_FILE'])
  308. if datastore['USERNAME']
  309. users.unshift datastore['USERNAME']
  310. credentials = prepend_chosen_username(datastore['USERNAME'], credentials) if credentials
  311. end
  312. users
  313. end
  314. def load_password_vars(credentials = nil)
  315. passwords = extract_words(datastore['PASS_FILE'])
  316. if datastore['PASSWORD']
  317. passwords.unshift datastore['PASSWORD']
  318. credentials = prepend_chosen_password(datastore['PASSWORD'], credentials) if credentials
  319. end
  320. passwords
  321. end
  322. # Takes protocol-specific username and password fields, and,
  323. # if present, prefer those over any given USERNAME or PASSWORD.
  324. # Note, these special username/passwords should get deprecated
  325. # some day. Note2: Don't use with SMB and FTP at the same time!
  326. def translate_proto_datastores
  327. switched = false
  328. ['SMBUser','FTPUSER'].each do |u|
  329. if datastore[u] and !datastore[u].empty?
  330. datastore['USERNAME'] = datastore[u]
  331. end
  332. end
  333. ['SMBPass','FTPPASS'].each do |p|
  334. if datastore[p] and !datastore[p].empty?
  335. datastore['PASSWORD'] = datastore[p]
  336. end
  337. end
  338. end
  339. def just_uniq_users(credentials)
  340. credentials.map {|x| [x[0],""]}.uniq
  341. end
  342. def just_uniq_passwords(credentials)
  343. credentials.map{|x| ["",x[1]]}.uniq
  344. end
  345. def prepend_chosen_username(user,cred_array)
  346. cred_array.map {|pair| [user,pair[1]]} + cred_array
  347. end
  348. def prepend_chosen_password(pass,cred_array)
  349. cred_array.map {|pair| [pair[0],pass]} + cred_array
  350. end
  351. def gen_blank_passwords(user_array,cred_array)
  352. blank_passwords = []
  353. unless user_array.empty?
  354. blank_passwords.concat(user_array.map {|u| [u,""]})
  355. end
  356. unless cred_array.empty?
  357. cred_array.each {|u,p| blank_passwords << [u,""]}
  358. end
  359. return(blank_passwords + cred_array)
  360. end
  361. def gen_user_as_password(user_array,cred_array)
  362. user_as_passwords = []
  363. unless user_array.empty?
  364. user_as_passwords.concat(user_array.map {|u| [u,u]})
  365. end
  366. unless cred_array.empty?
  367. cred_array.each {|u,p| user_as_passwords << [u,u]}
  368. end
  369. return(user_as_passwords + cred_array)
  370. end
  371. def combine_users_and_passwords(user_array,pass_array)
  372. if (user_array.length + pass_array.length) < 1
  373. return []
  374. end
  375. combined_array = []
  376. if pass_array.empty?
  377. combined_array = user_array.map {|u| [u,""] }
  378. elsif user_array.empty?
  379. combined_array = pass_array.map {|p| ["",p] }
  380. else
  381. user_array.each do |u|
  382. pass_array.each do |p|
  383. combined_array << [u,p]
  384. end
  385. end
  386. end
  387. creds = [ [], [], [], [] ] # userpass, pass, user, rest
  388. remaining_pairs = combined_array.length # counter for our occasional output
  389. interval = 60 # seconds between each remaining pair message reported to user
  390. next_message_time = Time.now + interval # initial timing interval for user message
  391. # Move datastore['USERNAME'] and datastore['PASSWORD'] to the front of the list.
  392. # Note that we cannot tell the user intention if USERNAME or PASSWORD is blank --
  393. # maybe (and it's often) they wanted a blank. One more credential won't kill
  394. # anyone, and hey, won't they be lucky if blank user/blank pass actually works!
  395. combined_array.each do |pair|
  396. if pair == [datastore['USERNAME'],datastore['PASSWORD']]
  397. creds[0] << pair
  398. elsif pair[1] == datastore['PASSWORD']
  399. creds[1] << pair
  400. elsif pair[0] == datastore['USERNAME']
  401. creds[2] << pair
  402. else
  403. creds[3] << pair
  404. end
  405. if Time.now > next_message_time
  406. print_brute(
  407. :level => :vstatus,
  408. :msg => "Pair list is still building with #{remaining_pairs} pairs left to process"
  409. )
  410. next_message_time = Time.now + interval
  411. end
  412. remaining_pairs -= 1
  413. end
  414. return creds[0] + creds[1] + creds[2] + creds[3]
  415. end
  416. def extract_words(wordfile)
  417. return [] unless wordfile && File.readable?(wordfile)
  418. begin
  419. words = File.open(wordfile) {|f| f.read(f.stat.size)}
  420. rescue
  421. return
  422. end
  423. save_array = words.split(/\r?\n/)
  424. return save_array
  425. end
  426. def get_object_from_memory_location(memloc)
  427. if memloc.to_s =~ /^memory:\s*([0-9]+)/
  428. id = $1
  429. ObjectSpace._id2ref(id.to_s.to_i)
  430. end
  431. end
  432. def extract_word_pair(wordfile)
  433. creds = []
  434. if wordfile.to_s =~ /^memory:/
  435. return extract_word_pair_from_memory(wordfile.to_s)
  436. else
  437. return [] unless wordfile && File.readable?(wordfile)
  438. begin
  439. upfile_contents = File.open(wordfile) {|f| f.read(f.stat.size)}
  440. rescue
  441. return []
  442. end
  443. upfile_contents.split(/\n/).each do |line|
  444. user,pass = line.split(/\s+/,2).map { |x| x.strip }
  445. creds << [user.to_s, pass.to_s]
  446. end
  447. return creds
  448. end
  449. end
  450. def extract_word_pair_from_memory(memloc)
  451. begin
  452. creds = []
  453. obj = get_object_from_memory_location(memloc)
  454. unless obj.all_creds.empty?
  455. these_creds = obj.all_creds
  456. else
  457. these_creds = obj.builders.select {|x| x.respond_to? :imported_users}.map {|b| b.imported_users}.flatten
  458. end
  459. these_creds.each do |cred|
  460. if @strip_passwords
  461. user = cred.split(/\s+/,2).map {|x| x.strip}[0]
  462. pass = ""
  463. elsif @strip_usernames
  464. user = ""
  465. pass = cred.split(/\s+/,2).map {|x| x.strip}[1]
  466. else
  467. user,pass = cred.split(/\s+/,2).map {|x| x.strip}
  468. end
  469. creds << [Rex::Text.dehex(user.to_s), Rex::Text.dehex(pass.to_s)]
  470. end
  471. if @strip_passwords || @strip_usernames
  472. return creds.uniq
  473. else
  474. return creds
  475. end
  476. rescue => e
  477. raise ArgumentError, "Could not read credentials from memory, raised: #{e.class}: #{e.message}"
  478. end
  479. end
  480. def userpass_sleep_interval
  481. sleep_time = case datastore['BRUTEFORCE_SPEED'].to_i
  482. when 0; 60 * 5
  483. when 1; 15
  484. when 2; 1
  485. when 3; 0.5
  486. when 4; 0.1
  487. else; 0
  488. end
  489. ::IO.select(nil,nil,nil,sleep_time) unless sleep_time == 0
  490. end
  491. # See #print_brute
  492. def vprint_brute(opts={})
  493. if datastore['VERBOSE']
  494. print_brute(opts)
  495. end
  496. end
  497. # Provides a consistant way to display messages about AuthBrute-mixed modules.
  498. # Acceptable opts are fairly self-explanatory, but :level can be tricky.
  499. #
  500. # It can be one of status, good, error, or line (and corresponds to the usual
  501. # print_status, print_good, etc. methods).
  502. #
  503. # If it's preceded by a "v" (ie, vgood, verror, etc), only print if
  504. # datastore["VERBOSE"] is set to true.
  505. #
  506. # If :level would make the method nonsense, default to print_status.
  507. #
  508. # TODO: This needs to be simpler to be useful.
  509. def print_brute(opts={})
  510. if opts[:level] and opts[:level].to_s[/^v/]
  511. return unless datastore["VERBOSE"]
  512. level = opts[:level].to_s[1,16].strip
  513. else
  514. level = opts[:level].to_s.strip
  515. end
  516. host_ip = opts[:ip] || opts[:rhost] || opts[:host] || (rhost rescue nil) || datastore['RHOST']
  517. host_port = opts[:port] || opts[:rport] || (rport rescue nil) || datastore['RPORT']
  518. msg = opts[:msg] || opts[:message] || opts[:legacy_msg]
  519. proto = opts[:proto] || opts[:protocol] || proto_from_fullname
  520. complete_message = build_brute_message(host_ip,host_port,proto,msg,!!opts[:legacy_msg])
  521. print_method = "print_#{level}"
  522. if self.respond_to? print_method
  523. self.send print_method, complete_message
  524. else
  525. print_status complete_message
  526. end
  527. end
  528. # Depending on the non-nil elements, build up a standardized
  529. # auth_brute message, but support the old style used by
  530. # vprint_status and friends as well.
  531. def build_brute_message(host_ip,host_port,proto,msg,legacy)
  532. ip = host_ip.to_s.strip if host_ip
  533. port = host_port.to_s.strip if host_port
  534. complete_message = nil
  535. extracted_message = nil
  536. if legacy # TODO: This is all a workaround until I get a chance to get rid of the legacy messages
  537. old_msg = msg.to_s.strip
  538. msg_regex = /(#{ip})(:#{port})?(\s*-?\s*)(#{proto.to_s})?(\s*-?\s*)(.*)/ni
  539. if old_msg.match(msg_regex) and !old_msg.match(msg_regex)[6].to_s.strip.empty?
  540. complete_message = ''
  541. complete_message << (old_msg.match(msg_regex)[4] || proto).to_s
  542. complete_message << " - "
  543. progress = tried_over_total(ip,port)
  544. complete_message << progress if progress
  545. complete_message << old_msg.match(msg_regex)[6].to_s.strip
  546. else
  547. complete_message = msg.to_s.strip
  548. end
  549. else
  550. complete_message = ''
  551. complete_message << "#{proto.to_s.strip} - " if proto
  552. progress = tried_over_total(ip,port)
  553. complete_message << progress if progress
  554. complete_message << msg.to_s.strip
  555. end
  556. end
  557. # Takes a credentials array, and returns just the first X involving
  558. # a particular user.
  559. def adjust_credentials_by_max_user(credentials)
  560. max = datastore['MaxGuessesPerUser'].to_i.abs
  561. if max == 0
  562. new_credentials = credentials
  563. else
  564. print_brute(
  565. :level => :vstatus,
  566. :msg => "Adjusting credentials by MaxGuessesPerUser (#{max})"
  567. )
  568. user_count = {}
  569. new_credentials = []
  570. credentials.each do |u,p|
  571. user_count[u] ||= 0
  572. user_count[u] += 1
  573. next if user_count[u] > max
  574. new_credentials << [u,p]
  575. end
  576. end
  577. return new_credentials
  578. end
  579. # Fun trick: Only prints if we're already in each_user_pass, since
  580. # only then is @@max_per_service defined.
  581. def tried_over_total(ip,port)
  582. total = self.class.class_variable_get("@@max_per_service") rescue nil
  583. return unless total
  584. total = total.to_i
  585. current_try = (@@guesses_per_service["#{ip}:#{port}"] || 1).to_i
  586. pad = total.to_s.size
  587. "[%0#{pad}d/%0#{pad}d] - " % [current_try, total]
  588. end
  589. # Protocols can nearly always be automatically determined from the
  590. # name of the module, assuming the name is sensible like ssh_login or
  591. # smb_auth.
  592. def proto_from_fullname
  593. File.split(self.fullname).last.match(/^(.*)_(login|auth|identify)/)[1].upcase rescue nil
  594. end
  595. # Legacy vprint
  596. def vprint_status(msg='')
  597. print_brute :level => :vstatus, :legacy_msg => msg
  598. end
  599. # Legacy vprint
  600. def vprint_error(msg='')
  601. print_brute :level => :verror, :legacy_msg => msg
  602. end
  603. # Legacy vprint
  604. def vprint_good(msg='')
  605. print_brute :level => :vgood, :legacy_msg => msg
  606. end
  607. # This method deletes the dictionary files if requested
  608. def cleanup_files
  609. path = datastore['USERPASS_FILE']
  610. if path and datastore['REMOVE_USERPASS_FILE']
  611. ::File.unlink(path) rescue nil
  612. end
  613. path = datastore['USER_FILE']
  614. if path and datastore['REMOVE_USER_FILE']
  615. ::File.unlink(path) rescue nil
  616. end
  617. path = datastore['PASS_FILE']
  618. if path and datastore['REMOVE_PASS_FILE']
  619. ::File.unlink(path) rescue nil
  620. end
  621. end
  622. end
  623. end