mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-03-12 21:48:33 +03:00
refactor: clean up and move binary security checks to aghos
This commit is contained in:
parent
11a7ac2013
commit
8afecf5a95
4 changed files with 139 additions and 65 deletions
internal
83
internal/aghos/binarysecurity_other.go
Normal file
83
internal/aghos/binarysecurity_other.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
//go:build !windows
|
||||
|
||||
package aghos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// protectedDirectories are directories which contain other application binaries,
|
||||
// as such AdGuard Home should never attempt store application data here, at risk of
|
||||
// overwriting other files. Moreover, these directories are innapproriate for storage of
|
||||
// config files or session storage.
|
||||
var protectedDirectories = []string{
|
||||
"/usr/bin"
|
||||
"/usr/sbin"
|
||||
"/user/bin"
|
||||
}
|
||||
|
||||
// serviceInstallDir is a executable path in a directory which secure permissions
|
||||
// which prevent the manipulation of the binary.
|
||||
const serviceInstallDir = "/usr/bin/AdGuardHome"
|
||||
|
||||
// SecureBinary is used before service.Install(). This function protects AdGuardHome from
|
||||
// privilege escalation vulnerabilities caused by writable files
|
||||
func SecureBinary() error {
|
||||
// Installalation can only be completed with root privileges, so check and handle if not
|
||||
if os.Getuid() != 0 {
|
||||
return errors.Error("permission denied. Root privileges required")
|
||||
}
|
||||
|
||||
// Get current file path
|
||||
binary, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.Executable(): %w", err)
|
||||
}
|
||||
|
||||
// Change owner to root:root
|
||||
err = os.Chown(binary, 0, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.Chown() %q: %w", binary, err)
|
||||
}
|
||||
|
||||
// Set permissions to root(read,write,exec), group(read,exec), public(read)
|
||||
// This combined with changing the owner make the file undeletable without root privlages
|
||||
// UNLESS THE PARENT FOLDER IS WRITABLE!
|
||||
if err := os.Chmod(binary, 0755); err != nil {
|
||||
return fmt.Errorf("os.Chmod() %q: %w", binary, err)
|
||||
}
|
||||
|
||||
|
||||
// Move binary to the PATH in a folder which is read-only to non root users
|
||||
// If already moved, this is a no-op
|
||||
if err := os.Rename(binary, serviceInstallDir); err != nil {
|
||||
return fmt.Errorf("os.Rename() %q to %q: %w", binary, installDir, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CurrentDirAvaliable returns true if it is okay to use this directory to store application
|
||||
// data.
|
||||
func CurrentDirAvaliable() (bool, error) {
|
||||
binary, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.Executable(): %w", err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(protectedDirectories); i++ {
|
||||
// Check if binary is within a protected directory
|
||||
if strings.HasPrefix(binary, protectedDirectories[i]) {
|
||||
// The binary is within a protected directory
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// The binary is outside of all checked protected directories
|
||||
return true, nil
|
||||
}
|
47
internal/aghos/binarysecurity_windows.go
Normal file
47
internal/aghos/binarysecurity_windows.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
//go:build windows
|
||||
|
||||
package aghos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// securePrefixDirectories is a list of directories where a service binary
|
||||
// has the appropriate permissions to mitigate a binary planting attack
|
||||
var securePrefixDirectories = []string{
|
||||
"C:\\Program Files",
|
||||
"C:\\Program Files (x86)",
|
||||
|
||||
// Some Windows users place binaries within /Windows/System32 to add it to %PATH%
|
||||
"C:\\Windows",
|
||||
}
|
||||
|
||||
// SecureBinary is used before service.Install(). This function protects AdGuardHome from
|
||||
// privilege escalation vulnerabilities caused by writable files
|
||||
func SecureBinary() error {
|
||||
// Get current file path
|
||||
binary, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.Executable(): %w", err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(securePrefixDirectories); i++ {
|
||||
// Check if binary is within a secure folder write protected folder
|
||||
if strings.HasPrefix(binary, securePrefixDirectories[i]) {
|
||||
// The binary is within a secure directory already
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// No secure directories matched
|
||||
return fmt.Errorf("insecure binary location for service instalation: %q. Please view: https://adguard-dns.io/kb/adguard-home/running-securely/", binary)
|
||||
}
|
||||
|
||||
// CurrentDirAvaliable returns true if it is okay to use this directory to store application
|
||||
// data.
|
||||
func CurrentDirAvaliable() (bool, error) {
|
||||
// We do not mind what directory is used on Windows
|
||||
return true, nil
|
||||
}
|
|
@ -760,17 +760,23 @@ func initWorkingDir(opts options) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// Can we use the current directory to store application data?
|
||||
pwdAvaliable, err := aghos.CurrentDirAvaliable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.workDir != "" {
|
||||
// If there is a custom config file, use it's directory as our working dir
|
||||
Context.workDir = opts.workDir
|
||||
} else if !execDirAvaliable() {
|
||||
} else if pwdAvaliable {
|
||||
// If running as a service and from /usr/bin/ use /var/lib for working dir instead of
|
||||
// /usr/bin/data
|
||||
Context.workDir = "/var/lib/AdGuardHome"
|
||||
|
||||
// Create dir if it does not already exist
|
||||
if err := os.MkdirAll(Context.workDir, 0755); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("os.MkdirAll: %s: %w", Context.workDir, err)
|
||||
}
|
||||
} else {
|
||||
Context.workDir = filepath.Dir(execPath)
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -306,7 +305,7 @@ func handleServiceStatusCommand(s service.Service) {
|
|||
// handleServiceStatusCommand handles service "install" command
|
||||
func handleServiceInstallCommand(s service.Service) {
|
||||
// Set the binary's permissions and move to /usr/bin (if on linux)
|
||||
if err := secureBinary(); err != nil {
|
||||
if err := aghos.SecureBinary(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -687,64 +686,3 @@ rc_bg=YES
|
|||
|
||||
rc_cmd $1
|
||||
`
|
||||
|
||||
// secureBinary is used before service.Install(). This function protects AdGuardHome from
|
||||
// privilege escalation vulnerabilities caused by writable files
|
||||
func secureBinary() error {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
// TODO: support windows service support securely
|
||||
// Set file owner to admin/system and public permissions read-only
|
||||
return errors.Error("you currently cannot install adguardhome as a service on window")
|
||||
default:
|
||||
return secureBinaryUnix()
|
||||
}
|
||||
}
|
||||
|
||||
func secureBinaryUnix() error {
|
||||
// Installalation can only be completed with root privileges, so check and handle if not
|
||||
if os.Getuid() != 0 {
|
||||
return errors.Error("permission denied. Root privileges required")
|
||||
}
|
||||
|
||||
// Get current file path
|
||||
binary, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Change owner to root:root
|
||||
err = os.Chown(binary, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set permissions to root(read,write,exec), group(read,exec), public(read)
|
||||
// This combined with changing the owner make the file undeletable without root privlages
|
||||
// UNLESS THE PARENT FOLDER IS WRITABLE!
|
||||
if err := os.Chmod(binary, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move binary to the PATH in a folder which is read-only to non root users
|
||||
// If already moved, this is a no-op
|
||||
if err := os.Rename(binary, "/usr/bin/AdGuardHome"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// execDirAvaliable returns true if the executable's current folder is avaliable to be
|
||||
// used as a workDir.
|
||||
// If AdGuardHome is running as a service, it should not use the binary's location as a
|
||||
// workDir, thus this function will return false.
|
||||
func execDirAvaliable() bool {
|
||||
binary, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// If installed in /usr/bin do not use /usr/bin/data to store files
|
||||
return filepath.Dir(binary) != "/usr/bin"
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue