Какъв адрес отчита Windows за RDP сесия?
WTS API връща отчетен адрес на клиента за една RDP сесия, а регистърът — не. Питайте правилния ID на сесията и декодирайте IPv4 или IPv6 правилно.
Една RDP сесия се нуждае от адрес на клиента и очевидните източници не са съгласни. CLIENTNAME дава име на компютър, регистърът пази стойности от други сесии, а отсрещната страна на сокета може да е адрес на шлюз или NAT, а не на клиента. Windows все пак предоставя отчетен адрес на клиента за всяка сесия, но трябва да поискате правилната сесия и да декодирате отговора коректно.
Обвържете търсенето с една сесия
Започнете със сесията, не с човека. Windows RDP хост може да носи няколко сесии за един акаунт и адрес, видян преди повторно свързване, може вече да не отговаря на живата връзка. Записвайте ID на сесията, потребителя и времето на наблюдение заедно. ID на сесията е ключът, който API очаква, и именно тази стойност пречи две сесии да се объркат.
На хоста инсталираните инструменти за сесии изброяват какво съществува; използвайте административния изглед само когато имате право на него. Потвърдете избрания ID непосредствено преди заявката, защото рециклиран ID връща данните на друга сесия.
qwinsta
Извикайте WTSQuerySessionInformationW за този ID
API на Terminal Services отговаря на въпроса с информационен клас 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, затова пускайте инструмента под акаунт, на който то е дадено, вместо да ескалирате ad hoc. Върнатият буфер принадлежи на API и трябва да бъде освободен дори когато декодирането се провали.
Декодирайте семейството, после освободете буфера
Структурата не е IP низ. Нейното поле за семейство решава как се четат двадесетте байта на адреса: IPv4 започва от отместване две, а IPv6 — от отместване нула. Неопределеният адрес означава, че клиентът не е отчел такъв, което не е същото като празен низ. Връзките през Remote Desktop Gateway са документирани като случай, в който адресът е недостъпен, така че сесия през шлюз не отчита нищо полезно за реалния адрес на клиента. Тогава корелацията трябва да се премести към собствените записи на шлюза.
Отпечатвайте семейството до адреса, докато отстранявате проблеми, защото несъответствието между двете е обичайната причина за правдоподобна, но грешна стойност. Освободете буфера с WTSFreeMemory в блок finally, за да не го изтече изключение.
Отделете отчетения адрес от доказателството
Отчетеният адрес на клиента е твърдение, предоставено от клиента, а не надеждно наблюдение. Той не е отсрещната страна на TCP, не е таблицата с наеми на VPN шлюза и не е публичният адрес на клиента. Когато истинският въпрос е кой VPN наем принадлежи на даден човек, съпоставете удостоверения запис на сесията на VPN сървъра по същото време. Когато въпросът е кой източник е достигнал слушателя, прочетете таблицата на връзките или разрешените сървърни журнали.
Дръжте тези стойности отделно обозначени във всеки инструмент за поддръжка. Липсващ резултат от WTS трябва да остане липсващ, вместо да се връща към произволна регистърна стойност от друга сесия. Записвайте ID на сесията до адреса, защото същият потребител може да се свърже отново и да получи нова стойност. Нито името на клиента, нито отчетеният адрес са достатъчно доказателство за предоставяне на достъп.