mirror of
https://github.com/kernelstub/Ferrum
synced 2026-06-21 13:55:09 +00:00
68 lines
2.3 KiB
Go
68 lines
2.3 KiB
Go
package services
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"ferrum/core"
|
|
win "ferrum/windows/facade"
|
|
)
|
|
|
|
func init() { core.Register(Module{}) }
|
|
|
|
type Module struct{}
|
|
|
|
func (Module) Name() string { return "services" }
|
|
|
|
func (Module) Description() string {
|
|
return "Inventory Windows services and highlight audit-worthy configuration"
|
|
}
|
|
|
|
func (Module) Run(ctx *core.Context) error {
|
|
ctx.Logger.Info("Enumerating Windows services...")
|
|
services, err := win.EnumerateServices()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.Logger.Info(fmt.Sprintf("Services enumerated: %d", len(services)))
|
|
reported := 0
|
|
for _, service := range services {
|
|
ctx.Logger.Verbose(fmt.Sprintf("service inventory : name=%s display=%s state=%s start=%s account=%s pid=%d type=%d path=%s", service.Name, service.DisplayName, service.State, service.StartType, service.Account, service.ProcessID, service.ServiceType, service.BinaryPath))
|
|
reasons := serviceReasons(service)
|
|
if len(reasons) == 0 {
|
|
continue
|
|
}
|
|
reported++
|
|
ctx.Logger.Success(fmt.Sprintf("%s > %s", service.Name, strings.Join(reasons, ", ")))
|
|
ctx.Logger.Verbose(fmt.Sprintf("%s : state=%s start=%s account=%s pid=%d path=%s", service.Name, service.State, service.StartType, service.Account, service.ProcessID, service.BinaryPath))
|
|
}
|
|
if reported == 0 {
|
|
ctx.Logger.Info("No service configuration stood out from the default heuristics.")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func serviceReasons(service win.ServiceInfo) []string {
|
|
reasons := []string{}
|
|
path := strings.TrimSpace(service.BinaryPath)
|
|
lowerPath := strings.ToLower(path)
|
|
if service.State == "Running" && service.Account != "" && !strings.Contains(strings.ToLower(service.Account), "localsystem") {
|
|
reasons = append(reasons, "non-System running account")
|
|
}
|
|
if isUnquotedPathWithSpaces(path) {
|
|
reasons = append(reasons, "unquoted path with spaces")
|
|
}
|
|
if strings.Contains(lowerPath, `\users\`) || strings.Contains(lowerPath, `\temp\`) || strings.Contains(lowerPath, `\programdata\`) {
|
|
reasons = append(reasons, "user-writable-looking path")
|
|
}
|
|
if service.StartType == "Auto" && service.State != "Running" {
|
|
reasons = append(reasons, "auto-start not running")
|
|
}
|
|
return reasons
|
|
}
|
|
|
|
func isUnquotedPathWithSpaces(path string) bool {
|
|
path = strings.TrimSpace(path)
|
|
return strings.Contains(path, " ") && !strings.HasPrefix(path, `"`)
|
|
}
|