Files
goclaw/internal/tools/shell_deny_groups.go
T
Viet Tran ce333c70f3 fix(security): followup hardening — ILIKE ESCAPE, allowlist logging, shell deny, tests (#251)
- Add explicit ESCAPE '\' clause to all ILIKE queries (knowledge_graph,
  custom_tools, channel_instances, channel_contacts) for correct wildcard
  escaping regardless of PostgreSQL standard_conforming_strings setting
- Log warning when filterAllowedKeys drops unknown fields for debuggability
- Widen base64 decode shell deny pattern to catch -di, -dw0 variants
- Add unit tests for filterAllowedKeys, pqStringArray, scanStringArray,
  pqStringArray↔scanStringArray roundtrip, limitedBuffer, base64 deny
2026-03-18 07:48:48 +07:00

260 lines
9.6 KiB
Go

package tools
import (
"regexp"
)
// DenyGroup is a named set of shell command deny patterns.
type DenyGroup struct {
Name string // machine name: "package_install"
Description string // human label: "Package Installation"
Default bool // true = denied by default
Patterns []*regexp.Regexp // deny patterns for this group
}
// DenyGroupRegistry holds all known deny groups, keyed by name.
// All groups are ON (denied) by default — admin must explicitly allow.
var DenyGroupRegistry = map[string]*DenyGroup{
"destructive_ops": {
Name: "destructive_ops",
Description: "Destructive Operations",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\brm\s+-[rf]{1,2}\b`),
regexp.MustCompile(`\brm\s+.*--recursive`),
regexp.MustCompile(`\brm\s+.*--force`),
regexp.MustCompile(`\bdel\s+/[fq]\b`),
regexp.MustCompile(`\brmdir\s+/s\b`),
regexp.MustCompile(`\b(mkfs|diskpart)\b|\bformat\s`),
regexp.MustCompile(`\bdd\s+if=`),
regexp.MustCompile(`>\s*/dev/sd[a-z]\b`),
regexp.MustCompile(`\b(shutdown|reboot|poweroff)\b`),
regexp.MustCompile(`:\(\)\s*\{.*\};\s*:`), // fork bomb
},
},
"data_exfiltration": {
Name: "data_exfiltration",
Description: "Data Exfiltration",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\bcurl\b.*\|\s*(ba)?sh\b`), // curl | sh
regexp.MustCompile(`\bcurl\b.*(-d\b|-F\b|--data|--upload|--form|-T\b|(-X|--request)\s*P(UT|OST|ATCH))`), // curl POST/PUT
regexp.MustCompile(`\bwget\b.*-O\s*-\s*\|\s*(ba)?sh\b`), // wget | sh
regexp.MustCompile(`\bwget\b.*(--post-(data|file)|--method=P(UT|OST|ATCH)|--body-data)`), // wget POST
regexp.MustCompile(`\b(nslookup|dig|host)\b`), // DNS exfiltration
regexp.MustCompile(`/dev/tcp/`), // bash tcp redirect
regexp.MustCompile(`\b(curl|wget)\b.*\blocalhost\b`), // curl/wget to localhost
regexp.MustCompile(`\b(curl|wget)\b.*\b127\.0\.0\.1\b`), // curl/wget to 127.0.0.1
regexp.MustCompile(`\b(curl|wget)\b.*\b0\.0\.0\.0\b`), // curl/wget to 0.0.0.0
},
},
"reverse_shell": {
Name: "reverse_shell",
Description: "Reverse Shell",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\b(nc|ncat|netcat)\b.*(\s+-[a-z]|\s+\d+\.\d+\.\d+\.\d+|\s+localhost\b)`),
regexp.MustCompile(`\bsocat\b`),
regexp.MustCompile(`\bopenssl\b.*s_client`),
regexp.MustCompile(`\btelnet\b.*\d+`),
regexp.MustCompile(`\bpython[23]?\b.*(import|from)\s+(socket|http|urllib|requests|httpx|aiohttp)\b`),
regexp.MustCompile(`\bperl\b.*-e\s*.*\b[Ss]ocket\b`),
regexp.MustCompile(`\bruby\b.*-e\s*.*\b(TCPSocket|Socket)\b`),
regexp.MustCompile(`\bnode\b.*-e\s*.*\b(net\.|http\.|https\.|fetch\(|axios|got\(|undici)\b`),
regexp.MustCompile(`\bnode\b.*-e\s*.*require\s*\(\s*['"]https?['"]\s*\)`),
regexp.MustCompile(`\bawk\b.*/inet/`),
regexp.MustCompile(`\bmkfifo\b`),
},
},
"code_injection": {
Name: "code_injection",
Description: "Code Injection & Eval",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\beval\s*\$`),
regexp.MustCompile(`\bbase64\s+(-d\w*|--decode)\b.*\|\s*(ba)?sh\b`),
},
},
"privilege_escalation": {
Name: "privilege_escalation",
Description: "Privilege Escalation",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\bsudo\b`),
regexp.MustCompile(`\bsu\s+-`),
regexp.MustCompile(`\bnsenter\b`),
regexp.MustCompile(`\bunshare\b`),
regexp.MustCompile(`\b(mount|umount)\b`),
regexp.MustCompile(`\b(capsh|setcap|getcap)\b`),
},
},
"dangerous_paths": {
Name: "dangerous_paths",
Description: "Dangerous Path Operations",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\bchmod\s+[0-7]{3,4}\s+/`),
regexp.MustCompile(`\bchown\b.*\s+/`),
regexp.MustCompile(`\bchmod\b.*\+x.*/tmp/`),
regexp.MustCompile(`\bchmod\b.*\+x.*/var/tmp/`),
regexp.MustCompile(`\bchmod\b.*\+x.*/dev/shm/`),
},
},
"env_injection": {
Name: "env_injection",
Description: "Environment Variable Injection",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\bLD_PRELOAD\s*=`),
regexp.MustCompile(`\bDYLD_INSERT_LIBRARIES\s*=`),
regexp.MustCompile(`\bLD_LIBRARY_PATH\s*=`),
regexp.MustCompile(`/etc/ld\.so\.preload`),
regexp.MustCompile(`\bGIT_EXTERNAL_DIFF\s*=`),
regexp.MustCompile(`\bGIT_DIFF_OPTS\s*=`),
regexp.MustCompile(`\bBASH_ENV\s*=`),
regexp.MustCompile(`\bENV\s*=.*\bsh\b`),
},
},
"container_escape": {
Name: "container_escape",
Description: "Container Escape",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`/var/run/docker\.sock|docker\.(sock|socket)`),
regexp.MustCompile(`/proc/sys/(kernel|fs|net)/`),
regexp.MustCompile(`/sys/(kernel|fs|class|devices)/`),
},
},
"crypto_mining": {
Name: "crypto_mining",
Description: "Crypto Mining",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\b(xmrig|cpuminer|minerd|cgminer|bfgminer|ethminer|nbminer|t-rex|phoenixminer|lolminer|gminer|claymore)\b`),
regexp.MustCompile(`stratum\+tcp://|stratum\+ssl://`),
},
},
"filter_bypass": {
Name: "filter_bypass",
Description: "Filter Bypass (CVE mitigations)",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\bsed\b.*['"]/e\b`),
regexp.MustCompile(`\bsort\b.*--compress-program`),
regexp.MustCompile(`\bgit\b.*(--upload-pack|--receive-pack|--exec)=`),
regexp.MustCompile(`\b(rg|grep)\b.*--pre=`),
regexp.MustCompile(`\bman\b.*--html=`),
regexp.MustCompile(`\bhistory\b.*-[saw]\b`),
regexp.MustCompile(`\$\{[^}]*@[PpEeAaKk]\}`),
},
},
"network_recon": {
Name: "network_recon",
Description: "Network Reconnaissance & Tunneling",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\b(nmap|masscan|zmap|rustscan)\b`),
regexp.MustCompile(`\b(ssh|scp|sftp)\b.*@`),
regexp.MustCompile(`\b(chisel|frp|ngrok|cloudflared|bore|localtunnel)\b`),
},
},
"package_install": {
Name: "package_install",
Description: "Package Installation",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\bpip3?\s+install\b`),
regexp.MustCompile(`\bnpm\s+install\b`),
regexp.MustCompile(`\bnpm\s+i\b`),
regexp.MustCompile(`\bapk\s+(add|del)\b`),
regexp.MustCompile(`\bdoas\s+apk\b`),
regexp.MustCompile(`\byarn\s+(add|global)\b`),
regexp.MustCompile(`\bpnpm\s+(add|install)\b`),
regexp.MustCompile(`\bpip3?\s+uninstall\b`),
regexp.MustCompile(`\bnpm\s+uninstall\b`),
regexp.MustCompile(`\bpython[23]?\b.*-m\s+pip\b`),
},
},
"persistence": {
Name: "persistence",
Description: "Persistence Mechanisms",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\bcrontab\b`),
regexp.MustCompile(`>\s*~/?\.(bashrc|bash_profile|profile|zshrc)`),
regexp.MustCompile(`\btee\b.*\.(bashrc|bash_profile|profile|zshrc)`),
},
},
"process_control": {
Name: "process_control",
Description: "Process Manipulation",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`\bkill\s+-9\s`),
regexp.MustCompile(`\b(killall|pkill)\b`),
},
},
"env_dump": {
Name: "env_dump",
Description: "Environment Variable Dumping",
Default: true,
Patterns: []*regexp.Regexp{
regexp.MustCompile(`^\s*env\s*$`),
regexp.MustCompile(`^\s*env\s*\|`),
regexp.MustCompile(`^\s*env\s*>\s`),
regexp.MustCompile(`\bprintenv\b`),
regexp.MustCompile(`^\s*(set|export\s+-p|declare\s+-x)\s*($|\|)`),
regexp.MustCompile(`\bcompgen\s+-e\b`),
regexp.MustCompile(`/proc/[^/]+/environ`),
regexp.MustCompile(`/proc/self/environ`),
regexp.MustCompile(`(?i)\bstrings\b.*/proc/`),
regexp.MustCompile(`(?i)\becho\b.*\$\{?GOCLAW_(GATEWAY_TOKEN|ENCRYPTION_KEY|POSTGRES_DSN)`),
regexp.MustCompile(`(?i)\bprintf\b.*\$\{?GOCLAW_(GATEWAY_TOKEN|ENCRYPTION_KEY|POSTGRES_DSN)`),
regexp.MustCompile(`\bpython[23]?\b.*os\.(environ|getenv).*GOCLAW_`),
regexp.MustCompile(`\bnode\b.*-e.*process\.env\.GOCLAW_`),
},
},
}
// DenyGroupNames returns all registered group names in stable order.
func DenyGroupNames() []string {
return []string{
"destructive_ops", "data_exfiltration", "reverse_shell", "code_injection",
"privilege_escalation", "dangerous_paths", "env_injection", "container_escape",
"crypto_mining", "filter_bypass", "network_recon", "package_install",
"persistence", "process_control", "env_dump",
}
}
// ResolveDenyPatterns merges group defaults with overrides and returns the effective deny patterns.
// overrides: map[groupName]enabled — true=deny, false=allow. nil = all defaults.
func ResolveDenyPatterns(overrides map[string]bool) []*regexp.Regexp {
var patterns []*regexp.Regexp
for _, name := range DenyGroupNames() {
group := DenyGroupRegistry[name]
enabled := group.Default
if overrides != nil {
if v, ok := overrides[name]; ok {
enabled = v
}
}
if enabled {
patterns = append(patterns, group.Patterns...)
}
}
return patterns
}
// IsGroupDenied checks if a specific deny group is active for the given context.
func IsGroupDenied(overrides map[string]bool, group string) bool {
if overrides != nil {
if v, ok := overrides[group]; ok {
return v
}
}
if g, ok := DenyGroupRegistry[group]; ok {
return g.Default
}
return true // unknown group = denied
}