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.

Powiązane artykuły