KnightCTF 2026 - Web 100 - Knight Shop
A client-side coupon validation vulnerability in a ReactJS medieval shop app. The fix is as simple as clearing a cookie.
Task
A modern e-commerce platform for medieval equipment. I know you'll figure it out.
Flag Format:
KCTF{Fl4g_heR3}
Enumeration
whatweb http://23.239.26.112:8087
# http://23.239.26.112:8087 [200 OK] ... Title[Knight Shop - Premium Medieval Equipment], X-Powered-By[Express]
Viewing the page source reveals a single JS import, confirming this is a ReactJS app:
<script defer="defer" src="/static/js/main.b42977dd.js"></script>
Since the JS bundle is minified, we can try fetching the source map, which is often left behind after a production build:
wget http://23.239.26.112:8087/static/js/main.b42977dd.js.map
The map file gives us all the original source files. The interesting ones are App.js and vendor.js. Opening them requires cleaning up escaped characters — in Neovim:
:%s/\\n//g
:%s/\\"/"/g
Vulnerability
Searching App.js for the string "flag" leads to the checkout handler:
const handleCheckout = async () => {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
discountCode: discountCount > 0 ? coupon : '',
discountCount: discountCount
})
});
const data = await res.json();
if (res.ok && data.flag) {
setMessage(`Purchase successful! Your flag: ${data.flag}`);
}
};
The flag is returned server-side on a successful purchase. The coupon logic lives in applyCoupon, which calls processTransaction imported from vendor.js:
export function processTransaction(input) {
const result = _0x1a8c(input);
return result;
}
function _0x1a8c(input) {
const base = [75, 78, 73, 71, 72, 84]; // ASCII: K N I G H T
const suffix = [50, 53]; // ASCII: 2 5
// validates coupon == "KNIGHT25"
// if valid, sets cookie promo_applied=1 to prevent reuse
document.cookie = 'promo_applied=1; path=/';
return { valid: true, code: input };
}
The coupon code is KNIGHT25. The reuse prevention is handled entirely client-side via a cookie — which means we can just delete it and reapply as many times as we want.
Exploitation
First, create an account and add an item to the cart:
Apply coupon KNIGHT25 — the 25% discount still isn't enough to cover the balance:
Trying to apply it again gives Invalid Coupon code because the promo_applied cookie is now set:
Clear the cookie from the browser's developer tools and reapply. Repeat until the balance is sufficient:
Proceed to checkout and the flag is returned in the response:
Flag
Flag: KCTF{kn1ght_c0up0n_m4st3r_2026}