Quale indirizzo segnala Windows per una sessione RDP?
L’API WTS restituisce un indirizzo client segnalato per una sessione RDP, e il registro no. Interrogate l’ID di sessione corretto e decodificate correttamente IPv4 o IPv6.
Una sessione RDP ha bisogno di un indirizzo client, e le fonti ovvie non concordano. CLIENTNAME fornisce un nome computer, il registro contiene valori di altre sessioni, e l’estremità del socket può essere un indirizzo di gateway o NAT anziché del client. Windows espone davvero un indirizzo client segnalato per sessione, ma bisogna chiedere la sessione giusta e decodificare correttamente la risposta.
Ancorate la ricerca a una sola sessione
Partite dalla sessione, non dalla persona. Un host RDP Windows può portare più sessioni per un solo account, e un indirizzo visto prima di una riconnessione può non corrispondere più al collegamento attivo. Annotate insieme l’ID di sessione, l’utente e il momento dell’osservazione. L’ID di sessione è la chiave che l’API si aspetta, ed è il valore che impedisce di confondere due sessioni.
Sull’host, gli strumenti di sessione installati elencano ciò che esiste; usate la vista amministrativa solo quando ne avete diritto. Confermate l’ID scelto subito prima dell’interrogazione, perché un ID riciclato restituisce i dati di un’altra sessione.
qwinsta
Chiamate WTSQuerySessionInformationW per quell’ID
L’API Terminal Services risponde alla domanda con la classe di informazioni 14, WTSClientAddress. Un piccolo wrapper ctypes basta su Windows, ed evita di leggere l’hive di un altro utente. Il codice sotto prende un ID di sessione esplicito e interroga l’host locale.
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)
Passate l’ID di sessione intero che avete appena verificato e mantenete l’handle server nullo per la macchina locale. Trattate una chiamata fallita come un errore e non come un indirizzo assente, perché le due cose portano a conclusioni diverse. Interrogare la sessione di un altro utente richiede l’autorizzazione Query Information adeguata, quindi eseguite lo strumento con un account a cui è stata concessa invece di elevare i privilegi ad hoc. Il buffer restituito appartiene all’API e deve essere liberato anche quando la decodifica fallisce.
Decodificate la famiglia, poi liberate il buffer
La struttura non è una stringa IP. Il suo campo famiglia decide come si leggono i venti byte di indirizzo: IPv4 inizia all’offset due, mentre IPv6 inizia all’offset zero. L’indirizzo non specificato significa che il client non ne ha segnalato alcuno, il che non equivale a una stringa vuota. Le connessioni tramite Remote Desktop Gateway sono documentate come un caso in cui l’indirizzo non è disponibile, quindi una sessione mediata da gateway non segnala nulla di utile sull’indirizzo reale del client. La correlazione deve allora spostarsi sui registri propri del gateway.
Stampate la famiglia accanto all’indirizzo durante il debug, perché una discrepanza tra le due è la causa abituale di un valore verosimile ma sbagliato. Liberate il buffer con WTSFreeMemory in un blocco finally perché un’eccezione non lo perda.
Separate l’indirizzo segnalato dalla prova
Un indirizzo client segnalato è una dichiarazione fornita dal client, non un’osservazione affidabile. Non è l’estremità TCP, non è la tabella delle concessioni del gateway VPN e non è l’indirizzo pubblico del client. Quando la domanda reale è quale concessione VPN appartiene a una persona, correlate nello stesso momento il record di sessione autenticato del server VPN. Quando la domanda è quale origine ha raggiunto l’ascoltatore, leggete la tabella delle connessioni o i log consentiti lato server.
Tenete questi valori etichettati separatamente in qualsiasi strumento di supporto. Un risultato WTS mancante deve restare mancante invece di ricadere su un valore di registro arbitrario di un’altra sessione. Registrate l’ID di sessione accanto all’indirizzo, perché lo stesso utente può riconnettersi e ricevere un valore nuovo. Né il nome del client né l’indirizzo segnalato sono prova sufficiente per concedere l’accesso.