T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/mercury.sh:75
- Finding
- Arbitrary Command Execution Through Invoice Amount Injection## Vulnerability Details **File Location**: `scripts/mercury.sh`, lines 75–106 **Vulnerability Type**: Python source-code injection caused by unsafe string interpolation **Risk Level**: High ### Vulnerable 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 `AMOUNT` argument is copied directly into the source code supplied to `python3 -c`. It is not parsed as an integer or passed to Python through a data-only interface such as `sys.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 1 ``` Python executes the command and then uses `1` as the invoice amount. This makes the flaw ex ...[truncated 1783 chars]
- 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`.
