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'
- DEFAULT_QUEUE_SIZE =
100
- LIKELY_PORTS =
[ 161, 162 ]
- LIKELY_SERVICE_NAMES =
[ 'snmp' ]
- PRIVATE_TYPES =
[ :password ]
- REALM_KEY =
nil
Instance Attribute Summary collapse
-
#queue_size ⇒ Integer
The number of logins to try in each batch.
-
#queued_credentials ⇒ Object
:nodoc:.
-
#queued_results ⇒ Object
:nodoc:.
-
#sock ⇒ Object
: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
35 36 37 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 35 def queue_size @queue_size end |
#queued_credentials ⇒ Object
:nodoc:
25 26 27 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 25 def queued_credentials @queued_credentials end |
#queued_results ⇒ Object
:nodoc:
26 27 28 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 26 def queued_results @queued_results end |
#sock ⇒ Object
:nodoc:
27 28 29 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 27 def sock @sock end |
#version ⇒ String
The SNMP version to scan
31 32 33 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 31 def version @version end |
Instance Method Details
#configure_socket ⇒ Object
Create a new socket for this scanner
320 321 322 323 324 325 326 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 320 def configure_socket shutdown_socket if self.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
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 262 def create_snmp_read_sys_descr_request(version_str, community) version = version_str == '1' ? 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
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 282 def create_snmp_write_sys_descr_request(version_str, community, data) version = version_str == '1' ? 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
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 302 def parse_snmp_response(pkt) asn = OpenSSL::ASN1.decode(pkt) rescue nil return if not asn snmp_vers = asn.value[0].value.to_i rescue nil snmp_comm = asn.value[1].value rescue nil snmp_error = asn.value[2].value[1].value.to_i rescue nil snmp_data = asn.value[2].value[3].value[0] rescue nil snmp_oid = snmp_data.value[0].value rescue nil snmp_info = snmp_data.value[1].value.to_s rescue nil return if not (snmp_error and snmp_comm and snmp_data and snmp_oid and 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
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 170 def process_logins(opts={}) self.queued_results ||= [] self.queued_credentials ||= [] unless opts[:final] || self.queued_credentials.length > self.queue_size self.queued_credentials.push [ opts[:type], opts[:community], opts[:version], opts[:data] ] return end return if self.queued_credentials.length == 0 process_responses(0.01) while self.queued_credentials.length > 0 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
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 225 226 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 197 def process_responses(timeout=1.0) queue = [] while (res = sock.recvfrom(65535, timeout)) # Ignore invalid responses break if not res[1] # Ignore empty responses next if not (res[0] and res[0].length > 0) # 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.
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 166 167 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 68 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 && self.queued_results.length > 0 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 = self.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 self.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 shutdown_socket nil end |
#send_snmp_read_request(version, community) ⇒ Object
Create and send a SNMP read request for sys.sysDescr.0
229 230 231 232 233 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 229 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
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 243 def send_snmp_request(pkt) resend_count = 0 begin sock.sendto(pkt, self.host, self.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
236 237 238 239 240 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 236 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
335 336 337 338 339 340 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 335 def set_sane_defaults self.connection_timeout = DEFAULT_TIMEOUT if self.connection_timeout.nil? self.port = DEFAULT_PORT if self.port.nil? self.version = DEFAULT_VERSION if self.version.nil? self.queue_size = DEFAULT_QUEUE_SIZE if self.queue_size.nil? end |
#shutdown_socket ⇒ Object
Close any open socket if it exists
329 330 331 332 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 329 def shutdown_socket self.sock.close if self.sock self.sock = nil end |
#versions ⇒ Array
This method returns an array of versions to scan
52 53 54 55 56 57 58 59 60 61 |
# File 'lib/metasploit/framework/login_scanner/snmp.rb', line 52 def versions case version when '1' [:SNMPv1] when '2c' [:SNMPv2c] when 'all' [:SNMPv1, :SNMPv2c] end end |