Qual endereço o Windows relata para uma sessão RDP?
A API WTS retorna um endereço de cliente relatado para uma sessão RDP, e o registro não. Consulte o ID de sessão correto e decodifique IPv4 ou IPv6 corretamente.
Uma sessão RDP precisa de um endereço de cliente, e as fontes óbvias discordam. CLIENTNAME fornece um nome de computador, o registro contém valores de outras sessões, e o par do socket pode ser um endereço de gateway ou NAT em vez do cliente. O Windows realmente expõe um endereço de cliente relatado por sessão, mas é preciso consultar a sessão correta e decodificar a resposta corretamente.
Ancore a consulta em uma única sessão
Comece pela sessão, não pela pessoa. Um host RDP do Windows pode carregar várias sessões para uma conta, e um endereço visto antes de uma reconexão pode já não corresponder ao vínculo ativo. Registre o ID de sessão, o usuário e o momento da observação juntos. O ID de sessão é a chave que a API espera, e é o valor que impede que duas sessões sejam confundidas.
No host, as ferramentas de sessão instaladas listam o que existe; use a visão administrativa apenas quando tiver direito a ela. Confirme o ID escolhido imediatamente antes da consulta, porque um ID reciclado retorna os dados de outra sessão.
qwinsta
Chame WTSQuerySessionInformationW para esse ID
A API de Serviços de Terminal responde à pergunta com a classe de informações 14, WTSClientAddress. Um pequeno invólucro ctypes basta no Windows, e ele evita ler o hive de outro usuário. O código abaixo recebe um ID de sessão explícito e consulta o 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)
Passe o ID de sessão inteiro que você acabou de verificar e mantenha o identificador de servidor nulo para a máquina local. Trate uma chamada falha como erro e não como endereço ausente, porque os dois levam a conclusões diferentes. Consultar a sessão de outro usuário exige a permissão Query Information adequada, então execute a ferramenta em uma conta que a tenha recebido em vez de elevar privilégios ad hoc. O buffer retornado pertence à API e deve ser liberado mesmo quando a decodificação falha.
Decodifique a família e depois libere o buffer
A estrutura não é uma string de IP. Seu campo de família decide como os vinte bytes de endereço são lidos: IPv4 começa no deslocamento dois, enquanto IPv6 começa no deslocamento zero. O endereço não especificado significa que o cliente não relatou nenhum, o que não é o mesmo que uma string vazia. As conexões pelo Remote Desktop Gateway estão documentadas como um caso em que o endereço não está disponível, então uma sessão mediada por gateway não relata nada útil sobre o endereço real do cliente. A correlação precisa então migrar para os registros do próprio gateway.
Imprima a família ao lado do endereço durante a depuração, porque uma divergência entre as duas é a causa habitual de um valor plausível mas errado. Libere o buffer com WTSFreeMemory em um bloco finally para que uma exceção não o vaze.
Separe o endereço relatado da prova
Um endereço de cliente relatado é uma declaração fornecida pelo cliente, não uma observação confiável. Não é o par TCP, não é a tabela de concessões do gateway VPN e não é o endereço público do cliente. Quando a pergunta real é qual concessão VPN pertence a uma pessoa, correlacione o registro de sessão autenticado do servidor VPN no mesmo momento. Quando a pergunta é qual origem chegou ao ouvinte, leia a tabela de conexões ou os registros permitidos do lado do servidor.
Mantenha esses valores rotulados separadamente em qualquer ferramenta de suporte. Um resultado WTS ausente deve permanecer ausente em vez de recorrer a um valor de registro arbitrário de outra sessão. Registre o ID de sessão ao lado do endereço, porque o mesmo usuário pode reconectar e receber um valor novo. Nem o nome do cliente nem o endereço relatado são prova suficiente para conceder acesso.