Skip to content

Commit d1d6378

Browse files
committed
Land rapid7#4566, Misfortune Cookie scanner improvements
2 parents 495a8f3 + a5e14d5 commit d1d6378

File tree

1 file changed

+143
-16
lines changed

1 file changed

+143
-16
lines changed

modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb

Lines changed: 143 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,38 +27,165 @@ def initialize(info = {})
2727
],
2828
'References' => [
2929
['CVE', '2014-9222'],
30-
['URL', 'http://mis.fortunecook.ie']
30+
['URL', 'http://mis.fortunecook.ie'],
31+
['URL', 'http://mis.fortunecook.ie/misfortune-cookie-suspected-vulnerable.pdf'], # list of likely vulnerable devices
32+
['URL', 'http://mis.fortunecook.ie/too-many-cooks-exploiting-tr069_tal-oppenheim_31c3.pdf'] # 31C3 presentation with POC
3133
],
3234
'DisclosureDate' => 'Dec 17 2014',
3335
'License' => MSF_LICENSE
3436
))
3537

36-
register_options([
37-
OptString.new('TARGETURI', [true, 'Path to fingerprint RomPager from', '/Allegro'])
38-
], self.class)
38+
register_options(
39+
[
40+
OptString.new('TARGETURI', [true, 'URI to test', '/'])
41+
], Exploit::Remote::HttpClient
42+
)
43+
44+
register_advanced_options(
45+
[
46+
OptString.new('CANARY_URI', [false, 'Try overwriting the requested URI with this canary value (empty for random)']),
47+
OptString.new('STATUS_CODES_REGEX', [true, 'Ensure that canary pages and probe responses have status codes that match this regex', '^40[134]$'])
48+
], self.class
49+
)
50+
end
51+
52+
def check_host(_ip)
53+
begin
54+
test_misfortune
55+
ensure
56+
disconnect
57+
end
58+
end
59+
60+
def run_host(ip)
61+
status = check_host(ip)
62+
case status
63+
when Exploit::CheckCode::Appears
64+
when Exploit::CheckCode::Detected
65+
when Exploit::CheckCode::Vulnerable
66+
print_good("#{peer} #{status.last}")
67+
else
68+
vprint_status("#{peer} #{status.last}")
69+
end
70+
end
71+
72+
def setup
73+
@status_codes_regex = Regexp.new(datastore['STATUS_CODES_REGEX'])
3974
end
4075

41-
def check_host(ip)
42-
res = send_request_cgi('uri' => normalize_uri(target_uri.path.to_s), 'method' => 'GET')
76+
# Fingerprints the provided HTTP response and returns
77+
# Exploit::CheckCode::Appears if it is a vulnerable version of RomPager,
78+
# otherwise returns the provided fall-back status.
79+
def check_response_fingerprint(res, fallback_status)
4380
fp = http_fingerprint(response: res)
44-
if /RomPager\/(?<version>[\d\.]+)$/ =~ fp
81+
if /RomPager\/(?<version>[\d\.]+)/ =~ fp
82+
vprint_status("#{peer} is RomPager #{version}")
4583
if Gem::Version.new(version) < Gem::Version.new('4.34')
84+
return Exploit::CheckCode::Appears
85+
end
86+
end
87+
fallback_status
88+
end
89+
90+
def find_canary
91+
vprint_status("#{peer} locating suitable canary URI")
92+
canaries = []
93+
if datastore['CANARY_URI']
94+
canaries << datastore['CANARY_URI']
95+
else
96+
# several random URIs in the hopes that one, generally the first, will be usable
97+
0.upto(4) { canaries << '/' + Rex::Text.rand_text_alpha(16) }
98+
end
99+
100+
canaries.each do |canary|
101+
res = send_request_raw(
102+
'uri' => normalize_uri(canary),
103+
'method' => 'GET',
104+
'headers' => headers
105+
)
106+
# in most cases, the canary URI will not exist and will return a 404, but
107+
# if everything under TARGETURI is protected by auth, a 401 may be OK too.
108+
# but, regardless, respect the configuration set for this module
109+
return [canary, res.code] if res && res.code.to_s =~ @status_codes_regex
110+
end
111+
nil
112+
end
113+
114+
def headers
115+
{
116+
'Referer' => full_uri
117+
}
118+
end
119+
120+
# To test for this vulnerability, we must first find a URI known to return
121+
# a 404 (not found) which we will use as a canary. This URI (for example,
122+
# /foo) is then taken and used as the value for a carefully crafted cookie
123+
# when making a request to the configured host+port+uri. If the response
124+
# is a 404 and the body includes the canary, it is likely that the cookie
125+
# overwrote RomPager's concept of the requested URI, indicating that it is
126+
# vulnerable.
127+
def test_misfortune
128+
# find a usable canary URI (one that returns an acceptable status code already)
129+
if canary = find_canary
130+
canary_value, canary_code = canary
131+
vprint_status("#{peer} found canary URI #{canary_value} with code #{canary_code}")
132+
else
133+
vprint_error("#{peer} Unable to find a suitable canary URI")
134+
return Exploit::CheckCode::Unknown
135+
end
136+
137+
canary_cookie_name = 'C107373883'
138+
canary_cookie = canary_cookie_name + "=#{canary_value};"
139+
140+
# Make a request containing a specific canary cookie name with the value set
141+
# from the suitable canary value found above.
142+
res = send_request_raw(
143+
'uri' => normalize_uri(target_uri.path.to_s),
144+
'method' => 'GET',
145+
'headers' => headers.merge('Cookie' => canary_cookie)
146+
)
147+
148+
unless res
149+
vprint_error("#{full_uri} no response")
150+
return Exploit::CheckCode::Unknown
151+
end
152+
153+
unless res.code.to_s =~ @status_codes_regex
154+
vprint_status("#{full_uri} unexpected HTTP code #{res.code} response")
155+
return check_response_fingerprint(res, Exploit::CheckCode::Detected)
156+
end
157+
158+
unless res.body
159+
vprint_status("#{full_uri} HTTP code #{res.code} had no body")
160+
return check_response_fingerprint(res, Exploit::CheckCode::Detected)
161+
end
162+
163+
# If that canary *value* shows up in the *body*, then there are two possibilities:
164+
#
165+
# 1) If the canary cookie *name* is also in the *body*, it is likely that
166+
# the endpoint is puppeting back our request to some extent and therefore
167+
# it is expected that the canary cookie *value* would also be there.
168+
# return Exploit::CheckCode::Detected
169+
#
170+
# 2) If the canary cookie *name* is *not* in the *body*, return
171+
# Exploit::CheckCode::Vulnerable
172+
if res.body.include?(canary_value)
173+
if res.body.include?(canary_cookie_name)
174+
vprint_status("#{full_uri} HTTP code #{res.code} response contained canary cookie name #{canary_cookie_name}")
175+
return check_response_fingerprint(res, Exploit::CheckCode::Detected)
176+
else
177+
vprint_good("#{full_uri} HTTP code #{res.code} response contained canary cookie value #{canary_value} as URI")
46178
report_vuln(
47-
host: ip,
179+
host: rhost,
48180
port: rport,
49181
name: name,
50182
refs: references
51183
)
52-
return Exploit::CheckCode::Appears
53-
else
54-
return Exploit::CheckCode::Detected
184+
return Exploit::CheckCode::Vulnerable
55185
end
56-
else
57-
return Exploit::CheckCode::Safe
58186
end
59-
end
60187

61-
def run_host(ip)
62-
print_good("#{peer} appears to be vulnerable") if check_host(ip) == Exploit::CheckCode::Appears
188+
vprint_status("#{full_uri} HTTP code #{res.code} response did not contain canary cookie value #{canary_value} as URI")
189+
check_response_fingerprint(res, Exploit::CheckCode::Safe)
63190
end
64191
end

0 commit comments

Comments
 (0)