T09 · Insecure Skill Coding Practices
Error
- Location
- patterns.md:29
- Finding
- Incomplete Server-Side Order Validation Before Payment Capture<![CDATA[ ## Vulnerability Details **File Location**: `patterns.md`, lines 29–47 **Vulnerability Type**: Insufficient validation of a client-selected payment order **Risk Level**: High ### Complete Code Snippet ```javascript const captureOrder = async (orderId) => { const token = await getToken(); // First verify the order const order = await fetch(`https://api.paypal.com/v2/checkout/orders/${orderId}`, { headers: { 'Authorization': `Bearer ${token}` } }).then(r => r.json()); if (order.status !== 'APPROVED') { throw new Error(`Invalid order status: ${order.status}`); } // Then capture const capture = await fetch(`https://api.paypal.com/v2/checkout/orders/${orderId}/capture`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); return capture.json(); }; ``` ### Technical Analysis The capture function accepts an `orderId` that the documented frontend submits to the server. Before capture, the server retrieves the selected PayPal order but validates only that its status is `APPROVED`. The function does not verify that: - The order belongs to the authenticated application user or current checkout. - The PayPal order is linked to the expected internal order. - The amount equals the server-side expected amount. - The currency equals the expected currency. - The payee merchant ID is the intended merchant. - The order intent and purchase units match the expected transaction. This omission is especially significant because `SKILL.md` lines 74–84 explicitly state that amount, currency, and merchant must be checked before fulfillment. The operational capture pattern does not implement those checks. ### Attack Path 1. An attacker starts or identifies a lower-value PayPal order available through the same integration. 2. The attacker approves that lower-value order through PayPal. 3. During a higher-value application checkout, the attacker sends the approved lower-value ...[truncated 1085 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Treat `orderId` as an untrusted identifier, even if PayPal generated it. 2. Load the current internal order from server-side storage using the authenticated user and checkout context. 3. Require an immutable association between the PayPal order and the internal order, such as a server-generated `custom_id` or `invoice_id`. 4. Before capture, validate all relevant PayPal fields: - `order.status === 'APPROVED'` - Expected capture intent - Expected amount using exact decimal-string or minor-unit comparison - Expected currency - Expected payee merchant ID - Expected internal order identifier - Expected purchaser or account association where applicable 5. Reject an order ID that is already associated with another internal order or user. 6. Perform fulfillment only from a verified capture record whose status, amount, currency, merchant, and internal-order association have all been checked server-side. 7. Add tests for lower-value order substitution, currency substitution, cross-user order IDs, reused order IDs, and malformed PayPal responses. 8. Check PayPal HTTP response status and schema before using response fields. ]]>
