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.

Articoli correlati