T09 · Insecure Skill Coding Practices
- Location
scripts/mercury.sh:75- Finding
Arbitrary Command Execution Through Invoice Amount Injection
- Content
View full analysis
Vulnerability Details
File Location:
scripts/mercury.sh, lines 75–106
Vulnerability Type: Python source-code injection caused by unsafe string interpolation
Risk Level: HighVulnerable Code
bash create-invoice) # Args: customer_id amount_cents due_date memo [invoice_number] CUSTOMER_ID="$1" AMOUNT="$2" DUE_DATE="$3" MEMO="${4:-}" INV_NUM="${5:-}" TODAY=$(date +%Y-%m-%d) # Auto-increment invoice number if not provided if [ -z "$INV_NUM" ]; then LAST=$(call GET /ar/invoices | python3 -c " import json, sys, re data = json.load(sys.stdin) nums = [] for inv in data.get('invoices', []): m = re.search(r'INV-(\d+)', inv.get('invoiceNumber','')) if m: nums.append(int(m.group(1))) print(max(nums)+1 if nums else 1) ") INV_NUM="INV-$LAST" fi PAYLOAD=$(python3 -c " import json d = { 'invoiceNumber': '$INV_NUM', 'invoiceDate': '$TODAY', 'dueDate': '$DUE_DATE', 'customerId': '$CUSTOMER_ID', 'destinationAccountId': '4ca92254-e020-11f0-ab61-779167c16d40', 'amount': $AMOUNT, 'achDebitEnabled': True, 'creditCardEnabled': False } if '$MEMO': d['payerMemo'] = '$MEMO' print(json.dumps(d)) ")Technical Analysis
The
AMOUNTargument is copied directly into the source code supplied topython3 -c. It is not parsed as an integer or passed to Python through a data-only interface such assys.argv.Consequently, the value is evaluated as an arbitrary Python expression when the invoice payload is constructed. An attacker able to influence command arguments can provide an expression that imports Python modules and invokes operating-system commands. For example, an amount shaped like the following is syntactically valid in the generated dictionary:
text __import__('os').system('id') or 1Python executes the command and then uses
1as the invoice amount. This makes the flaw ex ...[truncated 1783 chars]- Remediation
View remediation
Remediation Suggestions
- Never interpolate shell variables into Python source code.
- Pass every invoice field as a positional argument or through standard input, then construct the JSON object from those data values.
- Parse the amount with
int()and reject non-decimal input, zero or negative values, and values outside documented business limits. - Validate dates, UUIDs, invoice numbers, and memo lengths using strict allowlists or dedicated parsers.
- Keep business-level authorization checks separate from syntactic validation.
- Add regression tests containing quotes, backslashes, newlines, Python expressions, and shell metacharacters.
A safer construction pattern is:
bash PAYLOAD=$(python3 - \ "$INV_NUM" "$TODAY" "$DUE_DATE" "$CUSTOMER_ID" "$AMOUNT" "$MEMO" <<'PY' import json import sys invoice_number, invoice_date, due_date, customer_id, raw_amount, memo = sys.argv[1:] try: amount = int(raw_amount) except ValueError: raise SystemExit("Amount must be an integer number of cents") if amount <= 0: raise SystemExit("Amount must be positive") payload = { "invoiceNumber": invoice_number, "invoiceDate": invoice_date, "dueDate": due_date, "customerId": customer_id, "destinationAccountId": "4ca92254-e020-11f0-ab61-779167c16d40", "amount": amount, "achDebitEnabled": True, "creditCardEnabled": False, } if memo: payload["payerMemo"] = memo print(json.dumps(payload)) PY )The same data-only argument handling should be applied to all interpolated fields, not only
AMOUNT.
