Class: Metasploit::Framework::LoginScanner::SNMP
- Inherits:
-
Object
- Object
- Metasploit::Framework::LoginScanner::SNMP
- Includes:
- Base
- Defined in:
- lib/metasploit/framework/login_scanner/snmp.rb
Overview
This is the LoginScanner class for dealing with SNMP. It is responsible for taking a single target, and a list of credentials and attempting them. It then saves the results.
Constant Summary collapse
- DEFAULT_TIMEOUT =
2
- DEFAULT_PORT =
161
- DEFAULT_VERSION =
'1'.freeze
- DEFAULT_QUEUE_SIZE =
100
- LIKELY_PORTS =
[ 161, 162 ].freeze
- LIKELY_SERVICE_NAMES =
[ 'snmp' ].freeze
- PRIVATE_TYPES =
[ :password ].freeze
- REALM_KEY =
nil
Instance Attribute Summary collapse
-
#queue_size ⇒ Integer
The number of logins to try in each batch.
-
#queued_credentials ⇒ Object
:nodoc: # :nodoc: # :nodoc:.
-
#queued_results ⇒ Object
:nodoc: # :nodoc: # :nodoc:.
-
#sock ⇒ Object
:nodoc: # :nodoc: # :nodoc:.
-
#version ⇒ String
The SNMP version to scan.
Instance Method Summary collapse
-
#configure_socket ⇒ Object
Create a new socket for this scanner.
-
#create_snmp_read_sys_descr_request(version_str, community) ⇒ Object
Create a SNMP request that tries to read from sys.sysDescr.0.
-
#create_snmp_write_sys_descr_request(version_str, community, data) ⇒ Object
Create a SNMP request that tries to write to sys.sysDescr.0.
-
#parse_snmp_response(pkt) ⇒ Object
Parse a SNMP reply from a packet and return a response hash or nil.
-
#process_logins(opts = {}) ⇒ Object
Queue up and possibly send any requests, based on the queue limit and final flag.
-
#process_responses(timeout = 1.0) ⇒ Object
Process any responses on the UDP socket and queue the results.
-
#scan! {|result| ... } ⇒ void
Attempt to login with every credential in # #cred_details.
-
#send_snmp_read_request(version, community) ⇒ Object
Create and send a SNMP read request for sys.sysDescr.0.
-
#send_snmp_request(pkt) ⇒ Object
Send a SNMP request on the existing socket.
-
#send_snmp_write_request(version, community, data) ⇒ Object
Create and send a SNMP write request for sys.sysDescr.0.
-
#set_sane_defaults ⇒ Object
Sets the SNMP parameters if not specified.
-
#shutdown_socket ⇒ Object
Close any open socket if it exists.
-
#versions ⇒ Array
This method returns an array of versions to scan.
Instance Attribute Details
#queue_size ⇒ Integer
The number of logins to try in each batch
32 33 34 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 32 def queue_size @queue_size end |
#queued_credentials ⇒ Object
:nodoc: # :nodoc: # :nodoc:
24 25 26 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 24 def queued_credentials @queued_credentials end |
#queued_results ⇒ Object
:nodoc: # :nodoc: # :nodoc:
24 25 26 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 24 def queued_results @queued_results end |
#sock ⇒ Object
:nodoc: # :nodoc: # :nodoc:
24 25 26 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 24 def sock @sock end |
#version ⇒ String
The SNMP version to scan
28 29 30 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 28 def version @version end |
Instance Method Details
#configure_socket ⇒ Object
Create a new socket for this scanner
348 349 350 351 352 353 354 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 348 def configure_socket shutdown_socket if sock self.sock = ::Rex::Socket::Udp.create( 'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module } ) end |
#create_snmp_read_sys_descr_request(version_str, community) ⇒ Object
Create a SNMP request that tries to read from sys.sysDescr.0
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 261 def create_snmp_read_sys_descr_request(version_str, community) version = version_str == :SNMPv1 ? 1 : 2 OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(version - 1), OpenSSL::ASN1::OctetString(community), OpenSSL::ASN1::Set.new([ OpenSSL::ASN1::Integer(rand(0x80000000)), OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1.ObjectId('1.3.6.1.2.1.1.1.0'), OpenSSL::ASN1.Null(nil) ]) ]), ], 0, :IMPLICIT) ]).to_der end |
#create_snmp_write_sys_descr_request(version_str, community, data) ⇒ Object
Create a SNMP request that tries to write to sys.sysDescr.0
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 281 def create_snmp_write_sys_descr_request(version_str, community, data) version = version_str == :SNMPv1 ? 1 : 2 snmp_write = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(version - 1), OpenSSL::ASN1::OctetString(community), OpenSSL::ASN1::Set.new([ OpenSSL::ASN1::Integer(rand(0x80000000)), OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1.ObjectId('1.3.6.1.2.1.1.1.0'), OpenSSL::ASN1::OctetString(data) ]) ]), ], 3, :IMPLICIT) ]).to_der end |
#parse_snmp_response(pkt) ⇒ Object
Parse a SNMP reply from a packet and return a response hash or nil
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 301 def parse_snmp_response(pkt) asn = begin OpenSSL::ASN1.decode(pkt) rescue StandardError nil end return if !asn snmp_vers = begin asn.value[0].value.to_i rescue StandardError nil end snmp_comm = begin asn.value[1].value rescue StandardError nil end snmp_error = begin asn.value[2].value[1].value.to_i rescue StandardError nil end snmp_data = begin asn.value[2].value[3].value[0] rescue StandardError nil end snmp_oid = begin snmp_data.value[0].value rescue StandardError nil end snmp_info = begin snmp_data.value[1].value.to_s rescue StandardError nil end return if !(snmp_error && snmp_comm && snmp_data && snmp_oid && snmp_info) snmp_vers = snmp_vers == 0 ? '1' : '2c' { error: snmp_error, community: snmp_comm, proof: snmp_info, version: snmp_vers } end |
#process_logins(opts = {}) ⇒ Object
Queue up and possibly send any requests, based on the queue limit and final flag
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 168 def process_logins(opts = {}) self.queued_results ||= [] self.queued_credentials ||= [] unless opts[:final] || self.queued_credentials.length > queue_size self.queued_credentials.push [ opts[:type], opts[:community], opts[:version], opts[:data] ] return end return if self.queued_credentials.empty? process_responses(0.01) until self.queued_credentials.empty? action, community, version, data = self.queued_credentials.pop case action when 'read' send_snmp_read_request(version, community) when 'write' send_snmp_write_request(version, community, data) end sleep_between_attempts end process_responses(1.0) end |
#process_responses(timeout = 1.0) ⇒ Object
Process any responses on the UDP socket and queue the results
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 195 def process_responses(timeout = 1.0) queue = [] while (res = sock.recvfrom(65535, timeout)) # Ignore invalid responses break if !(res[1]) # Ignore empty responses next if !(res[0] && !res[0].empty?) # Trim the IPv6-compat prefix off if needed shost = res[1].sub(/^::ffff:/, '') response = parse_snmp_response(res[0]) next unless response self.queued_results << { community: response[:community], host: host, port: port, protocol: 'udp', service_name: 'snmp', proof: response[:proof], status: Metasploit::Model::Login::Status::SUCCESSFUL, access_level: 'read-only', snmp_version: response[:version], snmp_error: response[:error] } end end |
#scan! {|result| ... } ⇒ void
This method returns an undefined value.
Attempt to login with every credential in # #cred_details.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 65 def scan! valid! # Keep track of connection errors. # If we encounter too many, we will stop. consecutive_error_count = 0 total_error_count = 0 successful_users = Set.new first_attempt = true # Create a socket for the initial login tests (read-only) configure_socket # Create a map of community name to credential object credential_map = {} begin each_credential do |credential| # Track the credentials by community string credential_map[credential.public] = credential # Skip users for whom we've have already found a password if successful_users.include?(credential.public) # For Pro bruteforce Reuse and Guess we need to note that we # skipped an attempt. if credential.parent.respond_to?(:skipped) credential.parent.skipped = true credential.parent.save! end next end # Queue and trigger authentication if queue size is reached versions.each do |version| process_logins(community: credential.public, type: 'read', version: version) end # Exit early if we already have a positive result if stop_on_success && !queued_results.empty? break end end rescue Errno::ECONNREFUSED # Exit early if we get an ICMP port unreachable return end # Handle any unprocessed responses process_logins(final: true) # Create a non-duplicated set of credentials found_credentials = queued_results.uniq # Reset the queued results for our write test self.queued_results = [] # Grab a new socket to avoid stale replies configure_socket # Try to write back the originally received values found_credentials.each do |result| process_logins( version: result[:snmp_version], community: result[:community], type: 'write', data: result[:proof] ) end # Catch any stragglers process_logins(final: true) # Mark any results from our write scan as read-write in our found credentials queued_results.select { |r| [0, 17].include? r[:snmp_error] }.map { |r| r[:community] }.uniq.each do |c| found_credentials.select { |r| r[:community] == c }.each do |result| result[:access_level] = 'read-write' end end # Iterate the results found_credentials.each do || # Scrub the SNMP version & error code from the tracked result .delete(:snmp_version) .delete(:snmp_error) # Associate the community with the original credential [:credential] = credential_map[.delete(:community)] # In the rare chance that we got a result for a community we didn't scan... next unless [:credential] # Create, freeze, and yield the result result = ::Metasploit::Framework::LoginScanner::Result.new() result.freeze yield result if block_given? end nil ensure shutdown_socket end |
#send_snmp_read_request(version, community) ⇒ Object
Create and send a SNMP read request for sys.sysDescr.0
227 228 229 230 231 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 227 def send_snmp_read_request(version, community) send_snmp_request( create_snmp_read_sys_descr_request(version, community) ) end |
#send_snmp_request(pkt) ⇒ Object
Send a SNMP request on the existing socket
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 241 def send_snmp_request(pkt) resend_count = 0 begin sock.sendto(pkt, host, port, 0) rescue ::Errno::ENOBUFS resend_count += 1 if resend_count > MAX_RESEND_COUNT return false end ::IO.select(nil, nil, nil, 0.25) retry rescue ::Rex::ConnectionError # This fires for host unreachable, net unreachable, and broadcast sends # We can safely ignore all of these for UDP sends end end |
#send_snmp_write_request(version, community, data) ⇒ Object
Create and send a SNMP write request for sys.sysDescr.0
234 235 236 237 238 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 234 def send_snmp_write_request(version, community, data) send_snmp_request( create_snmp_write_sys_descr_request(version, community, data) ) end |
#set_sane_defaults ⇒ Object
Sets the SNMP parameters if not specified
363 364 365 366 367 368 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 363 def set_sane_defaults self.connection_timeout = DEFAULT_TIMEOUT if connection_timeout.nil? self.port = DEFAULT_PORT if port.nil? self.version = DEFAULT_VERSION if version.nil? self.queue_size = DEFAULT_QUEUE_SIZE if queue_size.nil? end |
#shutdown_socket ⇒ Object
Close any open socket if it exists
357 358 359 360 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 357 def shutdown_socket sock.close if sock self.sock = nil end |
#versions ⇒ Array
This method returns an array of versions to scan
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 49 def versions case version when '1' [:SNMPv1] when '2c' [:SNMPv2c] when 'all' %i[SNMPv1 SNMPv2c] end end |