fix(openclaw): reject leading-dash targets (flag-injection) + failure-record schema

This commit is contained in:
CyberMind-FR 2026-07-09 05:56:57 +02:00
parent fe10cf5dcd
commit 87a345a530
2 changed files with 9 additions and 4 deletions

View File

@ -15,7 +15,9 @@ SCANS_DIR="${SECUBOX_OPENCLAW_SCANS:-/var/lib/secubox/openclaw/scans}"
err() { echo "[ERROR] $*" >&2; }
# ---- injection guards (defense in depth; API validates too) ----
_valid_target() { [[ "$1" =~ ^[A-Za-z0-9._:@/-]+$ ]]; }
# Require an alphanumeric first char so a leading '-' can never be parsed as a
# flag by dig/nmap/whois (flag-injection). Domains/IPs/emails/CIDRs all qualify.
_valid_target() { [[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9._:@/-]*$ ]]; }
_valid_scanid() { [[ "$1" =~ ^[a-f0-9]{8}$ ]]; }
_valid_type() { case "$1" in domain|ip|email|dns|whois|certs|ports) return 0;; *) return 1;; esac; }
@ -97,7 +99,7 @@ cmd_scan() {
_valid_type "$type" || { err "bad type"; return 2; }
_valid_target "$target"|| { err "bad target"; return 2; }
_valid_scanid "$id" || { err "bad id"; return 2; }
lxc_running || { _scan_write "$id" "$(jq -nc --arg i "$id" --arg t "$type" --arg g "$target" '{id:$i,type:$t,target:$g,status:"failed",error:"container not running",results:null}')"; return 1; }
lxc_running || { local now; now="$(date -u +%FT%TZ)"; _scan_write "$id" "$(jq -nc --arg i "$id" --arg t "$type" --arg g "$target" --arg n "$now" '{id:$i,type:$t,target:$g,status:"failed",started_at:$n,finished_at:$n,results:null,error:"container not running"}')"; return 1; }
local started; started="$(date -u +%FT%TZ)"
local raw rc
case "$type" in
@ -106,8 +108,10 @@ cmd_scan() {
certs) raw="$(lxc_attach 'curl -s --max-time 20 "https://crt.sh/?q=$1&output=json"' "$target" 2>&1)"; rc=$? ;;
ports) raw="$(lxc_attach 'nmap -Pn -T4 --top-ports 100 -oG - "$1"' "$target" 2>&1)"; rc=$? ;;
ip) raw="$(lxc_attach 'nmap -Pn -T4 -sV --top-ports 200 "$1"' "$target" 2>&1)"; rc=$? ;;
domain) raw="$(lxc_attach 'echo "== DNS =="; dig +noall +answer ANY "$1"; echo "== WHOIS =="; whois -- "$1" 2>/dev/null | head -40; echo "== CERTS =="; curl -s --max-time 20 "https://crt.sh/?q=$1&output=json" | head -c 20000' "$target" 2>&1)"; rc=$? ;;
email) raw="$(lxc_attach 'd="${1#*@}"; echo "== MX =="; dig +short MX "$d"; echo "== SPF =="; dig +short TXT "$d" | grep -i spf' "$target" 2>&1)"; rc=$? ;;
# domain/email are OSINT aggregates: partial data is expected and the
# trailing head/grep would mask the real tool rc, so always report done.
domain) raw="$(lxc_attach 'echo "== DNS =="; dig +noall +answer ANY "$1"; echo "== WHOIS =="; whois -- "$1" 2>/dev/null | head -40; echo "== CERTS =="; curl -s --max-time 20 "https://crt.sh/?q=$1&output=json" | head -c 20000' "$target" 2>&1)"; rc=0 ;;
email) raw="$(lxc_attach 'd="${1#*@}"; echo "== MX =="; dig +short MX "$d"; echo "== SPF =="; dig +short TXT "$d" | grep -i spf' "$target" 2>&1)"; rc=0 ;;
*) err "unhandled type"; return 2 ;;
esac
local status="completed"; [ "$rc" -ne 0 ] && status="failed"

View File

@ -7,6 +7,7 @@ ok() { "$CTL" __guard "$1" "$2" >/dev/null 2>&1 && echo "PASS accept $1 '$2'" |
no() { "$CTL" __guard "$1" "$2" >/dev/null 2>&1 && { echo "FAIL should-reject $1 '$2'"; fail=1; } || echo "PASS reject $1 '$2'"; }
ok target "example.com"; ok target "192.168.1.10"; ok target "10.0.0.0/24"; ok target "a@b.com"
no target 'a;rm -rf /'; no target 'a b'; no target "$(printf 'a\nb')"
no target '-f/etc/hostname'; no target '-iL/tmp/x'
ok scanid "a1b2c3d4"; no scanid "XYZ"; no scanid "a1b2c3d4e5"
ok type domain; ok type ip; ok type email; ok type dns; ok type whois; ok type certs; ok type ports; no type pwn
exit $fail