Сеансу RDP потрібна адреса клієнта, і очевидні джерела розходяться. CLIENTNAME дає ім’я комп’ютера, реєстр зберігає значення з інших сеансів, а протилежний бік сокета може бути адресою шлюзу або NAT, а не клієнта. Windows справді надає повідомлену адресу клієнта для кожного сеансу, але треба запитати правильний сеанс і коректно декодувати відповідь.

Прив’яжіть пошук до одного сеансу

Починайте із сеансу, а не з людини. Хост RDP під Windows може нести кілька сеансів для одного облікового запису, і адреса, помічена перед перепідключенням, може вже не відповідати активному з’єднанню. Записуйте ID сеансу, користувача й час спостереження разом. ID сеансу — це ключ, якого очікує API, і саме це значення не дає переплутати два сеанси.

На хості встановлені засоби сеансів перелічують те, що існує; адміністративний подання використовуйте лише коли маєте на нього право. Підтвердьте вибраний ID безпосередньо перед запитом, бо повторно використаний ID повертає дані іншого сеансу.

qwinsta

Викличте WTSQuerySessionInformationW для цього ID

API служб терміналів відповідає на питання класом інформації 14, WTSClientAddress. Невеликої обгортки ctypes під Windows достатньо, і вона дає змогу не читати куст іншого користувача. Код нижче приймає явний ID сеансу й опитує локальний хост.

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)

Передайте цілочисельний ID сеансу, який ви щойно перевірили, і збережіть нульовий дескриптор сервера для локальної машини. Невдалий виклик вважайте помилкою, а не відсутньою адресою, бо ці два випадки ведуть до різних висновків. Запит сеансу іншого користувача потребує відповідного дозволу Query Information, тому запускайте засіб під обліковим записом, якому його надано, замість разового підвищення прав. Повернутий буфер належить API і має бути звільнений навіть коли декодування не вдалося.

Декодуйте родину, потім звільніть буфер

Структура — не рядок IP. Її поле родини визначає, як читати двадцять байтів адреси: IPv4 починається зі зсуву два, а IPv6 — зі зсуву нуль. Невизначена адреса означає, що клієнт її не повідомив, і це не те саме, що порожній рядок. З’єднання через Remote Desktop Gateway задокументовані як випадок, коли адреса недоступна, тому сеанс через шлюз не повідомляє нічого корисного про реальну адресу клієнта. Тоді зіставлення має перейти до власних записів шлюзу.

Під час налагодження друкуйте родину поряд з адресою, бо розбіжність між ними — звичайна причина правдоподібного, але хибного значення. Звільніть буфер за допомогою WTSFreeMemory у блоці finally, щоб виняток його не протікав.

Відокремте повідомлену адресу від доказу

Повідомлена адреса клієнта — це заява, яку надає клієнт, а не надійне спостереження. Це не протилежний бік TCP, не таблиця оренди VPN-шлюзу і не публічна адреса клієнта. Коли справжнє питання в тому, яка оренда VPN належить людині, зіставте автентифікований запис сеансу VPN-сервера одночасно. Коли питання в тому, яке джерело дісталося слухача, читайте таблицю з’єднань або дозволені журнали на боці сервера.

Тримайте ці значення окремо позначеними в будь-якому засобі підтримки. Відсутній результат WTS має залишатися відсутнім, а не підмінятися довільним значенням реєстру з іншого сеансу. Записуйте ID сеансу поряд з адресою, бо той самий користувач може перепідключитися й отримати нове значення. Ні ім’я клієнта, ні повідомлена адреса не є достатнім доказом для надання доступу.

Схожі статті