Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mitigating PowerShell Terminal Spoofing #2974

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cns/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -787,8 +787,10 @@ func main() {
}

// Setting the remote ARP MAC address to 12-34-56-78-9a-bc on windows for external traffic if HNS is enabled
execClient := platform.NewExecClient(nil)
err = platform.SetSdnRemoteArpMacAddress(execClient)

registry := platform.NewRegistryClient(nil)

err = platform.SetSdnRemoteArpMacAddress(registry)
if err != nil {
logger.Errorf("Failed to set remote ARP MAC address: %v", err)
return
Expand Down
64 changes: 64 additions & 0 deletions platform/mockregistryclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package platform

import (
"errors"

"golang.org/x/sys/windows/registry"

Check failure on line 6 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

could not import golang.org/x/sys/windows/registry (-: build constraints exclude all Go files in /home/runner/go/pkg/mod/golang.org/x/[email protected]/windows/registry) (typecheck)

Check failure on line 6 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, ubuntu-latest)

could not import golang.org/x/sys/windows/registry (-: build constraints exclude all Go files in /home/runner/go/pkg/mod/golang.org/x/[email protected]/windows/registry) (typecheck)
)

type MockRegistryClient struct {
returnError bool
keys map[string]*mockRegistryKey
setOpenKey setOpenKey
setRestartService setRestartService
}

type (
setOpenKey func(k registry.Key, path string, access uint32) (RegistryKey, error)

Check failure on line 17 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

undefined: RegistryKey (typecheck)

Check failure on line 17 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, ubuntu-latest)

undefined: RegistryKey (typecheck)
setRestartService func(serviceName string) error
)

type mockRegistryKey struct {
Values map[string]string
}

func NewMockRegistryClient(returnErr bool) *MockRegistryClient {
return &MockRegistryClient{
returnError: returnErr,
keys: make(map[string]*mockRegistryKey),
}
}

func (r *MockRegistryClient) SetOpenKey(fn setOpenKey) {
r.setOpenKey = fn
}

func (r *MockRegistryClient) OpenKey(k registry.Key, path string, access uint32) (RegistryKey, error) {

Check failure on line 36 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

undefined: RegistryKey (typecheck)

Check failure on line 36 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, ubuntu-latest)

undefined: RegistryKey (typecheck)
return r.setOpenKey(k, path, access)

}

func (k *mockRegistryKey) GetStringValue(name string) (string, uint32, error) {

Check failure on line 41 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

unnamedResult: consider giving a name to these results (gocritic)

Check failure on line 41 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

unnamedResult: consider giving a name to these results (gocritic)
if value, exists := k.Values[name]; exists {
return value, registry.SZ, nil
}
return "", 0, errors.New("value does not exist")
}

func (k *mockRegistryKey) SetStringValue(name string, value string) error {

Check failure on line 48 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

paramTypeCombine: func(name string, value string) error could be replaced with func(name, value string) error (gocritic)

Check failure on line 48 in platform/mockregistryclient.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

paramTypeCombine: func(name string, value string) error could be replaced with func(name, value string) error (gocritic)
k.Values[name] = name
return nil
}

func (k *mockRegistryKey) Close() error {
return nil
}

func (r *MockRegistryClient) SetRestartService(fn setRestartService) {
r.setRestartService = fn
}

func (r *MockRegistryClient) restartService(serviceName string) error {
return r.setRestartService(serviceName)

}
47 changes: 24 additions & 23 deletions platform/os_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"github.com/pkg/errors"
"go.uber.org/zap"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)

const (
Expand Down Expand Up @@ -61,24 +62,12 @@
// for vlan tagged arp requests
SDNRemoteArpMacAddress = "12-34-56-78-9a-bc"

// Command to get SDNRemoteArpMacAddress registry key
GetSdnRemoteArpMacAddressCommand = "(Get-ItemProperty " +
"-Path HKLM:\\SYSTEM\\CurrentControlSet\\Services\\hns\\State -Name SDNRemoteArpMacAddress).SDNRemoteArpMacAddress"

// Command to set SDNRemoteArpMacAddress registry key
SetSdnRemoteArpMacAddressCommand = "Set-ItemProperty " +
"-Path HKLM:\\SYSTEM\\CurrentControlSet\\Services\\hns\\State -Name SDNRemoteArpMacAddress -Value \"12-34-56-78-9a-bc\""

// Command to check if system has hns state path or not
CheckIfHNSStatePathExistsCommand = "Test-Path " +
"-Path HKLM:\\SYSTEM\\CurrentControlSet\\Services\\hns\\State"

// Command to fetch netadapter and pnp id
//TODO can we replace this (and things in endpoint_windows) with "golang.org/x/sys/windows"

Check failure on line 66 in platform/os_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

commentFormatting: put a space between `//` and comment text (gocritic)

Check failure on line 66 in platform/os_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

commentFormatting: put a space between `//` and comment text (gocritic)
//var adapterInfo windows.IpAdapterInfo
//var bufferSize uint32 = uint32(unsafe.Sizeof(adapterInfo))
GetMacAddressVFPPnpIDMapping = "Get-NetAdapter | Select-Object MacAddress, PnpDeviceID| Format-Table -HideTableHeaders"

// Command to restart HNS service
RestartHnsServiceCommand = "Restart-Service -Name hns"

// Interval between successive checks for mellanox adapter's PriorityVLANTag value
defaultMellanoxMonitorInterval = 30 * time.Second

Expand Down Expand Up @@ -257,40 +246,51 @@
}

// SetSdnRemoteArpMacAddress sets the regkey for SDNRemoteArpMacAddress needed for multitenancy if hns is enabled
func SetSdnRemoteArpMacAddress(execClient ExecClient) error {
exists, err := execClient.ExecutePowershellCommand(CheckIfHNSStatePathExistsCommand)
func SetSdnRemoteArpMacAddress(registerClient RegistryClient) error {
key, err := registerClient.OpenKey(registry.LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\hns\\State", registry.READ|registry.SET_VALUE)
if err != nil {
if err == registry.ErrNotExist {
log.Printf("hns state path does not exist, skip setting SdnRemoteArpMacAddress")
return nil
}
errMsg := fmt.Sprintf("Failed to check the existent of hns state path due to error %s", err.Error())
log.Printf(errMsg)
return errors.Errorf(errMsg)
}
if strings.EqualFold(exists, "false") {

if key == nil {
log.Printf("hns state path does not exist, skip setting SdnRemoteArpMacAddress")
return nil
}

if sdnRemoteArpMacAddressSet == false {
result, err := execClient.ExecutePowershellCommand(GetSdnRemoteArpMacAddressCommand)

//Was (Get-ItemProperty -Path HKLM:\\SYSTEM\\CurrentControlSet\\Services\\hns\\State -Name SDNRemoteArpMacAddress).SDNRemoteArpMacAddress"

Check failure on line 268 in platform/os_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

commentFormatting: put a space between `//` and comment text (gocritic)

Check failure on line 268 in platform/os_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

commentFormatting: put a space between `//` and comment text (gocritic)
result, _, err := key.GetStringValue("SDNRemoteArpMacAddress")
if err != nil {
return err
}

// Set the reg key if not already set or has incorrect value
if result != SDNRemoteArpMacAddress {
if _, err = execClient.ExecutePowershellCommand(SetSdnRemoteArpMacAddressCommand); err != nil {

//was "Set-ItemProperty -Path HKLM:\\SYSTEM\\CurrentControlSet\\Services\\hns\\State -Name SDNRemoteArpMacAddress -Value \"12-34-56-78-9a-bc\""

Check failure on line 277 in platform/os_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

commentFormatting: put a space between `//` and comment text (gocritic)

Check failure on line 277 in platform/os_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

commentFormatting: put a space between `//` and comment text (gocritic)

if err := key.SetStringValue("SDNRemoteArpMacAddress", SDNRemoteArpMacAddress); err != nil {
log.Printf("Failed to set SDNRemoteArpMacAddress due to error %s", err.Error())
return err
}

log.Printf("[Azure CNS] SDNRemoteArpMacAddress regKey set successfully. Restarting hns service.")
if _, err := execClient.ExecutePowershellCommand(RestartHnsServiceCommand); err != nil {

// was "Restart-Service -Name hns"
if err := registerClient.restartService("hns"); err != nil {
log.Printf("Failed to Restart HNS Service due to error %s", err.Error())
return err
}
}

sdnRemoteArpMacAddressSet = true
}

return nil
}

Expand Down Expand Up @@ -364,6 +364,7 @@
pidstr = strings.Trim(pidstr, "\r\n")
cmd := fmt.Sprintf("Get-Process -Id %s|Format-List", pidstr)
p := NewExecClient(nil)
//TODO not removing this because it seems to only be called in test?

Check failure on line 367 in platform/os_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

commentFormatting: put a space between `//` and comment text (gocritic)

Check failure on line 367 in platform/os_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

commentFormatting: put a space between `//` and comment text (gocritic)
out, err := p.ExecutePowershellCommand(cmd)
if err != nil {
log.Printf("Process is not running. Output:%v, Error %v", out, err)
Expand Down
54 changes: 37 additions & 17 deletions platform/os_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"context"
"errors"
"os/exec"
"strings"
"testing"
"time"

"github.com/Azure/azure-container-networking/platform/windows/adapter/mocks"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/windows/registry"
)

var errTestFailure = errors.New("test failure")
Expand Down Expand Up @@ -116,34 +116,54 @@
}

func TestSetSdnRemoteArpMacAddress_hnsNotEnabled(t *testing.T) {
mockExecClient := NewMockExecClient(false)
// testing skip setting SdnRemoteArpMacAddress when hns not enabled
mockExecClient.SetPowershellCommandResponder(func(_ string) (string, error) {
return "False", nil

mockRegistryClient := NewMockRegistryClient(false)
// mockRegistryClient.SetOpenKey(func(k registry.Key, path string, access uint32) (RegistryKey, error) {

Check failure on line 121 in platform/os_windows_test.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

commentedOutCode: may want to remove commented-out code (gocritic)

Check failure on line 121 in platform/os_windows_test.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

commentedOutCode: may want to remove commented-out code (gocritic)
// return nil,nil
// })
mockRegistryClient.SetOpenKey(func(k registry.Key, path string, access uint32) (RegistryKey, error) {
return nil, nil
})

mockRegistryClient.SetRestartService(func(s string) error {
return nil
})
err := SetSdnRemoteArpMacAddress(mockExecClient)

err := SetSdnRemoteArpMacAddress(mockRegistryClient)
assert.NoError(t, err)
assert.Equal(t, false, sdnRemoteArpMacAddressSet)

// testing the scenario when there is an error in checking if hns is enabled or not
mockExecClient.SetPowershellCommandResponder(func(_ string) (string, error) {
return "", errTestFailure

mockRegistryClient.SetOpenKey(func(k registry.Key, path string, access uint32) (RegistryKey, error) {
return &mockRegistryKey{
Values: map[string]string{
"": "",
},
}, errTestFailure
})
err = SetSdnRemoteArpMacAddress(mockExecClient)

err = SetSdnRemoteArpMacAddress(mockRegistryClient)
assert.ErrorAs(t, err, &errTestFailure)
assert.Equal(t, false, sdnRemoteArpMacAddressSet)
}

func TestSetSdnRemoteArpMacAddress_hnsEnabled(t *testing.T) {
mockExecClient := NewMockExecClient(false)
// happy path
mockExecClient.SetPowershellCommandResponder(func(cmd string) (string, error) {
if strings.Contains(cmd, "Test-Path") {
return "True", nil
}
return "", nil

mockRegistryClient := NewMockRegistryClient(false) // happy path

mockRegistryClient.SetOpenKey(func(k registry.Key, path string, access uint32) (RegistryKey, error) {
return &mockRegistryKey{
Values: map[string]string{
"SDNRemoteArpMacAddress": "MockData1",
},
}, nil
})
mockRegistryClient.SetRestartService(func(s string) error {
return nil
})
err := SetSdnRemoteArpMacAddress(mockExecClient)

err := SetSdnRemoteArpMacAddress(mockRegistryClient)
assert.NoError(t, err)
assert.Equal(t, true, sdnRemoteArpMacAddressSet)
// reset sdnRemoteArpMacAddressSet
Expand Down
102 changes: 102 additions & 0 deletions platform/registryInterface_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package platform

import (
"fmt"
"time"

"go.uber.org/zap"
"golang.org/x/sys/windows/registry"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)

type RegistryClient interface {
OpenKey(k registry.Key, path string, access uint32) (RegistryKey, error)
restartService(serviceName string) error
}

type RegistryKey interface {
GetStringValue(name string) (val string, valtype uint32, err error)
SetStringValue(name, value string) error
Close() error
}

type registryClient struct {
Timeout time.Duration
logger *zap.Logger
}

type registryKey struct {
key registry.Key
}

func NewRegistryClient(logger *zap.Logger) RegistryClient {
return &registryClient{
Timeout: defaultExecTimeout * time.Second,
logger: logger,
}
}

func (r *registryClient) OpenKey(k registry.Key, path string, access uint32) (RegistryKey, error) {
key, err := registry.OpenKey(k, path, access)
if err != nil {
return nil, err
}
return &registryKey{key}, nil
}

func (k *registryKey) GetStringValue(name string) (string, uint32, error) {

Check failure on line 48 in platform/registryInterface_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

unnamedResult: consider giving a name to these results (gocritic)

Check failure on line 48 in platform/registryInterface_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

unnamedResult: consider giving a name to these results (gocritic)
return k.key.GetStringValue(name)
}

func (k *registryKey) SetStringValue(name string, value string) error {

Check failure on line 52 in platform/registryInterface_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

paramTypeCombine: func(name string, value string) error could be replaced with func(name, value string) error (gocritic)

Check failure on line 52 in platform/registryInterface_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

paramTypeCombine: func(name string, value string) error could be replaced with func(name, value string) error (gocritic)
return k.key.SetStringValue(name, value)
}

func (k *registryKey) Close() error {
return k.key.Close()
}

// straight out of chat gpt
func (r *registryClient) restartService(serviceName string) error {
// Connect to the service manager
m, err := mgr.Connect()
if err != nil {
return fmt.Errorf("could not connect to service manager: %v", err)
}
defer m.Disconnect()

Check failure on line 67 in platform/registryInterface_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

Error return value of `m.Disconnect` is not checked (errcheck)

Check failure on line 67 in platform/registryInterface_windows.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

Error return value of `m.Disconnect` is not checked (errcheck)

// Open the service by name
service, err := m.OpenService(serviceName)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer service.Close()

// Stop the service
_, err = service.Control(svc.Stop)
if err != nil {
return fmt.Errorf("could not stop service: %v", err)
}

// Wait for the service to stop
status, err := service.Query()
if err != nil {
return fmt.Errorf("could not query service status: %v", err)
}
for status.State != svc.Stopped {
time.Sleep(500 * time.Millisecond)
status, err = service.Query()
if err != nil {
return fmt.Errorf("could not query service status: %v", err)
}
}

// Start the service again
err = service.Start()
if err != nil {
return fmt.Errorf("could not start service: %v", err)
}

return nil
}
Loading