Jaki adres zgłasza Windows dla sesji RDP?
Interfejs WTS API zwraca zgłoszony adres klienta dla jednej sesji RDP, a rejestr nie. Odpytaj właściwy identyfikator sesji i poprawnie zdekoduj IPv4 lub IPv6.
Sesja RDP potrzebuje adresu klienta, a oczywiste źródła się nie zgadzają. CLIENTNAME podaje nazwę komputera, rejestr przechowuje wartości z innych sesji, a drugi koniec gniazda może być adresem bramy lub NAT, a nie klienta. Windows rzeczywiście udostępnia zgłoszony adres klienta dla każdej sesji, ale trzeba odpytać właściwą sesję i poprawnie zdekodować odpowiedź.
Zakotwicz wyszukiwanie w jednej sesji
Zacznij od sesji, nie od osoby. Host RDP systemu Windows może prowadzić kilka sesji dla jednego konta, a adres widziany przed ponownym połączeniem może już nie odpowiadać aktywnemu połączeniu. Zapisz razem identyfikator sesji, użytkownika i czas obserwacji. Identyfikator sesji to klucz, którego oczekuje interfejs API, i właśnie ta wartość chroni przed pomyleniem dwóch sesji.
Na hoście zainstalowane narzędzia sesji wymieniają to, co istnieje; widoku administracyjnego używaj tylko wtedy, gdy masz do niego prawo. Potwierdź wybrany identyfikator bezpośrednio przed zapytaniem, ponieważ recyklingowany identyfikator zwraca dane innej sesji.
qwinsta
Wywołaj WTSQuerySessionInformationW dla tego identyfikatora
Interfejs Terminal Services odpowiada na pytanie klasą informacji 14, WTSClientAddress. Niewielka otoczka ctypes wystarcza w systemie Windows i pozwala uniknąć czytania gałęzi rejestru innego użytkownika. Poniższy kod przyjmuje jawny identyfikator sesji i pyta lokalny 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)
Przekaż całkowity identyfikator sesji, który właśnie sprawdziłeś, i zachowaj zerowy uchwyt serwera dla maszyny lokalnej. Nieudane wywołanie traktuj jako błąd, a nie jako brak adresu, ponieważ oba prowadzą do różnych wniosków. Odpytanie sesji innego użytkownika wymaga odpowiedniego uprawnienia Query Information, więc uruchamiaj narzędzie na koncie, któremu je nadano, zamiast doraźnie podnosić uprawnienia. Zwrócony bufor należy do interfejsu API i musi zostać zwolniony nawet gdy dekodowanie się nie powiedzie.
Zdekoduj rodzinę, a następnie zwolnij bufor
Struktura nie jest łańcuchem IP. Jej pole rodziny decyduje o tym, jak odczytać dwadzieścia bajtów adresu: IPv4 zaczyna się od przesunięcia dwa, natomiast IPv6 od przesunięcia zero. Adres nieokreślony oznacza, że klient go nie zgłosił, co nie jest tym samym co pusty łańcuch. Połączenia przez bramę Remote Desktop Gateway są udokumentowane jako przypadek, w którym adres jest niedostępny, więc sesja prowadzona przez bramę nie zgłasza nic użytecznego o rzeczywistym adresie klienta. Korelacja musi wtedy przejść do własnych rejestrów bramy.
Podczas debugowania wypisuj rodzinę obok adresu, ponieważ niezgodność między nimi jest zwykłą przyczyną wiarygodnej, ale błędnej wartości. Zwolnij bufor za pomocą WTSFreeMemory w bloku finally, aby wyjątek go nie wyciekł.
Oddziel zgłoszony adres od dowodu
Zgłoszony adres klienta to oświadczenie dostarczone przez klienta, a nie wiarygodna obserwacja. Nie jest to drugi koniec TCP, nie jest to tabela dzierżaw bramy VPN i nie jest to publiczny adres klienta. Gdy prawdziwym pytaniem jest, która dzierżawa VPN należy do osoby, skoreluj jednocześnie uwierzytelniony zapis sesji serwera VPN. Gdy pytaniem jest, które źródło dotarło do odbiorcy, czytaj tabelę połączeń albo dozwolone dzienniki po stronie serwera.
Trzymaj te wartości osobno oznaczone w każdym narzędziu wsparcia. Brakujący wynik WTS powinien pozostać brakujący, zamiast sięgać po dowolną wartość rejestru z innej sesji. Zapisuj identyfikator sesji obok adresu, ponieważ ten sam użytkownik może połączyć się ponownie i otrzymać nową wartość. Ani nazwa klienta, ani zgłoszony adres nie są wystarczającym dowodem do przyznania dostępu.