T09 · Insecure Skill Coding Practices
- Location
scripts/check_payment.py:43- Finding
Forgeable Local Receipt Allows Payment-Gate Bypass
- Content
View full analysis
None: payload = { "email": email, "session_id": session_id, "ts": int(time.time()), "checksum": hashlib.sha256(f"{email}:{session_id}".encode()).hexdigest(), } RECEIPT_PATH.write_text(json.dumps(payload)) def _read_receipt() -> dict | None: if not RECEIPT_PATH.exists(): return None try: data = json.loads(RECEIPT_PATH.read_text()) expected = hashlib.sha256( f"{data['email']}:{data['session_id']}".encode() ).hexdigest() if data.get("checksum") == expected: return data except (json.JSONDecodeError, KeyError, TypeError): pass return None ``` ```python def check_payment_status() -> bool: """True if a valid local receipt exists.""" return _read_receipt() is not None ``` ### Technical Analysis The receipt checksum is a plain SHA-256 digest whose complete input—`email` and `session_id`—is stored in the same attacker-editable file. It contains no server-held secret and therefore provides no authenticity guarantee. Any local actor able to create or modify `~/.skill-payment-demo-receipt` can calculate a matching checksum. `check_payment_status()` accepts that receipt without querying Stripe or validating the session identifier, payment state, product, or user. ### Attack Path 1. Select an arbitrary email and arbitrary session identifier. 2. Calculate `SHA256(":")`. 3. Create `~/.skill-payment-demo-receipt` containing those values and the calculated checksum. 4. Invoke the Skill or call `check_payment_status()`. 5. `_read_receipt()` recomputes the attacker-known digest and accepts the forged receipt. 6. The Skill considers payment ...[truncated 357 chars]- Remediation
View remediation
