API Version Rollback: When an Old Version Serves Unredacted Data
The challenge
The order API is versioned by date through the X-Api-Version header, and the gateway still accepts four of them: 2024-06-01, 2024-11-15, 2025-03-20 and 2025-09-10. Card redaction was added at some point in that history, but not every accepted version got it. Send the same request once per version and compare the responses side by side. One of them returns the customer's full card number and billing address instead of the masked summary. It is not the oldest one, so you cannot guess it. Submit the version string that leaks.
What you'll learn
- Enumerate a small, known set of API versions rather than guessing one
- Compare responses field by field to spot a difference in redaction
- Understand version rollback as an access-control and data-exposure bug
- See why a sunset version and a deprecated version are not the same risk
- Explain why a security fix must be applied to every accepted version
Skills tested
Prerequisites
- Reading a raw HTTP request and response
- Basic understanding of API versioning
How it works
Date-based API versioning lets clients pin behaviour, and it is a good pattern. The risk appears when a security fix ships in one version and the older versions stay switched on. The new version is not the attack surface. Everything you still accept is.
Here the gateway accepts four versions. The request is otherwise identical every time, so any difference in the response is caused purely by the version header. Working through them: 2025-09-10 and 2025-03-20 return a masked payment summary; 2024-06-01 is sunset and returns 410 Gone; and 2024-11-15 returns 200 with the complete card number, expiry, cardholder name, phone number and billing address.
The leak sits in the middle of the timeline, which is the point of the exercise. A guess based on intuition lands on the oldest version, and the oldest version is the safe one because it was actually turned off. Only sending all four and comparing the payment object finds the real answer. Redaction was introduced in the 2025-03-20 revision and never backported to 2024-11-15, which stayed accepted for compatibility.
Common mistakes
- Guessing 2024-06-01 because it is oldest. It was sunset and answers 410 Gone. Deprecated and disabled are different states, and only one of them still serves data.
- Testing one version and stopping. The current version behaves correctly, which proves nothing about the others.
- Editing more than the version header. Changing the Host breaks routing and returns a 404, and changing the order id changes the record rather than the redaction behaviour.
- Reading only the status line. Three versions answer 200; the difference is inside the payment object, between
last4andpan. - Submitting the card number. The question asks for the version string that leaks, not the data it leaked.
How to defend against it
Treat the set of accepted versions as part of your attack surface and keep it as small as you can defend.
- Apply security fixes such as redaction in a shared serialisation layer that every version routes through, rather than per version handler.
- Keep an explicit inventory of accepted versions with a sunset date for each, and enforce the sunset automatically.
- Test old versions in CI. Run the sensitive-data assertions against every accepted version, not just the current one.
- Reject unknown and expired versions outright instead of silently falling back to a legacy handler.
- Alert on traffic to deprecated versions; a sudden surge in requests pinned to an old version is a strong exploitation signal.
- Never return a full PAN from an order endpoint. Store and serve a token, so the worst case in a handler bug is still not cardholder data.