mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
- 将 avdetect 的进程匹配逻辑合并到 systeminfo 插件 - 修复进程匹配使用 Contains 导致大量误报,改为精确匹配 - 修正 auto.json 中 Microsoft Security Essentials 为 Microsoft Defender - 使用 map 索引优化进程匹配性能 - 清理废弃的 envinfo/avdetect i18n key
349 lines
8.6 KiB
Go
349 lines
8.6 KiB
Go
//go:build (plugin_systeminfo || !plugin_selective) && !no_local
|
|
|
|
package local
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/shadow1ng/fscan/common"
|
|
"github.com/shadow1ng/fscan/common/i18n"
|
|
"github.com/shadow1ng/fscan/plugins"
|
|
)
|
|
|
|
//go:embed auto.json
|
|
var avDatabase []byte
|
|
|
|
type avProduct struct {
|
|
Processes []string `json:"processes"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type SystemInfoPlugin struct {
|
|
plugins.BasePlugin
|
|
output strings.Builder
|
|
}
|
|
|
|
func NewSystemInfoPlugin() *SystemInfoPlugin {
|
|
return &SystemInfoPlugin{
|
|
BasePlugin: plugins.NewBasePlugin("systeminfo"),
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) log(key string, args ...interface{}) {
|
|
msg := i18n.Tr(key, args...)
|
|
common.LogInfo(msg)
|
|
p.output.WriteString(msg + "\n")
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) logSuccess(key string, args ...interface{}) {
|
|
msg := i18n.Tr(key, args...)
|
|
common.LogSuccess(msg)
|
|
p.output.WriteString(msg + "\n")
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
|
common.LogSuccess(i18n.GetText("systeminfo_start"))
|
|
|
|
p.collectBasicInfo()
|
|
p.collectNetworkInfo()
|
|
p.collectPrivilegeInfo()
|
|
p.collectPlatformInfo()
|
|
p.collectAVInfo()
|
|
p.collectSensitiveEnvVars()
|
|
|
|
return &plugins.Result{
|
|
Success: true,
|
|
Type: plugins.ResultTypeService,
|
|
Output: p.output.String(),
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) collectBasicInfo() {
|
|
p.log("systeminfo_os", runtime.GOOS)
|
|
p.log("systeminfo_arch", runtime.GOARCH)
|
|
p.log("systeminfo_cpu", runtime.NumCPU())
|
|
|
|
if hostname, err := os.Hostname(); err == nil {
|
|
p.log("systeminfo_hostname", hostname)
|
|
}
|
|
if u, err := user.Current(); err == nil {
|
|
p.log("systeminfo_user", u.Username)
|
|
if u.HomeDir != "" {
|
|
p.log("systeminfo_homedir", u.HomeDir)
|
|
}
|
|
}
|
|
if wd, err := os.Getwd(); err == nil {
|
|
p.log("systeminfo_workdir", wd)
|
|
}
|
|
p.log("systeminfo_tempdir", os.TempDir())
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) collectNetworkInfo() {
|
|
ifaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, iface := range ifaces {
|
|
if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 {
|
|
continue
|
|
}
|
|
addrs, err := iface.Addrs()
|
|
if err != nil || len(addrs) == 0 {
|
|
continue
|
|
}
|
|
var ips []string
|
|
for _, addr := range addrs {
|
|
ips = append(ips, addr.String())
|
|
}
|
|
p.log("systeminfo_iface", iface.Name, strings.Join(ips, ", "), iface.HardwareAddr.String())
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) collectPrivilegeInfo() {
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
if out, err := p.runCommand("net", "session"); err == nil {
|
|
_ = out
|
|
p.logSuccess("systeminfo_privilege", "Administrator")
|
|
} else {
|
|
p.log("systeminfo_privilege", "Normal User")
|
|
}
|
|
if out, err := p.runCommand("whoami", "/groups"); err == nil {
|
|
if strings.Contains(out, "S-1-5-32-544") {
|
|
p.logSuccess("systeminfo_privilege_group", "Administrators")
|
|
}
|
|
}
|
|
case "linux", "darwin":
|
|
if uid := os.Getuid(); uid == 0 {
|
|
p.logSuccess("systeminfo_privilege", "root")
|
|
} else {
|
|
p.log("systeminfo_privilege", fmt.Sprintf("uid=%d", uid))
|
|
}
|
|
if out, err := p.runCommand("id"); err == nil {
|
|
p.log("systeminfo_id_info", strings.TrimSpace(out))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) collectPlatformInfo() {
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
p.collectWindowsInfo()
|
|
case "linux":
|
|
p.collectLinuxInfo()
|
|
case "darwin":
|
|
p.collectDarwinInfo()
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) collectWindowsInfo() {
|
|
if out, err := p.runCommand("cmd", "/c", "ver"); err == nil {
|
|
p.log("systeminfo_winver", strings.TrimSpace(out))
|
|
}
|
|
if out, err := p.runCommand("cmd", "/c", "echo %USERDOMAIN%"); err == nil {
|
|
domain := strings.TrimSpace(out)
|
|
if domain != "" && domain != "%USERDOMAIN%" {
|
|
p.log("systeminfo_domain", domain)
|
|
}
|
|
}
|
|
|
|
if out, err := p.runCommand("netsh", "advfirewall", "show", "allprofiles", "state"); err == nil {
|
|
for _, line := range strings.Split(out, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if strings.Contains(line, "ON") || strings.Contains(line, "OFF") {
|
|
p.log("systeminfo_firewall", line)
|
|
}
|
|
}
|
|
}
|
|
|
|
if out, err := p.runCommand("wmic", "qfe", "get", "HotFixID,InstalledOn"); err == nil {
|
|
lines := strings.Split(strings.TrimSpace(out), "\n")
|
|
patches := 0
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(strings.TrimSpace(line), "KB") {
|
|
patches++
|
|
}
|
|
}
|
|
if patches > 0 {
|
|
p.log("systeminfo_patches", patches)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) collectLinuxInfo() {
|
|
if out, err := p.runCommand("uname", "-a"); err == nil {
|
|
p.log("systeminfo_kernel", strings.TrimSpace(out))
|
|
}
|
|
if data, err := os.ReadFile("/etc/os-release"); err == nil {
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
if strings.HasPrefix(line, "PRETTY_NAME=") {
|
|
name := strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"")
|
|
p.log("systeminfo_distro", name)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if out, err := p.runCommand("iptables", "-L", "-n", "--line-numbers"); err == nil {
|
|
ruleCount := 0
|
|
for _, line := range strings.Split(out, "\n") {
|
|
if len(line) > 0 && line[0] >= '0' && line[0] <= '9' {
|
|
ruleCount++
|
|
}
|
|
}
|
|
p.log("systeminfo_firewall_rules", ruleCount)
|
|
}
|
|
|
|
if out, err := p.runCommand("sudo", "-l", "-n"); err == nil {
|
|
if strings.Contains(out, "ALL") {
|
|
p.logSuccess("systeminfo_sudo", "ALL commands")
|
|
} else if strings.Contains(out, "NOPASSWD") {
|
|
p.logSuccess("systeminfo_sudo", "NOPASSWD entries found")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) collectDarwinInfo() {
|
|
if out, err := p.runCommand("uname", "-a"); err == nil {
|
|
p.log("systeminfo_kernel", strings.TrimSpace(out))
|
|
}
|
|
if out, err := p.runCommand("sw_vers"); err == nil {
|
|
for _, line := range strings.Split(out, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" {
|
|
p.log("systeminfo_macos_detail", line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) collectAVInfo() {
|
|
var avProducts map[string]avProduct
|
|
if err := json.Unmarshal(avDatabase, &avProducts); err != nil {
|
|
return
|
|
}
|
|
|
|
processes := p.getRunningProcesses()
|
|
if len(processes) == 0 {
|
|
return
|
|
}
|
|
|
|
// 建立进程名索引,O(1) 查找
|
|
processIndex := make(map[string][]string)
|
|
for _, proc := range processes {
|
|
name := proc
|
|
if idx := strings.Index(proc, " (PID: "); idx != -1 {
|
|
name = proc[:idx]
|
|
}
|
|
key := strings.ToLower(name)
|
|
processIndex[key] = append(processIndex[key], proc)
|
|
}
|
|
|
|
for avName, av := range avProducts {
|
|
var matched []string
|
|
for _, avProc := range av.Processes {
|
|
if procs, ok := processIndex[strings.ToLower(avProc)]; ok {
|
|
matched = append(matched, procs...)
|
|
}
|
|
}
|
|
if len(matched) > 0 {
|
|
p.logSuccess("systeminfo_antivirus", fmt.Sprintf("%s (%d个进程)", avName, len(matched)))
|
|
for _, proc := range matched {
|
|
p.log("systeminfo_av_process", proc)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) getRunningProcesses() []string {
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
return p.getWindowsProcesses()
|
|
case "linux", "darwin":
|
|
return p.getUnixProcesses()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) getWindowsProcesses() []string {
|
|
out, err := p.runCommand("tasklist", "/fo", "csv", "/nh")
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var processes []string
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if !strings.HasPrefix(line, "\"") {
|
|
continue
|
|
}
|
|
parts := strings.Split(line, "\",\"")
|
|
if len(parts) >= 2 {
|
|
name := strings.Trim(parts[0], "\"")
|
|
pid := strings.Trim(parts[1], "\"")
|
|
if name != "" && pid != "" {
|
|
processes = append(processes, fmt.Sprintf("%s (PID: %s)", name, pid))
|
|
}
|
|
}
|
|
}
|
|
return processes
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) getUnixProcesses() []string {
|
|
out, err := p.runCommand("ps", "-eo", "comm")
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var processes []string
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" && line != "COMMAND" {
|
|
processes = append(processes, line)
|
|
}
|
|
}
|
|
return processes
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) collectSensitiveEnvVars() {
|
|
keywords := []string{
|
|
"password", "passwd", "secret", "key", "token",
|
|
"auth", "credential", "api_key", "access_key",
|
|
}
|
|
for _, env := range os.Environ() {
|
|
parts := strings.SplitN(env, "=", 2)
|
|
if len(parts) != 2 || parts[1] == "" {
|
|
continue
|
|
}
|
|
name := strings.ToLower(parts[0])
|
|
for _, kw := range keywords {
|
|
if strings.Contains(name, kw) {
|
|
display := parts[1]
|
|
if len(display) > 8 {
|
|
display = display[:8] + "***"
|
|
}
|
|
p.logSuccess("systeminfo_sensitive_env", parts[0], display)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *SystemInfoPlugin) runCommand(name string, args ...string) (string, error) {
|
|
out, err := exec.Command(name, args...).Output()
|
|
return string(out), err
|
|
}
|
|
|
|
func init() {
|
|
RegisterLocalPlugin("systeminfo", func() Plugin {
|
|
return NewSystemInfoPlugin()
|
|
})
|
|
}
|