T09 · Insecure Skill Coding Practices
- Location
build.sh:35- Finding
PowerShell Command Injection Through Unsanitized Version Value
- Content
View full analysis
/dev/null 2>&1 || command -v powershell >/dev/null 2>&1; then PSH="$(command -v powershell.exe 2>/dev/null || command -v powershell)" WIN_SRC="$(cygpath -w "$STAGE_DIR" 2>/dev/null || echo "$STAGE_DIR")" WIN_OUT="$(cygpath -w "$OUT_DIR/$ZIP_NAME" 2>/dev/null || echo "$OUT_DIR/$ZIP_NAME")" "$PSH" -NoProfile -Command "Compress-Archive -Path '${WIN_SRC}' -DestinationPath '${WIN_OUT}' -Force" ``` The affected values originate from the version selection logic: ```bash if [[ -n "${1:-}" ]]; then VER="$1" else VER="$(grep '^version:' "$SRC_DIR/SKILL.md" | head -1 | sed -E 's/version: *//' | tr -d ' ')" fi ``` ### Technical Analysis The build version, `VER`, is accepted either from the first command-line argument or from the `version:` field in `SKILL.md`. It is not validated against a strict version format. `VER` is incorporated into `OUT_DIR`, `ZIP_NAME`, and ultimately `WIN_OUT`. The script then interpolates `WIN_OUT` directly into PowerShell source code supplied through the `-Command` parameter: ```powershell Compress-Archive -Path '${WIN_SRC}' -DestinationPath '${WIN_OUT}' -Force ``` Shell quoting does not protect the value once it becomes part of the PowerShell program. In PowerShell, a single quote inside `WIN_OUT` can terminate the quoted destination path. Additional PowerShell statements can then be inserted before the remainder of the generated command. This path is exploitable when neither `zip` nor `7z` is available and the script selects its PowerShell archive backend. ### Attack Path 1. An attacker supplies a malicious first argument to `build.sh`, or modifies the `version:` value in `SKILL.md`. 2. The victim runs the build script in an environment where PowerShell is selected as the archive backend. 3. The attacker-controlled versi ...[truncated 1376 chars]- Remediation
View remediation
&2 exit 1 fi ``` If prerelease or build metadata is required, use a carefully tested allowlist expression rather than permitting arbitrary characters. 2. **Do not interpolate paths into PowerShell source code.** Pass paths as positional arguments to a script block: ```bash "$PSH" -NoProfile -Command \ '& { param([string]$SourcePath, [string]$DestinationPath) Compress-Archive -LiteralPath $SourcePath -DestinationPath $DestinationPath -Force }' \ "$WIN_SRC" "$WIN_OUT" ``` Verify argument behavior for both native PowerShell and `powershell.exe` under WSL, Git Bash, and other supported environments. 3. **Use literal-path semantics.** Prefer `-LiteralPath` for attacker-influenced or dynamically generated paths so wildcard characters are not interpreted. 4. **Validate manifest-derived data independently.** Do not assume `SKILL.md` is trusted merely because it is part of the repository. Apply the same validation whether the version comes from an argument or the manifest. 5. **Add negative security tests.** Confirm that versions containing quotes, semicolons, newlines, command separators, wildcard characters, and path traversal sequences are rejected before directories are created or PowerShell is invoked. 6. **Apply least privilege to release builds.** Run packaging jobs without administrative privileges and without unnecessary production credentials to limit impact if another build-command vulnerability is introduced. ]]>
