Skip to content

fix: close JDBC resources in SQL injection challenges and lesson (part of #857) - #998

Open
ismisepaul wants to merge 2 commits into
devfrom
dev#857-sqli
Open

ismisepaul wants to merge 2 commits into
devfrom
dev#857-sqli

Conversation

@ismisepaul

@ismisepaul ismisepaul commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

⚠️ Manual testing required before merging

The automated tests prove these levels no longer exhaust their connection pools. They don't prove each level can still be solved the intended way, and these are deliberately vulnerable training levels.

Please run this branch, follow the steps, and comment on this PR with the result: pass, or the step that failed and what you saw.

Branch: dev#857-sqli

Set up. Use a machine where the Shepherd Docker volumes can be thrown away: down -v deletes their data. JDK 17 and Maven 3.9.2+ are needed.

git fetch origin
git checkout 'dev#857-sqli'
mvn -Pdocker clean install -DskipTests
docker compose down -v
docker compose up -d --build
  1. Open https://localhost, accept the self-signed certificate warning, log in as admin / password and set a new password when asked.
  2. On the Get Started page, tick Enable Unsafe Levels and click Open All Levels.
  3. Admin → Module Management → Change Module Layout → Open Floor, then reload the page, so every level is listed on the left.
  4. Admin → Configuration → Cheat Sheet Management → Enable Cheat Sheets for Administrators. With a level open, the Cheat button in the left-hand menu then shows its solution.

Test

  1. For each of these levels, solve it with the solution from its Cheat button and check you get the result key: SQL Injection (lesson), SQL Injection 2, SQL Injection 5, SQL Injection 6, SQL Injection 7, SQL Injection Escaping, SQL Injection Stored Procedure.
  2. In each of the same levels, submit a broken input such as a single ' five times in a row, then a normal input (e.g. test). Each broken input should get the level's usual error message, and the normal input should get a normal answer straight away.
  3. In SQL Injection 5, also try a made-up coupon code five times, then complete a purchase.

It's broken if: a level's cheat-sheet solution no longer returns the result key; or a level answers An Error Occurred! You must be getting funky!, hangs for about 5 seconds, or mentions Connection is not available after the repeated inputs, and keeps doing so until Tomcat restarts.


Part of #857. It doesn't touch the files in #932 (SqlInjection1, 3, 4, 5) or #919 (DirectObject).

What

Each challenge schema has its own connection pool of three connections (#816). These servlets gave their connection back only when the query succeeded. SqlInjectionEscaping and SqlInjectionLesson never gave it back at all, so the lesson stopped working after three searches of any kind. A few malformed injection attempts took a level offline until Tomcat restarted, which is the #931 symptom.

The Connection, Statement and ResultSet are now opened in try-with-resources in:

  • SqlInjection5CouponCheck, SqlInjection5VipCheck, SqlInjection6, SqlInjection7
  • SqlInjectionEmail, SqlInjectionEscaping, SqlInjectionStoredProcedure
  • lesson/SqlInjectionLesson

SqlInjection7 already closed its connection on every path, so there only the statement and result set change.

The levels are exactly as vulnerable as before

Only resource handling changed. These are all unchanged:

  • query strings, filtering and escaping;
  • the messages players see, including the SQL exception text some levels show;
  • the catch block each exception reaches.

executeQuery stays where it was relative to each inner try/catch, so a failed query still produces the same output. Review with git diff -w origin/dev: without whitespace, each file only loses Connection conn = … / conn.close() and gains a try (…) header and closing braces.

Tests

SqlInjectionPoolLeakIT: for each level it sends 5 malformed inputs, more than the pool's 3 connections, and checks that each one gets the level's error. Then it sends a well-formed input that must still reach the database. The lesson test also runs 5 well-formed searches.

On dev it fails 7 of 8 with the pool exhausted, for example:

Malformed email did not fail: <p>An error was detected!</p><p>java.sql.SQLTransientConnectionException: ChallengePool-… - Connection is not available, request timed out after 5010ms (total=3, active=3, idle=0, …

With this change, all 8 pass. Every step of test.yml also passed locally (lint, build, PGP check, compose build, Tomcat config check, unit tests, integration tests).

Found on dev, not fixed here

SqlInjection5CouponCheck's catch block uses a missing i18n key: #990. The new IT tolerates it.

🤖 Generated with Claude Code

…t of #857)

Each challenge schema has its own connection pool of three connections.
These servlets only closed their connection when the query succeeded, or
never closed it at all (SqlInjectionEscaping, SqlInjectionLesson), so a
few malformed injection attempts took the level offline until Tomcat was
restarted.

Open the Connection, Statement and ResultSet in try-with-resources. Only
resource handling changes: the queries, filtering, error messages and the
order in which exceptions reach each catch block are the same as before.

SqlInjectionPoolLeakIT sends five malformed submissions to each level,
then a well-formed one that must still reach the database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ismisepaul ismisepaul added this to the 4.0.0 milestone Oct 24, 2026
Copilot AI balanced review requested due to automatic review settings October 24, 2026 23:52
@ismisepaul ismisepaul moved this from Backlog to Next Release Backlog in Security Shepherd Oct 24, 2026
Comment thread src/it/java/servlets/module/challenge/SqlInjectionPoolLeakIT.java Fixed
ResultSet coupons = prepstmt.executeQuery()) {
try {
if (coupons.next()) {
htmlOutput = new String("Valid Coupon for ");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is unchanged on dev. This PR only re-indents it inside the new try-with-resources block, and these files are training content where the PR changes resource handling only. Tracked for a separate clean-up in #999.

ResultSet coupons = prepstmt.executeQuery()) {
try {
if (coupons.next()) {
htmlOutput = new String("Valid Coupon for ");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is unchanged on dev. This PR only re-indents it inside the new try-with-resources block, and these files are training content where the PR changes resource handling only. Tracked for a separate clean-up in #999.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The isolated resource-management changes preserve challenge behavior and include targeted regression coverage, with no unresolved blocking issues.

No open issues 🎉

What changed in this PR

Addresses part of #857 by closing JDBC resources in seven SQL injection challenges and one lesson, preventing challenge-pool exhaustion while preserving intentional vulnerabilities.

Changes:

  • Uses try-with-resources for connections, statements, and result sets.
  • Preserves existing queries, filtering, and query-error handling.
  • Adds integration tests covering repeated malformed requests and subsequent valid queries across all eight servlets.
File Description
src/​main/​java/​servlets/​module/​lesson/​SqlInjectionLesson.java Closes JDBC resources after successful and failed searches.
src/​main/​java/​servlets/​module/​challenge/​SqlInjectionStoredProcedure.java Ensures cleanup around stored-procedure queries.
src/​main/​java/​servlets/​module/​challenge/​SqlInjectionEscaping.java Returns connections and closes query resources.
src/​main/​java/​servlets/​module/​challenge/​SqlInjectionEmail.java Ensures cleanup after email searches fail.
src/​main/​java/​servlets/​module/​challenge/​SqlInjection7.java Adds statement and result-set cleanup; retains connection cleanup.
src/​main/​java/​servlets/​module/​challenge/​SqlInjection6.java Closes resources while preserving nested error handling.
src/​main/​java/​servlets/​module/​challenge/​SqlInjection5VipCheck.java Ensures cleanup after VIP coupon queries.
src/​main/​java/​servlets/​module/​challenge/​SqlInjection5CouponCheck.java Ensures cleanup after standard coupon queries.
src/​it/​java/​servlets/​module/​challenge/​SqlInjectionPoolLeakIT.java Adds pool-exhaustion regression coverage for all eight servlets.

Review effort: Balanced

Review follow-up: fail with a clear message instead of an array index
error when the name/value list has an odd length.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The scoped resource-lifecycle changes preserve challenge behavior, include regression coverage, and have no identified blocking issues.

No open issues 🎉

Review effort: Balanced

@ismisepaul ismisepaul added the Needs Manual Testing Must be tested by hand from the PR's branch (steps in the PR) before merging label Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Levels Needs Manual Testing Must be tested by hand from the PR's branch (steps in the PR) before merging

Projects

Status: Next Release Backlog

Development

Successfully merging this pull request may close these issues.

2 participants