Skip to content

Commit f2a8d68

Browse files
author
RageLtMan
committed
Permit encrypted SSH keys for login scanner
Net::SSH::KeyFactory permits loading keys using a passphrase. The Framework SSH modules were implemented back when we had a fork of net-ssh in our tree, and can now use functionality provided by the upstream gem. Update the ssh key login scanner to add a KEY_PASS datastore OptString which is then passed to the KeyCollection class and used in the updated :read_key method which now calls the KeyFactory to read data and give us the appropriate String representation of the key in the KeyCollection's cache. A bit of cleanup performed as well, removing legacy code paths no longer hit by the module. Shamelessly added self to authors, fair amount of blood and sweat in the SSH subsystem over the years, hope nobody objects. Testing: None yet
1 parent 8c2c30c commit f2a8d68

File tree

1 file changed

+8
-85
lines changed

1 file changed

+8
-85
lines changed

modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb

Lines changed: 8 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,19 @@ def initialize
2828
this module will record successful logins and hosts so you can
2929
track your access.
3030
31-
Note that password-protected key files will not function with this
32-
module -- it is designed specifically for unencrypted (passwordless)
33-
keys.
34-
35-
Key files may be a single private (unencrypted) key, or several private
36-
keys concatenated together as an ASCII text file. Non-key data should be
37-
silently ignored.
31+
Key files may be a single private key, or several private keys in a single
32+
directory. Only a single passphrase is supported however, so it must either
33+
be shared between subject keys or only belong to a single one.
3834
},
39-
'Author' => ['todb'],
35+
'Author' => ['todb', 'RageLtMan'],
4036
'License' => MSF_LICENSE
4137
)
4238

4339
register_options(
4440
[
4541
Opt::RPORT(22),
4642
OptPath.new('KEY_PATH', [true, 'Filename or directory of cleartext private keys. Filenames beginning with a dot, or ending in ".pub" will be skipped.']),
43+
OptString.new('KEY_PASS', [false, 'Passphrase for SSH private key(s)']),
4744
], self.class
4845
)
4946

@@ -63,10 +60,6 @@ module -- it is designed specifically for unencrypted (passwordless)
6360

6461
end
6562

66-
def key_dir
67-
datastore['KEY_DIR']
68-
end
69-
7063
def rport
7164
datastore['RPORT']
7265
end
@@ -75,71 +68,6 @@ def ip
7568
datastore['RHOST']
7669
end
7770

78-
def read_keyfile(file)
79-
if file == :keyfile_b64
80-
keyfile = datastore['SSH_KEYFILE_B64'].unpack("m*").first
81-
elsif file.kind_of? Array
82-
keyfile = ''
83-
file.each do |dir_entry|
84-
next unless File.readable? dir_entry
85-
keyfile << File.open(dir_entry, "rb") {|f| f.read(f.stat.size)}
86-
end
87-
else
88-
keyfile = File.open(file, "rb") {|f| f.read(f.stat.size)}
89-
end
90-
keys = []
91-
this_key = []
92-
in_key = false
93-
keyfile.split("\n").each do |line|
94-
in_key = true if(line =~ /^-----BEGIN [RD]SA PRIVATE KEY-----/)
95-
this_key << line if in_key
96-
if(line =~ /^-----END [RD]SA PRIVATE KEY-----/)
97-
in_key = false
98-
keys << (this_key.join("\n") + "\n")
99-
this_key = []
100-
end
101-
end
102-
if keys.empty?
103-
print_error "#{ip}:#{rport} SSH - No keys found."
104-
end
105-
return validate_keys(keys)
106-
end
107-
108-
# Validates that the key isn't total garbage. Also throws out SSH2 keys --
109-
# can't use 'em for Net::SSH.
110-
def validate_keys(keys)
111-
keepers = []
112-
keys.each do |key|
113-
# Needs a beginning
114-
next unless key =~ /^-----BEGIN [RD]SA PRIVATE KEY-----\x0d?\x0a/m
115-
# Needs an end
116-
next unless key =~ /\n-----END [RD]SA PRIVATE KEY-----\x0d?\x0a?$/m
117-
# Shouldn't have binary.
118-
next unless key.scan(/[\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff]/).empty?
119-
# Add more tests to taste.
120-
keepers << key
121-
end
122-
if keepers.empty?
123-
print_error "#{ip}:#{rport} SSH - No valid keys found"
124-
end
125-
return keepers
126-
end
127-
128-
def pull_cleartext_keys(keys)
129-
cleartext_keys = []
130-
keys.each do |key|
131-
next unless key
132-
next if key =~ /Proc-Type:.*ENCRYPTED/
133-
this_key = key.gsub(/\x0d/,"")
134-
next if cleartext_keys.include? this_key
135-
cleartext_keys << this_key
136-
end
137-
if cleartext_keys.empty?
138-
print_error "#{ip}:#{rport} SSH - No valid cleartext keys found"
139-
end
140-
return cleartext_keys
141-
end
142-
14371
def session_setup(result, ssh_socket, fingerprint)
14472
return unless ssh_socket
14573

@@ -196,6 +124,7 @@ def run_host(ip)
196124

197125
keys = KeyCollection.new(
198126
key_path: datastore['KEY_PATH'],
127+
password: datastore['KEY_PASS'],
199128
user_file: datastore['USER_FILE'],
200129
username: datastore['USERNAME'],
201130
)
@@ -289,7 +218,7 @@ def valid!
289218
end
290219

291220
def valid_key?(key_data)
292-
!!(key_data.match(/BEGIN [RD]SA PRIVATE KEY/) && !key_data.match(/Proc-Type:.*ENCRYPTED/))
221+
!!(key_data.match(/BEGIN [RECD]SA PRIVATE KEY/) && !key_data.match(/Proc-Type:.*ENCRYPTED/))
293222
end
294223

295224
def each
@@ -321,13 +250,7 @@ def each_key
321250

322251
def read_key(filename)
323252
@cache ||= {}
324-
unless @cache[filename]
325-
data = File.open(filename, 'rb') { |fd| fd.read(fd.stat.size) }
326-
#if data.match
327-
328-
@cache[filename] = data
329-
end
330-
253+
@cache[filename] ||= Net::SSH::KeyFactory.load_data_private_key(File.read(key_path), password, false, key_path).to_s
331254
@cache[filename]
332255
end
333256

0 commit comments

Comments
 (0)