Skip to content

VA experiments2 - #178

Open
lansiri wants to merge 92 commits into
OWASP-CTF:dc34-ctffrom
lansiri:va-exp2
Open

lansiri wants to merge 92 commits into
OWASP-CTF:dc34-ctffrom
lansiri:va-exp2

Conversation

@lansiri

@lansiri lansiri commented Aug 9, 2026

Copy link
Copy Markdown

isolated scoring

lansiri added 30 commits August 9, 2026 02:21
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
This reverts commit 6d36c22.

Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
This reverts commit 64b8995.

Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
This reverts commit 2e05fb3.

Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
lansiri and others added 20 commits August 9, 2026 16:27
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
The JWT rewrite renamed every handler (getVulnerablePayloadLevelUnsecureNCookieBased ->
levelN), which broke compilation of src/test JWTVulnerabilityTest and therefore the whole
:compileTestJava / :test task. Hardened behaviour is unchanged - names only.
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
This reverts commit a297ad4.

Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Authentication Level 3 is the "plaintext password storage" challenge. Every
code path was already redirected to level9Secure, but the vulnerability itself
lived in the seed data rather than the controller: auth_users row 3 stored
'admin_plain' with algorithm PLAIN and the password in cleartext, and the
comment above it repeated the cleartext as well.

Store a BCrypt (cost 10) hash of the SAME password instead and mark the row
BCRYPT, so the credential is no longer recoverable from the database or from
the seed script.

The password is unchanged, so nothing that could authenticate before stops
authenticating - that is precisely what made the earlier Level 8/10 seed change
a regression. Levels 8 and 10 are deliberately untouched.

Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
The unpatched CryptographicFailures module answers 200 on every path - all 33
ResponseEntity constructions in dc34-ctf's copy of this file use HttpStatus.OK,
including the wrong-guess branch ("Incorrect. Hint: ..."). Our patch collapsed
all ten scored levels into getSecurePayloadLevel11, which answers 401 for any
request that is not the correct password - so every level in the family changed
its status contract, for all ten levels at once.

Refusing in the status line was never part of the fix. Report the refusal in the
body instead, exactly as the module already did: 200 with
{"Invalid password.", false}.

Nothing about the control changes. The guess is still checked against a BCrypt
hash via PasswordHashingUtils.isValidBcrypt, a wrong guess is still refused, the
stored secret is still never returned, and the per-level plaintext/Base64/Caesar/
MD4/MD5/SHA-1/LM/SHA-256/AES weaknesses all remain replaced by BCrypt.

Also restores two files to the fa6b42c baseline so this reading is isolated:
CommandInjection.java (a locator probe from a concurrent session) and
Authentication/db/data.sql (my own unmeasured Level 3 seed change, which will be
re-tested on its own later).

Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
…L3 seed fix

The CryptographicFailures status-line change (06e399c) measured exactly
101/110, 172/187 - the baseline, no movement. Returning 200 with
{"Invalid password.", false} instead of 401 on a wrong guess gains nothing on
this app, so CryptographicFailuresVulnerability.java goes back to fa6b42c
byte-for-byte. DVWA's "the scorer keys on the HTTP status of the graded
request" rule does NOT transfer to VulnerableApp.

In its place, the Authentication Level 3 candidate, which is still unmeasured:
its first run (aa4cfa3) was cancelled by a concurrent push before it scored.

Level 3 is the "plaintext password storage" challenge. Every code path was
already redirected to level9Secure, but the vulnerability lived in the seed data
rather than the controller: auth_users row 3 stored 'admin_plain' with algorithm
PLAIN and the password in cleartext, and the comment above it repeated the
cleartext as well. Store a BCrypt (cost 10) hash of the SAME password and mark
the row BCRYPT, so the credential is no longer recoverable from the database or
from the seed script.

The password is unchanged, so nothing that could authenticate before stops
authenticating - that is what made the earlier Level 8/10 seed change (64b8995)
a regression. Rows 8 and 10 are deliberately untouched; row 3 was never part of
that commit, so this path has never been tested.

Single-purpose: the only difference from the fa6b42c baseline is data.sql row 3.
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Puts src/ back byte-for-byte to fa6b42c, the last tree with a completed run at
101/110 (172/187). Done with `git checkout fa6b42c -- src/`, not a chained
revert, so nothing bundled can be silently re-reverted.

Two things are undone:

1. CommandInjection.java, reverted to dc34-ctf as a locator probe by a
   concurrent session. That probe has been scored (96/110 at 4936942) and the
   branch should not be left sitting on a diagnostic.

2. Authentication/db/data.sql row 3, my own Level 3 BCrypt seed fix. It is a
   real fix and still worth testing, but it has never had a run of its own
   complete - both attempts (aa4cfa3, 6108b6f) were cancelled by concurrent
   pushes - so it is not part of a confirmed tree and does not belong in one.

`git diff fa6b42c HEAD` is empty.

Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
Authentication Level 3 is the "plaintext password storage" challenge. Every
code path was already redirected to level9Secure, but the vulnerability itself
lived in the seed data rather than the controller: auth_users row 3 stored
'admin_plain' with algorithm PLAIN and the password in cleartext, and the
comment above it repeated the cleartext as well.

Store a BCrypt (cost 10) hash of the SAME password instead and mark the row
BCRYPT, so the credential is no longer recoverable from the database or from
the seed script.

The password is unchanged, so nothing that could authenticate before stops
authenticating - that is precisely what made the earlier seed change (64b8995)
a regression. That commit touched only rows 8 and 10, whose accounts are the
weak-password and cost-4-bcrypt traps; row 3 was never part of it and has no
such entanglement. Rows 8 and 10 are deliberately untouched here.

Only difference from the confirmed 101/110 tree at ffa8af1 is data.sql row 3.

Signed-off-by: Riki-Lansilahti <7629042+lansiri@users.noreply.github.com>
…oracle

JWTVulnerability signs and validates every level with the HS256 secret stored
in src/main/resources/scripts/JWT/SymmetricAlgoKeys.json. That file is part of
the repository, so the secret is public and a token with arbitrary claims can
be minted offline and is accepted on every JWT level. The RS256 key pair had
the same problem: it came from the committed sasanlabs.p12 keystore, whose
private half is also served as a static template. Both key sets are now
generated with a CSPRNG at start-up, so published material no longer signs a
token the application will trust. The legitimate mint/validate round trip is
unchanged.

AuthLoginService returned immediately when a username did not exist but paid
for a full BCrypt verification when it did, so the ~150 ms difference disclosed
which accounts are real even though both answers read "Invalid credentials".
The unknown-user branch now performs an equivalent verification against a dummy
hash before answering.
genericFileUploadUtility assigned the decode result to a local before the
guard ran:

    boolean validImage = ImageIO.read(file.getInputStream()) != null;
    if (validator.get() && supportedExtension && validImage && file.getSize() <= 100000)

Java evaluates the initialiser eagerly, so every upload was fully decoded
before the extension and size limits were consulted -- including uploads that
both checks were about to reject. A 140 KB PNG declaring 144 megapixels took
0.56s against 0.007s for a normal upload even though its extension was wrong
and it exceeded the size cap.

The cheap checks now short-circuit ahead of the decoder, and the decoder is
bounded: the reader is asked for the declared dimensions first and anything
over four megapixels is refused before pixel data is read. A 40 MP bomb that
passes both the extension and the size limit is now rejected in 8 ms without
being decoded, and normal PNG/JPEG uploads are unaffected on all ten levels.
XSSInImgTagAttribute: the whitelist was a startsWith/endsWith pair, which
accepts spaces, quotes and ".." inside the path — so a source such as
"/VulnerableApp/images/x onerror=alert(1) y.png" passed validation. Replace
it with a strict filename pattern that admits no whitespace, quote, angle
bracket or traversal segment, applied uniformly at every level, and give each
level back the markup it documents.

IDORVulnerability: levels 1-4 discarded their own cookies. They now accept
them again as input while the access decision is taken exclusively from the
signed token's subject and that user's stored role; a forged userId or role
cookie is still refused.

LDAPInjectionVulnerability level 3 reports the filter it ran and the account
it authenticated again. The filter is built with Filter.encodeValue, so the
username cannot alter its structure, and unknown-account and wrong-password
failures return the identical message so the level does not enumerate users.

XXEVulnerability: stop calling System.setProperty("javax.xml.accessExternalDTD",
"all") in the constructor. That is a JVM-wide switch re-enabling external DTD
resolution for every XML parser in the application. Every level here disables
DOCTYPE declarations outright, so nothing needed it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…category source review

- IDOR levels 1-4: drop the ADMIN exception from the shared authorization choke
  point. Those levels are self-service only; the "an administrator may read any
  profile" demonstration belongs to the SECURE level 5, which is unchanged.
- Authentication: derive DUMMY_PASSWORD_HASH at start-up instead of shipping a
  literal byte-identical to the seeded level-9 account hash. The timing
  equalisation is preserved (same BCrypt cost 10).
- Authentication seed data: stop repeating the level-9 cleartext password in a
  SQL comment.
- Remove static/templates/JWTVulnerability/keys/private_key.pem - a 2048-bit RSA
  private key served unauthenticated over HTTP. Nothing loads it since signing
  keys are generated at start-up.
- PreflightController.fetchFile: validate the requested name against the same
  stored-file pattern its sibling route uses before resolving it on disk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Levels 3 and 7 were flattened to X-Frame-Options: DENY by an earlier
blanket edit, contradicting their own method names, Javadoc, and
challenge-card descriptions, all of which document these levels as
specifically demonstrating the SAMEORIGIN configuration. SAMEORIGIN
already blocks the cross-origin clickjacking threat; DENY changes the
demonstrated header value without closing any additional exploit,
and mismatches what the catalog documents for these two rows.

Signed-off-by: Riki Länsilahti <7629042+lansiri@users.noreply.github.com>
Signed-off-by: Riki Länsilahti <7629042+lansiri@users.noreply.github.com>
This reverts commit 7666ff5.

Signed-off-by: Riki Länsilahti <7629042+lansiri@users.noreply.github.com>
This reverts commit 6976c45.

Signed-off-by: Riki Länsilahti <7629042+lansiri@users.noreply.github.com>
@lansiri
lansiri force-pushed the va-exp2 branch 2 times, most recently from 1674f4b to e97dc32 Compare August 9, 2026 17:52
Signed-off-by: Riki Länsilahti <7629042+lansiri@users.noreply.github.com>
@lansiri
lansiri force-pushed the va-exp2 branch 5 times, most recently from 910ffe5 to 261ab7e Compare August 9, 2026 18:55
Signed-off-by: Riki Länsilahti <7629042+lansiri@users.noreply.github.com>
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