AIGC标识 RDP网络身份验证

import socket
import struct
import sys

def check_rdp_nla(target_ip, port=3389, timeout=5):
    """
    检测目标 RDP 服务是否启用了网络级身份验证 (NLA)。
    
    返回:
        dict: {
            'status': 'open' | 'closed' | 'error',
            'nla_enabled': True | False | None,
            'supported_protocols': list, # ['RDP', 'SSL', 'NLA']
            'message': str
        }
    """
    result = {
        'status': 'error',
        'nla_enabled': None,
        'supported_protocols': [],
        'message': ''
    }

    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        sock.connect((target_ip, port))
        result['status'] = 'open'

        # 1. 构建 RDP Negotiation Request (Client -> Server)
        # TPKT Header (4 bytes)
        # X.224 Connection Request (variable)
        # RDP Negotiation Request (optional but recommended for detection)
        
        # RDP Negotiation Request Structure:
        # Type: 0x01 (TYPE_RDP_NEG_REQ)
        # Flags: 0x00
        # Length: 0x08 (LSB), 0x00 (MSB)
        # Protocols: 0x03 (PROTOCOL_RDP | PROTOCOL_SSL) - 我们声明支持这两种,看服务器回什么
        # 如果要测试服务器是否强制 NLA,我们可以声明支持 NLA (0x02) 或者不声明。
        # 最佳实践:声明支持所有常见协议 (RDP | SSL | HYBRID),看服务器选择哪个。
        # PROTOCOL_RDP=0x00, PROTOCOL_SSL=0x01, PROTOCOL_HYBRID(NLA)=0x02
        
        supported_protocols_client = 0x03 # RDP | SSL
        
        neg_req = struct.pack('<BBHI', 
            0x01,       # Type: RDP_NEG_REQ
            0x00,       # Flags
            0x0008,     # Length: 8 bytes
            supported_protocols_client # Protocols
        )
        
        # X.224 Connection Request PDU
        # Length: variable (will be calculated)
        # Type: 0xE0 (Connection Request)
        # dst_ref: 0x0000
        # src_ref: 0x0000
        # class: 0x00
        
        x224_cr = struct.pack('<BBHHB', 
            0x00,       # Length MSB (placeholder)
            0x00,       # Length LSB (placeholder)
            0x0000,     # dst_ref
            0x0000,     # src_ref
            0x00        # class
        )
        # Prepend X.224 Type (0xE0) which is part of the PDU but often handled separately in simple structs
        # Actually, standard X.224 CR:
        # Byte 0: Length (including this byte)
        # Byte 1: Type (0xE0)
        # Byte 2-3: dst_ref
        # Byte 4-5: src_ref
        # Byte 6: class
        
        x224_header = bytearray([
            0x00,       # Length MSB (placeholder)
            0x00,       # Length LSB (placeholder)
            0xe0,       # Type: Connection Request
            0x00, 0x00, # dst_ref
            0x00, 0x00, # src_ref
            0x00        # class
        ])
        
        # Combine X.224 Header + RDP Neg Req
        payload = bytes(x224_header) + neg_req
        
        # Calculate total length for X.224 and TPKT
        x224_len = len(payload)
        tpkt_len = x224_len + 4 # TPKT header is 4 bytes
        
        # Update X.224 Length (Bytes 0-1 of x224_header are at index 0-1 of payload)
        # X.224 length includes the X.224 header itself but NOT the TPKT header? 
        # Wait, TPKT wraps X.224. 
        # TPKT Length field covers the entire TPKT packet (including TPKT header).
        # X.224 Length field covers the X.224 PDU (including X.224 header).
        
        # Update X.224 Length in payload
        payload[0] = (x224_len >> 8) & 0xFF
        payload[1] = x224_len & 0xFF
        
        # Build TPKT Header
        tpkt_header = struct.pack('>BBH', 
            0x03,       # Version
            0x00,       # Reserved
            tpkt_len    # Length
        )
        
        final_packet = tpkt_header + payload
        
        # 2. 发送请求
        sock.send(final_packet)
        
        # 3. 接收响应
        response = sock.recv(1024)
        
        if not response:
            result['message'] = "No response received"
            return result
            
        # 4. 解析响应
        # 期望收到 TPKT -> X.224 Connect Confirm -> RDP Negotiation Response
        
        if len(response) < 11:
            result['message'] = "Response too short to parse"
            return result
            
        # Parse TPKT
        tpkt_ver = response[0]
        tpkt_len = struct.unpack('>H', response[2:4])[0]
        
        # Parse X.224
        x224_type = response[5] # Index 5 because TPKT is 4 bytes, X.224 type is the 2nd byte of X.224 PDU? 
        # Let's look at structure:
        # TPKT: [Ver(1)][Res(1)][Len(2)]
        # X.224: [Len(2)][Type(1)][DstRef(2)][SrcRef(2)][Class(1)]
        # So X.224 Type is at index 4+2 = 6? 
        # TPKT: 0,1,2,3
        # X.224 Len: 4,5
        # X.224 Type: 6
        
        x224_type = response[6]
        
        if x224_type != 0xd0: # 0xD0 is Connect Confirm
            # Might be a Disconnect or Error
            result['message'] = f"Unexpected X.224 type: {hex(x224_type)}"
            return result
            
        # Check for RDP Negotiation Response
        # It should follow immediately after X.224 header
        # X.224 header is 7 bytes (Len 2 + Type 1 + Dst 2 + Src 2 + Class 1) -- Wait, standard X.224 CR/CC is 7 bytes?
        # Len(2) + Type(1) + Dst(2) + Src(2) + Class(1) = 8 bytes? 
        # Let's re-verify: 
        # My payload construction: 
        # x224_header: [LenMSB, LenLSB, 0xE0, 0,0, 0,0, 0] -> 8 bytes.
        # So X.224 PDU starts at index 4 of response.
        # X.224 Type is at index 4+2 = 6. Correct.
        # X.224 PDU length is 8 bytes.
        # So RDP Neg Resp should start at index 4 + 8 = 12.
        
        if len(response) >= 15: # 4(TPKT) + 8(X.224) + 3(min RDP neg)
            rdp_neg_type = response[12]
            if rdp_neg_type == 0x02: # TYPE_RDP_NEG_RSP
                flags = response[13]
                length = struct.unpack('<H', response[14:16])[0]
                protocol = struct.unpack('<I', response[16:20])[0]
                
                protocols_supported = []
                if protocol & 0x00: # PROTOCOL_RDP
                    protocols_supported.append('RDP')
                if protocol & 0x01: # PROTOCOL_SSL
                    protocols_supported.append('SSL')
                if protocol & 0x02: # PROTOCOL_HYBRID (NLA)
                    protocols_supported.append('NLA')
                    
                result['supported_protocols'] = protocols_supported
                
                # 判断 NLA 是否启用/被选择
                # 如果服务器选择了 PROTOCOL_HYBRID (0x02),则必须使用 NLA
                if protocol == 0x02 or protocol == 0x08: # 0x08 is HYBRID_EX
                    result['nla_enabled'] = True
                    result['message'] = "NLA is required/enabled"
                elif protocol == 0x01:
                    result['nla_enabled'] = False
                    result['message'] = "SSL selected, NLA not strictly enforced in negotiation"
                elif protocol == 0x00:
                    result['nla_enabled'] = False
                    result['message'] = "Standard RDP security selected"
                else:
                    result['nla_enabled'] = False
                    result['message'] = f"Unknown protocol selected: {hex(protocol)}"
                    
            else:
                result['message'] = f"No RDP Negotiation Response found (Type: {hex(rdp_neg_type)})"
        else:
            result['message'] = "Response too short for RDP Negotiation"

        sock.close()

    except socket.timeout:
        result['status'] = 'closed'
        result['message'] = "Connection timed out"
    except ConnectionRefusedError:
        result['status'] = 'closed'
        result['message'] = "Connection refused"
    except Exception as e:
        result['status'] = 'error'
        result['message'] = str(e)

    return result

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python check_rdp_nla.py <target_ip> [port]")
        sys.exit(1)
        
    ip = sys.argv[1]
    port = int(sys.argv[2]) if len(sys.argv) > 2 else 3389
    
    print(f"[*] Checking RDP NLA status for {ip}:{port} ...")
    res = check_rdp_nla(ip, port)
    
    print(f"Status: {res['status']}")
    print(f"NLA Enabled: {res['nla_enabled']}")
    print(f"Supported Protocols: {res['supported_protocols']}")
    print(f"Details: {res['message']}")

  

posted @ 2026-10-08 16:25  LalaTina  阅读(8)  评论(0)    收藏  举报