Unha sesión RDP necesita un enderezo de cliente, e as fontes obvias non coinciden. CLIENTNAME dá un nome de equipo, o rexistro contén valores doutras sesións, e o extremo do socket pode ser un enderezo de pasarela ou NAT en lugar do cliente. Windows si expón un enderezo de cliente informado por sesión, pero hai que pedir a sesión correcta e decodificar a resposta correctamente.

Ancore a consulta nunha soa sesión

Comece pola sesión, non pola persoa. Un host RDP de Windows pode levar varias sesións para unha conta, e un enderezo visto antes dunha reconexión pode que xa non corresponda á ligazón activa. Anote o ID de sesión, o usuario e o momento de observación xuntos. O ID de sesión é a clave que espera a API, e é o valor que evita confundir dúas sesións.

No host, as ferramentas de sesión instaladas listan o que existe; use a vista administrativa só cando teña dereito a ela. Confirme o ID escollido xusto antes da consulta, porque un ID reciclado devolve os datos doutra sesión.

qwinsta

Chame a WTSQuerySessionInformationW para ese ID

A API de Servizos de Terminal responde á pregunta coa clase de información 14, WTSClientAddress. Un pequeno envoltorio de ctypes abonda en Windows, e evita ler o hive doutro usuario. O código de abaixo toma un ID de sesión explícito e pregunta ao 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 o ID de sesión enteiro que acaba de verificar e conserve o identificador de servidor nulo para a máquina local. Trate unha chamada fallida como un erro e non como un enderezo ausente, porque ambas as cousas levan a conclusións distintas. Consultar a sesión doutro usuario require o permiso Query Information axeitado, así que execute a ferramenta cunha conta á que se lle concedese en lugar de escalar de forma ad hoc. O búfer devolto pertence á API e debe liberarse mesmo cando a decodificación falla.

Decodifique a familia e despois libere o búfer

A estrutura non é unha cadea IP. O seu campo de familia decide como se len os vinte bytes de enderezo: IPv4 comeza no desprazamento dous, mentres que IPv6 comeza no desprazamento cero. O enderezo non especificado significa que o cliente non informou ningún, o que non é o mesmo ca unha cadea baleira. As conexións a través de Remote Desktop Gateway están documentadas como un caso no que o enderezo non está dispoñible, así que unha sesión mediada por pasarela non informa nada útil sobre o enderezo real do cliente. A correlación debe pasar entón aos rexistros propios da pasarela.

Imprima a familia xunto ao enderezo mentres depura, porque unha discrepancia entre ambas é a causa habitual dun valor verosímil pero incorrecto. Libere o búfer con WTSFreeMemory nun bloque finally para que unha excepción non o perda.

Separe o enderezo informado da proba

Un enderezo de cliente informado é unha afirmación que achega o cliente, non unha observación fiable. Non é o extremo TCP, nin a táboa de concesións da pasarela VPN, nin o enderezo público do cliente. Cando a pregunta real é que concesión VPN pertence a unha persoa, correlacione o rexistro de sesión autenticado do servidor VPN ao mesmo tempo. Cando a pregunta é que orixe chegou ao oínte, lea a táboa de conexións ou os rexistros permitidos do lado do servidor.

Manteña estes valores etiquetados por separado en calquera ferramenta de soporte. Un resultado WTS ausente debe seguir ausente en lugar de recorrer a un valor de rexistro arbitrario doutra sesión. Rexistre o ID de sesión xunto ao enderezo, porque o mesmo usuario pode reconectarse e recibir un valor novo. Nin o nome do cliente nin o enderezo informado son proba suficiente para conceder acceso.

Artigos relacionados