mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-24 07:39:07 +02:00
Place the Windows VNC agent socket under SystemTemp, falling back to Windows\Temp where it does not exist
This commit is contained in:
@@ -14,9 +14,10 @@ import (
|
|||||||
// rests on three other measures, none of which assume the socket path is
|
// rests on three other measures, none of which assume the socket path is
|
||||||
// secret:
|
// secret:
|
||||||
//
|
//
|
||||||
// - the socket lives in a dedicated directory (agentSocketDir) created
|
// - the socket lives in a dedicated directory (agentSocketDirPath) under
|
||||||
// with a DACL granting only SYSTEM and Administrators, so an
|
// %SystemRoot%\SystemTemp, created with a DACL granting only SYSTEM and
|
||||||
// unprivileged local user cannot create or squat a socket there;
|
// Administrators, so an unprivileged local user cannot create or squat a
|
||||||
|
// socket there;
|
||||||
// - each spawn uses a cryptographically random socket name, so the path
|
// - each spawn uses a cryptographically random socket name, so the path
|
||||||
// is unguessable before the agent binds it;
|
// is unguessable before the agent binds it;
|
||||||
// - the daemon publishes the path only after confirming the spawned
|
// - the daemon publishes the path only after confirming the spawned
|
||||||
|
|||||||
@@ -385,14 +385,10 @@ type sessionManager struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// agentSocketDir is a dedicated subdirectory under C:\Windows\Temp that
|
// agentSocketDirName is the dedicated subdirectory the agent socket lives
|
||||||
// the daemon creates with a restrictive DACL (SYSTEM + Administrators
|
// in, created with agentSocketDirSDDL under the parent agentSocketParent
|
||||||
// only). The default ACL on C:\Windows\Temp grants BUILTIN\Users
|
// picks.
|
||||||
// create-file rights, so the agent socket must not live directly there:
|
agentSocketDirName = "netbird-vnc"
|
||||||
// an unprivileged local user could pre-create a predictable path and
|
|
||||||
// intercept the daemon→agent stream. Both the daemon and the agent run
|
|
||||||
// as SYSTEM, so a SYSTEM-write-only directory is sufficient.
|
|
||||||
agentSocketDir = `C:\Windows\Temp\netbird-vnc`
|
|
||||||
|
|
||||||
// agentSocketDirSDDL grants full access to Local System (SY) and the
|
// agentSocketDirSDDL grants full access to Local System (SY) and the
|
||||||
// Builtin Administrators group (BA) only, with the DACL protected
|
// Builtin Administrators group (BA) only, with the DACL protected
|
||||||
@@ -719,6 +715,7 @@ func (m *sessionManager) maybeSpawnAgent(sid uint32) bool {
|
|||||||
// transient sockets, so removing it loses nothing. Fails closed: returns an
|
// transient sockets, so removing it loses nothing. Fails closed: returns an
|
||||||
// error if the directory cannot be created with the intended security.
|
// error if the directory cannot be created with the intended security.
|
||||||
func ensureAgentSocketDir() error {
|
func ensureAgentSocketDir() error {
|
||||||
|
agentSocketDir := agentSocketDirPath()
|
||||||
sd, err := windows.SecurityDescriptorFromString(agentSocketDirSDDL)
|
sd, err := windows.SecurityDescriptorFromString(agentSocketDirSDDL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse socket dir SDDL: %w", err)
|
return fmt.Errorf("parse socket dir SDDL: %w", err)
|
||||||
@@ -754,7 +751,39 @@ func newAgentSocketPath(sessionID uint32) (string, error) {
|
|||||||
return "", fmt.Errorf("read random: %w", err)
|
return "", fmt.Errorf("read random: %w", err)
|
||||||
}
|
}
|
||||||
name := fmt.Sprintf("netbird-vnc-%d-%s.sock", sessionID, hex.EncodeToString(b))
|
name := fmt.Sprintf("netbird-vnc-%d-%s.sock", sessionID, hex.EncodeToString(b))
|
||||||
return filepath.Join(agentSocketDir, name), nil
|
return filepath.Join(agentSocketDirPath(), name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// agentSocketDirPath returns the directory the agent socket lives in.
|
||||||
|
//
|
||||||
|
// The parent is %SystemRoot%\SystemTemp where it exists: the temp directory
|
||||||
|
// Windows reserves for SYSTEM, with an ACL that admits SYSTEM and
|
||||||
|
// Administrators only. No unprivileged account can create anything in it, so a
|
||||||
|
// user cannot pre-create the socket directory, or a junction in its place, to
|
||||||
|
// intercept the daemon-to-agent stream. It is present on current Windows 11 and
|
||||||
|
// Server 2022 and later, and on Windows 10 and Server 2019 through servicing.
|
||||||
|
//
|
||||||
|
// Only where it is missing does this fall back to %SystemRoot%\Temp, whose ACL
|
||||||
|
// lets Users create entries. The protected DACL on the subdirectory and the
|
||||||
|
// tear-down-and-recreate in ensureAgentSocketDir are what hold there.
|
||||||
|
func agentSocketDirPath() string {
|
||||||
|
return filepath.Join(agentSocketParent(), agentSocketDirName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// agentSocketParent picks the parent directory for agentSocketDirPath. A
|
||||||
|
// SystemTemp that is a reparse point is not trusted, since only an
|
||||||
|
// administrator could have made it one and it no longer names the directory
|
||||||
|
// whose ACL is the point of using it.
|
||||||
|
func agentSocketParent() string {
|
||||||
|
winDir, err := windows.GetSystemWindowsDirectory()
|
||||||
|
if err != nil || winDir == "" {
|
||||||
|
winDir = `C:\Windows`
|
||||||
|
}
|
||||||
|
systemTemp := filepath.Join(winDir, "SystemTemp")
|
||||||
|
if info, err := os.Lstat(systemTemp); err == nil && info.IsDir() && info.Mode()&os.ModeType == os.ModeDir {
|
||||||
|
return systemTemp
|
||||||
|
}
|
||||||
|
return filepath.Join(winDir, "Temp")
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitForAgentListening dials the agent's Unix socket until it answers or the
|
// waitForAgentListening dials the agent's Unix socket until it answers or the
|
||||||
|
|||||||
Reference in New Issue
Block a user