Named pipe client PID spoofing (impersonation trick)¶
NPFS, client PID'sini bir kez, connect anında, connect eden thread'den kaydeder — yani pipe'ı hangi process'in açtığını kontrol ederek (ya da SMB üzerinden bir Extended Attribute buffer'ı sağlayarak) bir attacker, bir server'a arbitrary bir
ClientProcessIdokutur ve PID tabanlı access check'leri devre dışı bırakır.
Mechanism¶
Note
Bir client, bir named pipe'ın server ucunu açtığında, NPFS.SYS connection
attribute'larını pipe'ın connection object'ine snapshot'lar. Client process
ID'si NPFS'in create path'inde (NpCreateClientEnd) set edilir ve normal
durumda connect eden thread'den PsGetThreadProcessId() üzerinden gelir. Bir
server daha sonra bunu GetNamedPipeClientProcessId ile geri okur; bu da
arka planda undocumented bir FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE
File-System-Control isteği gönderir ve değeri saklanan attribute list'inden
çeker.
İki olgu bunu exploit edilebilir kılar:
-
PID connect anında sabitlenir ve connect eden process'tir — daha sonra pipe'a read/write yapan değil. I/O'yu yapan process'in, handle'ı açan process ile eşleşmesini zorlayan hiçbir şey yoktur. Yani attacker, server'la fiilen konuşandan farklı (örneğin yüksek yetkili ya da recycle edilmiş) bir PID kaydettirebilir.
-
Attribute list, caller'ın sağladığı bir Extended Attribute (EA) buffer ile sürülebilen generic bir mekanizma (
NpSetAttributeInList) üzerinden doldurulur. NPFS, PID/session'ı yalnızca hiçbir EA buffer sağlanmadığında current process'ten alır. Bir EA buffer sağlanırsa, driver bunun yerineClientProcessId,ClientComputerNameveClientSessionId'yi onun içinden okur. Driver bunu, yalnızca in-kernel SMB server'ın bu attribute'ları set etmesine izin vermeyi amaçlayan birAccessMode != KernelModecheck'iyle korur — ama o guard'a, örneğin bir NTFS mount point / reparse'ı NPFS'e yönlendirerek istek SMB server'dan geliyormuş gibi ulaşılabilir (Windows 10 1709+).
Security etkisi: third-party (ve bazı first-party) service'ler named-pipe
client PID'sini bir authentication sinyali olarak ele alır — "beni yalnızca
process X çağırabilir" — ve onu GetNamedPipeClientProcessId ile sorgular.
O PID attacker tarafından etkilenebilir olduğundan, böyle check'ler trusted
process olarak çalışmaya hiç gerek kalmadan bypass edilebilir.
Walkthrough¶
James Forshaw'ın writeup'ı birkaç teknik sunar; en geniş kullanılabilir olanlar ne SMB ne de admin hakkı gerektirir.
- PID recycling. Target'ın beklediği programı OS size istediğiniz PID'yi
verene kadar tekrar tekrar spawn'layın, sonra pipe'ı o process'ten açın,
böylece NPFS istenen
ClientProcessId'yi kaydetsin:
while (true) {
using (var p = Process.Start("target.exe")) {
if (p.Id == 65276) break; // desired PID acquired
p.Kill();
}
}
// open the server pipe from the process now holding PID 65276
-
Cross-process handle passing. Pipe'ı bir process'te oluşturup/connect ederek onun PID'sini gömün, sonra pipe handle'ını
DuplicateHandleile başka bir process'e (örneğin bir child'a) geçirip asıl I/O'yu orada yapın. Kaydedilen client PID, açan process'inki olarak kalır ve onu sonra recycle edebilirsiniz. Forshaw bunun "named pipe açılabiliyorsa bir sandbox içinde çalışması gerektiğini" ve SMB gerektirmediğini belirtir. -
SMB üzerinden EA-buffer spoof / mount-point redirect. Pipe'ı açarken bir EA buffer sağlayın, böylece NPFS arbitrary bir PID'yi doğrudan attribute list'ine kopyalar:
EaBuffer ea = new EaBuffer();
ea.AddEntry("ClientComputerName", "FAKE\0", EaBufferEntryFlags.None);
ea.AddEntry("ClientProcessId", 1234, EaBufferEntryFlags.None);
ea.AddEntry("ClientSessionId", new byte[8], EaBufferEntryFlags.None);
// open \\127.0.0.1\pipe\target with this EA via the loopback SMB path /
// an NTFS mount-point reparse into NPFS so AccessMode appears as KernelMode
Server ardından forge edilmiş ClientProcessId'yi okur:
// server side
HANDLE hPipe = /* connected pipe */;
ULONG clientPid = 0;
GetNamedPipeClientProcessId(hPipe, &clientPid); // returns 1234 (spoofed)
Server neden kandırılır
GetNamedPipeClientProcessId, PID'yi canlı connection'dan yeniden türetmez —
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE ile connect anında cache'lenen değeri
döndürür. O cache'lenmiş değer ister recycle edilmiş bir PID'nin
PsGetThreadProcessId()'sinden ister bir attacker EA buffer'ından gelsin,
server farkı söyleyemez. Dolayısıyla "if (OpenProcess(clientPid)
signed/trusted ise) izin ver" gibi bir check, istekleri gönderen process
değil, attacker'ın seçtiği process tarafından sağlanmış olur.
Detection¶
- Authorization için client PID'sine güvenen bir server, caller'ın token'ını
ImpersonateNamedPipeClient+ token/SID check'leri üzerinden ek olarak doğrulamalıdır; bunlar aynı şekilde spoof edilemez. PID-gated bir service'te impersonation'ın olmaması asıl açıktır. - Telemetri: belirli bir image'in hızlı spawn/kill loop'ları (PID recycling) ve
non-default EA buffer'ları (non-SMB bir caller'dan gelen
ClientProcessId/ClientComputerNameattribute'ları) taşıyan pipe açılışları anormaldir.
Mitigation¶
- Named-pipe client PID'sini bir security boundary olarak kullanmayın. Client'ın
access token'ı ile authenticate edin:
ImpersonateNamedPipeClient, sonra impersonate edilen SID/integrity level/TokenLinkedToken'ı kontrol edin. - Bir PID yalnızca correlation için gerekiyorsa, onu untrusted ipucu verisi olarak ele alın.
- Pipe'a kimin ulaşabileceğini sıkı bir pipe DACL ve benzersiz, öngörülemez bir pipe ismi ile kısıtlayın, böylece bir attacker önceden connect olamasın.