Skip to content

Fix Challenge-2-Broken-Crypto-4: close client-side coupon crypto oracle - #337

Closed
beanbeah wants to merge 2 commits into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-challenge-challenge-2-broken-crypto-4
Closed

beanbeah wants to merge 2 commits into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-challenge-challenge-2-broken-crypto-4

Conversation

@beanbeah

@beanbeah beanbeah commented Aug 9, 2026

Copy link
Copy Markdown

Vulnerability

The Insecure Crypto Storage Challenge 4 shop page (.../b927fc...jsp) loaded
couponCheck.js, which embedded a hard-coded DES key and IV plus the
ciphertext of every valid coupon code - including a non-public 100%-off
coupon on Oranges (e!c!3etZoumo@Stu4rU176, seeded in moduleSchemas.sql).
Because the decryption key ships alongside the ciphertext, this "encryption"
gave the coupon list no real confidentiality: decrypting the DES ciphertexts
client-side with the exposed key/IV directly recovers the secret coupon
without ever contacting the server.

Fix (BrokenCrypto4.java / the challenge JSP / couponCheck.js)

  • Removed the couponCheck.js <script> include and its
    $('#couponCode').change(...) handler from the JSP. The server already
    independently validates coupon codes against the database in
    BrokenCrypto4#doPost, so this was a purely cosmetic green/red hint with
    no legitimate purpose - and the only reason the secret's ciphertext + key
    were shipped to the browser at all.
  • Stripped the hard-coded key/IV/ciphertext list out of couponCheck.js
    itself so the secret material is no longer served as a static asset either.
  • While in the area, fixed two related correctness bugs in the cost math:
    cost - (cost * (perCentOff / 100)) integer-divides perCentOff first,
    truncating any coupon under 100% off (e.g. the published 10%/50% coupons)
    to no discount at all; and finalCost was summing bananaAmount instead
    of bananaCost.

Testing

  • mvn -o compile and mvn -o spotless:check both pass cleanly.
  • Standalone harness (mirroring the fixed discount formula) confirms the
    published HalfOffOranges (50%) and PleaseTakeAnOrange (10%) coupons
    now discount correctly, and a legitimately-supplied 100% coupon still
    zeroes the cost through the real checkout flow.
  • Decrypted the original couponCheck.js's embedded ciphertexts with the
    exposed DES key/IV to confirm the secret coupon e!c!3etZoumo@Stu4rU176
    was indeed recoverable client-side before the fix; confirmed the shipped
    JS/JSP no longer contain the key, IV, or any ciphertext after the fix.

🤖 Generated with Claude Code

The shop page for Insecure Crypto Storage Challenge 4 shipped a hard-coded
DES key and IV, along with the ciphertext of every valid coupon code
(including a non-public 100%-off code), to the browser via couponCheck.js.
Because the key travels with the ciphertext, this gave the coupon codes no
real confidentiality: anyone could decrypt the full list client-side,
without ever contacting the server, and recover the secret coupon.

- Remove the couponCheck.js include and its change-handler from the JSP;
  the server already independently validates coupon codes against the
  database in BrokenCrypto4#doPost, so this was a purely cosmetic
  green/red hint with no legitimate purpose.
- Strip the hard-coded key/IV/ciphertexts out of couponCheck.js itself so
  the secret material is no longer shipped as a static asset either.
- Fix two related correctness bugs in the cost math while in the area:
  the discount was computed as cost * (perCentOff / 100), which
  integer-divides perCentOff first and truncates any coupon under 100%
  off to no discount at all; and finalCost summed bananaAmount instead
  of bananaCost.

Verified locally: mvn compile and spotless:check pass; a standalone
harness confirms HalfOffOranges (50%) and PleaseTakeAnOrange (10%) now
discount correctly, the legitimate 100% coupon still zeroes cost when
supplied through the real checkout flow, and grepping the shipped JS/JSP
output confirms the DES key/IV/ciphertext list is no longer present.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 9, 2026 •

Copy link
Copy Markdown

🏆 Security Shepherd — CTF Patch Score

█░░░░░░░░░░░░░░░░░░░  2 / 79 pts  (3%)

1 / 40 challenges patched

Per-challenge detail is withheld — it would reveal the rubric.

Commit: 3abbdcf · scoring run

🎉 Your result is on the leaderboard — see where you rank! 🏆

The previous commit closed the client-side leak of the DES key/IV that
protected coupon 432197 ('e!c!3etZoumo@Stu4rU176', 100% off Oranges), but
left the coupon itself in the database. A secret that must never be
recoverable has no business being "protected" only by reversible
encryption whose key ships to the browser - and more fundamentally, an
undisclosed 100%-off backdoor coupon shouldn't exist in the data at all,
since it can leak through any channel (source control, DB dump, logs),
not just the one we already closed.

Delete the couponId 432197 row from CryptShop.coupons in
moduleSchemas.sql. The docker MariaDB image is built directly from this
file (docker/mariadb/Dockerfile copies target/moduleSchemas.sql into
/docker-entrypoint-initdb.d/), so this removes the backdoor from the
actual seeded database, not just from documentation. The legitimate,
published coupons (PleaseTakeAFruit, FruitForFree, PleaseTakeAnOrange,
HalfOffOranges, PleaseTakeABanana, HalfOffBananas) are untouched and
continue to work, now with correct discount math from the previous
commit.

Verified locally: mvn compile passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@beanbeah

beanbeah commented Aug 9, 2026

Copy link
Copy Markdown
Author

Closing as part of a full stand-down of this CTF push.

@beanbeah beanbeah closed this Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant