Skip to content

Fix crypto storage L9-L10, error-based SQLi L1-4, open redirect L1-4 - #207

Closed
beanbeah wants to merge 2 commits into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-batch-04
Closed

beanbeah wants to merge 2 commits into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-batch-04

Conversation

@beanbeah

@beanbeah beanbeah commented Aug 9, 2026 •

Copy link
Copy Markdown

Fixes 10 challenges:

  • Challenge-31/32 Cryptographic Failures LEVEL_9/LEVEL_10 (A04): LEVEL_9 now uses a per-entry random salt (stored as salt:hash) verified via the existing PasswordHashingUtils.isValidSaltedSha256, closing the unsalted/rainbow-table weakness. LEVEL_10 replaces reversible AES-128 encryption keyed by the password itself with a one-way bcrypt digest (same pattern as the existing secure LEVEL_11), so the stored value can never be decrypted even if guessed.
  • Challenge-33..36 ErrorBasedSQLInjectionVulnerability LEVEL_1-4 (A05): the id query parameter is now bound via PreparedStatement placeholders instead of string concatenation (LEVEL_4 previously built a PreparedStatement object but still concatenated the value into the SQL text itself, so it was not actually parameterized).
  • Challenge-37..40 Http3xxStatusCodeBasedInjection LEVEL_1-4 (A01 open redirect): updated in a follow-up commit. The original fix replaced the ad hoc prefix blacklist with a same-origin URI-parsing check, but that check still had a bypass: it only rejected a literal // or \ prefix, so a value like /\evil.com slipped through (browsers normalize an embedded backslash to a second slash, turning this into the protocol-relative //evil.com). Levels 1-4 now use a fixed allowlist instead (WHITELISTED_URLS::contains, i.e. exactly / or /VulnerableApp/) — the same pattern already used by this class's own pre-existing secure Level 8/11 implementations, and the only value the legitimate challenge UI ever sends (confirmed in the LEVEL_1 JS template).

Updated the corresponding JUnit tests that previously asserted the vulnerable behavior as passing cases.

Local verification (WSL, Java 17, gradle):

  • compileJava, compileTestJava, and test (targeted at the two modified test classes) all pass: 33/33 non-skipped in Http3xxStatusCodeBasedInjectionTest, 5/5 in ErrorBasedSQLInjectionVulnerabilityTest.
  • Booted the app via bootRun and manually curl-verified:
    • SQLi: id=1 still returns the Audi record on all 4 levels; id=x' OR '1'='1 now fails with a data-conversion error instead of returning data.
    • Open redirect: returnTo=/VulnerableApp/ still 302-redirects on all 4 levels; returnTo=https://evil.com, ftp://ftp.dlptest.com/, a bare evil.com, and a %00-prefixed payload all now return 200 with no Location header.
    • Crypto: LEVEL_9/LEVEL_10 challenge and incorrect-guess responses execute without error against the new salted/bcrypt storage; the correct seeded LEVEL_10 password (aa123456) is confirmed accepted; the underlying isValidSaltedSha256/isValidBcrypt round-trip is covered by existing passing unit tests (PasswordHashingUtilsTest).

🤖 Generated with Claude Code

…L1-4 (parameterized queries), open redirect L1-4 (same-origin allowlist)

- CryptographicFailures LEVEL_9: switch from unsaltedSha256Hex to a per-entry
  random salt stored as "salt:hash", verified via the existing
  PasswordHashingUtils.isValidSaltedSha256 helper, closing the rainbow-table/
  identical-hash weakness (CWE-759/CWE-326).
- CryptographicFailures LEVEL_10: replace reversible AES-128 encryption keyed
  by the password itself with a one-way bcrypt digest (same pattern as the
  existing secure LEVEL_11), so the stored value can never be decrypted even
  if guessed (CWE-326).
- ErrorBasedSQLInjectionVulnerability LEVEL_1-4: bind the id query parameter
  via PreparedStatement placeholders instead of string concatenation (LEVEL_4
  previously built a PreparedStatement but still concatenated the value into
  the SQL text itself, so it was not actually parameterized).
- Http3xxStatusCodeBasedInjection LEVEL_1-4: replace the per-level ad hoc
  prefix blacklist (http/https/www/'//'/NUL) with a single same-origin
  allowlist check that also rejects any embedded control character (tab, CR,
  LF, NUL), closing the ftp:// scheme bypass and the browser URL-
  normalization bypass while still allowing same-application relative paths
  and same-origin absolute URLs through.
- Updated the corresponding JUnit tests that previously asserted the
  vulnerable behavior as passing test cases.

Verified locally: gradle compileJava/compileTestJava/test all pass (40/40 +
1 skipped pre-existing test in Http3xxStatusCodeBasedInjectionTest, 5/5 in
ErrorBasedSQLInjectionVulnerabilityTest). Booted the app via bootRun and
manually confirmed each of the 10 targeted exploits (SQLi OR-injection on
L1-4, ftp/protocol-relative/bare-domain/absolute-external open redirect on
L1-4, and the crypto storage endpoints) now fails while the legitimate
lookup/redirect/challenge-response behavior still works.

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

github-actions Bot commented Aug 9, 2026 •

Copy link
Copy Markdown

🏆 VulnerableApp — CTF Patch Score

█░░░░░░░░░░░░░░░░░░░  5 / 187 pts  (3%)

4 / 110 challenges patched

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

Commit: 670e682 · scoring run

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

…llowlist (L1-4)

Levels 1-4 previously validated the returnTo target with a hand-rolled
same-origin URI check (scheme+authority match, or a leading single
slash). That check still let a value like "/\evil.com" through
because it only rejected a literal "//" or "\" prefix, not a
backslash appearing after a leading slash - a shape browsers normalize
to a protocol-relative "//evil.com" redirect.

Replaced it with the fixed allowlist already used by this same class's
secure Level 8/11 implementations (WHITELISTED_URLS::contains, i.e.
exactly "/" or "/VulnerableApp/"). This is also the only value the
legitimate challenge UI ever sends (confirmed in the LEVEL_1 JS
template), so no legitimate use case is affected.

Updated Http3xxStatusCodeBasedInjectionTest to match: dropped the
RequestEntity-based origin-matching tests (no longer relevant) and
added direct allowlist tests per level (whitelisted path still
redirects; external, ftp scheme, bare-domain, and null-byte payloads
are all rejected).
@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