Welk adres rapporteert Windows voor een RDP-sessie?
De WTS-API retourneert een gerapporteerd clientadres voor één RDP-sessie, en het register niet. Vraag de juiste sessie-ID op en decodeer IPv4 of IPv6 correct.
Een RDP-sessie heeft een clientadres nodig, en de voor de hand liggende bronnen zijn het niet eens. CLIENTNAME geeft een computernaam, het register bevat waarden uit andere sessies, en de socket-tegenpartij kan een gateway- of NAT-adres zijn in plaats van de client. Windows biedt weliswaar een gerapporteerd clientadres per sessie, maar je moet de juiste sessie opvragen en het antwoord correct decoderen.
Veranker de opzoeking aan één sessie
Begin bij de sessie, niet bij de persoon. Een Windows RDP-host kan meerdere sessies voor één account dragen, en een adres dat vóór een herverbinding is gezien, past mogelijk niet meer bij de actieve verbinding. Noteer de sessie-ID, de gebruiker en het observatiemoment samen. De sessie-ID is de sleutel die de API verwacht, en het is de waarde die voorkomt dat twee sessies worden verward.
Op de host sommen de geïnstalleerde sessiehulpmiddelen op wat bestaat; gebruik de administratieve weergave alleen wanneer je er recht op hebt. Bevestig de gekozen ID vlak vóór de query, want een hergebruikte ID levert de gegevens van een andere sessie.
qwinsta
Roep WTSQuerySessionInformationW aan voor die ID
De Terminal Services-API beantwoordt de vraag met informatieklasse 14, WTSClientAddress. Een kleine ctypes-wrapper volstaat op Windows, en hij vermijdt het lezen van de hive van een andere gebruiker. De code hieronder neemt een expliciete sessie-ID en vraagt de lokale host.
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)
Geef de gehele sessie-ID door die je zojuist hebt geverifieerd, en houd het nul-serverhandvat voor de lokale machine. Behandel een mislukte aanroep als een fout en niet als een ontbrekend adres, want beide leiden tot verschillende conclusies. Een sessie van een andere gebruiker opvragen vereist de juiste Query Information-machtiging, dus voer het hulpmiddel uit onder een account waaraan die is verleend in plaats van ad hoc te escaleren. De geretourneerde buffer hoort bij de API en moet worden vrijgegeven, ook wanneer het decoderen mislukt.
Decodeer de familie en geef daarna de buffer vrij
De structuur is geen IP-tekenreeks. Het familieveld bepaalt hoe de twintig adresbytes worden gelezen: IPv4 begint bij offset twee, terwijl IPv6 bij offset nul begint. Het niet-gespecificeerde adres betekent dat de client er geen heeft gerapporteerd, wat niet hetzelfde is als een lege tekenreeks. Verbindingen via Remote Desktop Gateway zijn gedocumenteerd als een geval waarin het adres niet beschikbaar is, dus een via een gateway geleide sessie rapporteert niets bruikbaars over het werkelijke adres van de client. De correlatie moet dan naar de eigen administratie van de gateway verschuiven.
Druk de familie naast het adres af tijdens het debuggen, want een verschil tussen beide is de gebruikelijke oorzaak van een plausibele maar onjuiste waarde. Geef de buffer vrij met WTSFreeMemory in een finally-blok, zodat een uitzondering hem niet laat lekken.
Scheid het gerapporteerde adres van bewijs
Een gerapporteerd clientadres is een verklaring die de client levert, geen betrouwbare waarneming. Het is niet de TCP-tegenpartij, niet de leasetabel van de VPN-gateway en niet het openbare adres van de client. Wanneer de echte vraag is welke VPN-lease bij een persoon hoort, correleer dan tegelijkertijd de geauthenticeerde sessieadministratie van de VPN-server. Wanneer de vraag is welke bron de luisteraar heeft bereikt, lees dan de verbindingstabel of de toegestane serverlogs.
Houd deze waarden afzonderlijk gelabeld in elk ondersteuningshulpmiddel. Een ontbrekend WTS-resultaat moet ontbrekend blijven in plaats van terug te vallen op een willekeurige registerwaarde uit een andere sessie. Log de sessie-ID naast het adres, want dezelfde gebruiker kan opnieuw verbinden en een nieuwe waarde krijgen. Noch de clientnaam noch het gerapporteerde adres is voldoende bewijs om toegang te verlenen.