Una sesión RDP necesita una dirección de cliente, y las fuentes obvias no coinciden. CLIENTNAME da un nombre de equipo, el registro contiene valores de otras sesiones, y el extremo del socket puede ser una dirección de puerta de enlace o NAT en lugar del cliente. Windows sí expone una dirección de cliente informada por sesión, pero hay que pedir la sesión correcta y decodificar la respuesta correctamente.

Ancle la consulta a una sola sesión

Empiece por la sesión, no por la persona. Un host RDP de Windows puede albergar varias sesiones para una cuenta, y una dirección vista antes de una reconexión puede que ya no corresponda al enlace activo. Registre el ID de sesión, el usuario y el momento de observación juntos. El ID de sesión es la clave que espera la API, y es el valor que evita confundir dos sesiones.

En el host, las herramientas de sesión instaladas enumeran lo que existe; use la vista administrativa solo cuando tenga derecho a ella. Confirme el ID elegido justo antes de la consulta, porque un ID reciclado devuelve los datos de otra sesión.

qwinsta

Llame a WTSQuerySessionInformationW para ese ID

La API de Servicios de Terminal responde a la pregunta con la clase de información 14, WTSClientAddress. Un pequeño envoltorio de ctypes basta en Windows, y evita leer el hive de otro usuario. El código de abajo toma un ID de sesión explícito y pregunta al host local.

import ctypes
import ipaddress
import socket
from ctypes import wintypes

class WTS_CLIENT_ADDRESS(ctypes.Structure):
    _fields_ = [("AddressFamily", wintypes.DWORD),
                ("Address", ctypes.c_ubyte * 20)]

wts = ctypes.WinDLL("Wtsapi32", use_last_error=True)
query = wts.WTSQuerySessionInformationW
query.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.c_int,
                  ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(wintypes.DWORD)]
query.restype = wintypes.BOOL
free = wts.WTSFreeMemory
free.argtypes = [ctypes.c_void_p]

def client_address(session_id):
    buf = ctypes.c_void_p()
    size = wintypes.DWORD()
    if not query(None, session_id, 14, ctypes.byref(buf), ctypes.byref(size)):
        raise ctypes.WinError(ctypes.get_last_error())
    try:
        if size.value < ctypes.sizeof(WTS_CLIENT_ADDRESS):
            raise RuntimeError("short WTS buffer")
        value = ctypes.cast(buf, ctypes.POINTER(WTS_CLIENT_ADDRESS)).contents
        raw = bytes(value.Address)
        if value.AddressFamily == socket.AF_INET:
            addr = ipaddress.ip_address(raw[2:6])
        elif value.AddressFamily == socket.AF_INET6:
            addr = ipaddress.ip_address(raw[:16])
        else:
            return None
        return None if addr.is_unspecified else str(addr)
    finally:
        free(buf)

Pase el ID de sesión entero que acaba de verificar y conserve el identificador de servidor nulo para la máquina local. Trate una llamada fallida como un error y no como una dirección ausente, porque ambas cosas llevan a conclusiones distintas. Consultar la sesión de otro usuario requiere el permiso Query Information adecuado, así que ejecute la herramienta con una cuenta a la que se le haya concedido en lugar de escalar de forma ad hoc. El búfer devuelto pertenece a la API y debe liberarse incluso cuando la decodificación falla.

Decodifique la familia y luego libere el búfer

La estructura no es una cadena IP. Su campo de familia decide cómo se leen los veinte bytes de dirección: IPv4 empieza en el desplazamiento dos, mientras que IPv6 empieza en el desplazamiento cero. La dirección no especificada significa que el cliente no informó ninguna, lo que no es lo mismo que una cadena vacía. Las conexiones a través de Remote Desktop Gateway están documentadas como un caso en el que la dirección no está disponible, así que una sesión mediada por puerta de enlace no informa nada útil sobre la dirección real del cliente. La correlación debe pasar entonces a los registros propios de la puerta de enlace.

Imprima la familia junto a la dirección mientras depura, porque una discrepancia entre ambas es la causa habitual de un valor verosímil pero incorrecto. Libere el búfer con WTSFreeMemory en un bloque finally para que una excepción no lo pierda.

Separe la dirección informada de la prueba

Una dirección de cliente informada es una afirmación que aporta el cliente, no una observación fiable. No es el extremo TCP, ni la tabla de concesiones de la puerta de enlace VPN, ni la dirección pública del cliente. Cuando la pregunta real es qué concesión VPN pertenece a una persona, correlacione el registro de sesión autenticado del servidor VPN al mismo tiempo. Cuando la pregunta es qué origen llegó al oyente, lea la tabla de conexiones o los registros permitidos del lado del servidor.

Mantenga estos valores etiquetados por separado en cualquier herramienta de soporte. Un resultado WTS ausente debe seguir ausente en lugar de recurrir a un valor de registro arbitrario de otra sesión. Registre el ID de sesión junto a la dirección, porque el mismo usuario puede reconectarse y recibir un valor nuevo. Ni el nombre del cliente ni la dirección informada son prueba suficiente para conceder acceso.

Artículos relacionados