Skip to content

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 ClientProcessId okutur 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:

  1. 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.

  2. 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 yerine ClientProcessId, ClientComputerName ve ClientSessionId'yi onun içinden okur. Driver bunu, yalnızca in-kernel SMB server'ın bu attribute'ları set etmesine izin vermeyi amaçlayan bir AccessMode != KernelMode check'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.

  1. 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
  1. Cross-process handle passing. Pipe'ı bir process'te oluşturup/connect ederek onun PID'sini gömün, sonra pipe handle'ını DuplicateHandle ile 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.

  2. 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 / ClientComputerName attribute'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.

References