T09 · Insecure Skill Coding Practices
- Location
scripts/notify.sh:78- Finding
Arbitrary Shell Command Injection Through Unsafe eval
- Content
View full analysis
Vulnerability Details
File Location:
scripts/notify.sh, lines 78–94 and 98–107
Vulnerability Type: Shell command injection
Risk Level: HighVulnerable Code
bash build_cmd() { local target="$1" local cmd=(openclaw message send --channel "$CHANNEL" --target "$target" --message "$MESSAGE") if [[ -n "$MEDIA" ]]; then cmd+=(--media "$MEDIA") fi if [[ "$SILENT" == true ]]; then cmd+=(--silent) fi if [[ "$DRY_RUN" == true ]]; then cmd+=(--dry-run) fi if [[ "$VERBOSE" == true ]]; then cmd+=(--verbose) fi echo "${cmd[@]}" } # 发送单条 send_one() { local target="$1" local cmd cmd=$(build_cmd "$target") if [[ "$VERBOSE" == true ]]; then echo "📤 发送到 $CHANNEL:$target" fi eval "$cmd" }Targets can also originate from standard input:
bash while IFS= read -r line || [[ -n "$line" ]]; do # 跳过空行和注释 line=$(echo "$line" | xargs) [[ -z "$line" || "$line" == \#* ]] && continue count=$((count + 1)) if send_one "$line"; then : else echo "⚠️ 发送到 $line 失败" failed=$((failed + 1)) fiTechnical Analysis
The script initially constructs the OpenClaw invocation as a Bash array, which would preserve argument boundaries if executed directly. However,
build_cmdserializes that array into an unescaped string with:bash echo "${cmd[@]}"send_onethen passes the resulting string toeval. This causes Bash to parse attacker-controlled values a second time as shell syntax. Consequently, shell metacharacters and constructs contained in--channel,--target,--message,--media, or a target read from standard input can become executable syntax rather than literal OpenClaw arguments.Quoting values while constructing the original array does not mitigate the issue because those quote boundaries are discarded when the array is flattened. The subsequent
evalintroduces the vulnerable parsing stage.Attack Path
- An attacker supplies a crafted channel, ...[truncated 1505 chars]
- Remediation
View remediation
Remediation Suggestions
Remove
evaland preserve the command as a Bash array until execution. Construct and execute the array in the same function:bash send_one() { local target="$1" local cmd=( openclaw message send --channel "$CHANNEL" --target "$target" --message "$MESSAGE" ) if [[ -n "$MEDIA" ]]; then cmd+=(--media "$MEDIA") fi if [[ "$SILENT" == true ]]; then cmd+=(--silent) fi if [[ "$DRY_RUN" == true ]]; then cmd+=(--dry-run) fi if [[ "$VERBOSE" == true ]]; then cmd+=(--verbose) printf 'Sending to %s:%s\n' "$CHANNEL" "$target" fi "${cmd[@]}" }Apply these additional hardening measures:
-
Never pass user-controlled command text to
eval,bash -c, orsh -c. -
If command display is required, print a shell-escaped representation without executing it:
bash printf '%q ' "${cmd[@]}" printf '\n' -
Validate
CHANNELagainst an explicit allowlist of supported channel identifiers. -
Validate target formats according to the selected channel where practical.
-
Treat stdin batch files as untrusted input and pass every line only as a quoted array element.
-
Add regression tests using values containing spaces, semicolons, command substitutions, redirections, and newline characters to verify that they remain literal arguments.
-
Run scheduled notification jobs under a dedicated least-privileged account with access only to the credentials and files required for message delivery.
-
