Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bfe68fb
Initial plan
Copilot Jul 29, 2025
7a4d274
Implement Challenge 57: Database Connection String Exposure through E…
Copilot Jul 29, 2025
372f123
Fix formatting issues in Challenge 57 files
Copilot Jul 29, 2025
3b94656
Merge branch 'master' into copilot/fix-2122
commjoen Jul 30, 2025
c9c4725
Merge branch 'master' into copilot/fix-2122
commjoen Aug 2, 2025
cc5f015
Fix GitHub Pages workflow artifact naming conflict
Copilot Aug 2, 2025
161cec1
Implement Challenge 58 and update Challenge 57 with complete GitHub P…
Copilot Aug 2, 2025
e2449d3
Fix spotless formatting violations and Python script syntax warning
Copilot Aug 3, 2025
9cf7246
Fix MediaType constant issue in Challenge57Controller
Copilot Aug 3, 2025
7252d92
Merge branch 'master' into copilot/fix-2122
commjoen Aug 3, 2025
cc8b721
Fix workflow failures: Black formatting and SpotBugs violation
Copilot Aug 3, 2025
e4f177c
Fix HTML and adoc formatting issues and add ZAP whitelist rule
Copilot Aug 3, 2025
5b83209
Merge master and resolve conflicts - keep only Challenge 57 (Database…
Copilot Aug 22, 2025
d8e2623
Clean up remaining Challenge58 files after merge
Copilot Aug 22, 2025
7bb7d6a
Fix Challenge structure: Challenge 57 as LLM, Challenge 58 as databas…
Copilot Aug 22, 2025
11b2faa
Restore Challenge 57 LLM files from master and fix Challenge 58 forma…
Copilot Aug 23, 2025
a0b85b8
Update preview generation script to include Challenge 58 as latest ch…
Copilot Aug 23, 2025
134b5a1
Fix Challenge 58 missing files and Black formatting issues
Copilot Aug 23, 2025
7f05a3e
Merge branch 'master' into copilot/fix-2122
commjoen Aug 23, 2025
c907dad
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Aug 23, 2025
57aef45
Merge branch 'master' into copilot/fix-2122
commjoen Aug 23, 2025
e066120
Merge branch 'master' into copilot/fix-2122
commjoen Aug 23, 2025
6abff4f
undo copilot issues: have git ignore removed and add the right snippets
commjoen Aug 24, 2025
57b4655
extend explanation
commjoen Aug 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

Welcome to the OWASP WrongSecrets game! The game is packed with real life examples of how to _not_ store secrets in your software. Each of these examples is captured in a challenge, which you need to solve using various tools and techniques. Solving these challenges will help you recognize common mistakes & can help you to reflect on your own secrets management strategy.

Can you solve all the 56 challenges?
Can you solve all the 57 challenges?

Try some of them on [our Heroku demo environment](https://wrongsecrets.herokuapp.com/).

Expand Down Expand Up @@ -130,7 +130,7 @@ Not sure which setup is right for you? Here's a quick guide:
| Try it quickly online | [Container running on Heroku](https://www.wrongsecrets.com/) | Basic challenges (1-4, 8, 12-32, 34-43, 49-52, 54-55) |
| Run locally with Docker | [Basic Docker](#basic-docker-exercises) | Same as above, but on your machine |
| Learn Kubernetes secrets | [K8s/Minikube Setup](#basic-k8s-exercise) | Kubernetes challenges (1-6, 8, 12-43, 48-55) |
| Practice with cloud secrets | [Cloud Challenges](#cloud-challenges) | All challenges (1-55) |
| Practice with cloud secrets | [Cloud Challenges](#cloud-challenges) | All challenges (1-57) |
| Run a workshop/CTF | [CTF Setup](#ctf) | Customizable challenge sets |
| Contribute to the project | [Development Setup](#notes-on-development) | All challenges + development tools |

Expand Down Expand Up @@ -330,7 +330,7 @@ This is because if you run the start script again it will replace the secret in

## Cloud Challenges

_Can be used for challenges 1-55_
_Can be used for challenges 1-57_

**READ THIS**: Given that the exercises below contain IAM privilege escalation exercises,
never run this on an account which is related to your production environment or can influence your account-over-arching
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.owasp.wrongsecrets.challenges.docker;

import com.google.common.base.Strings;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import lombok.extern.slf4j.Slf4j;
import org.owasp.wrongsecrets.challenges.Challenge;
import org.owasp.wrongsecrets.challenges.Spoiler;
import org.springframework.stereotype.Component;

/** Challenge demonstrating database connection string exposure through error messages. */
@Slf4j
@Component
public class Challenge57 implements Challenge {

// Simulated database connection string with embedded credentials
private static final String DB_CONNECTION_STRING =
"jdbc:postgresql://db.example.com:5432/userdb?user=dbadmin&password=SuperSecretDB2024!&ssl=true";

private static final String EXPECTED_SECRET = "SuperSecretDB2024!";

@Override
public Spoiler spoiler() {
return new Spoiler(EXPECTED_SECRET);
}

@Override
public boolean answerCorrect(String answer) {
return !Strings.isNullOrEmpty(answer) && EXPECTED_SECRET.equals(answer.trim());
}

/**
* This method simulates what happens when an application tries to connect to a database
* but fails, exposing the full connection string (including credentials) in error messages.
* This is a common real-world mistake where developers include sensitive information
* in connection strings and don't properly handle/sanitize database connection errors.
*/
public String simulateDatabaseConnectionError() {
try {
// This will fail since we don't have a real database, but it demonstrates
// how connection errors can expose credentials
Connection conn = DriverManager.getConnection(DB_CONNECTION_STRING);
conn.close();
return "Connection successful";
} catch (SQLException e) {
// Poor error handling - exposing the full connection string in the error message
String errorMessage = "Database connection failed with connection string: " + DB_CONNECTION_STRING
+ "\nError: " + e.getMessage();

// Log the error (another way credentials get exposed)
log.error("Failed to connect to database: {}", errorMessage);

return errorMessage;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.owasp.wrongsecrets.challenges.docker;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/** REST controller for Challenge 57 to trigger database connection error. */
@Slf4j
@RestController
@RequiredArgsConstructor
public class Challenge57Controller {

private final Challenge57 challenge;

/**
* Endpoint to trigger a database connection error that exposes connection string with credentials.
* This simulates what happens when applications try to connect to unavailable databases.
*/
@GetMapping("/error-demo/database-connection")
public String triggerDatabaseError() {
log.info("Attempting database connection for Challenge 57...");
return challenge.simulateDatabaseConnectionError();
}
}
33 changes: 33 additions & 0 deletions src/main/resources/explanations/challenge57.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
=== Database Connection String Exposure in Error Messages

One of the most common and dangerous ways secrets leak in real-world applications is through database connection strings that contain embedded credentials. When applications fail to connect to databases, they often expose the full connection string (including usernames and passwords) in error messages, logs, or even user-facing interfaces.

This challenge demonstrates a scenario where a developer:

1. **Uses embedded credentials in connection strings** instead of external secret management
2. **Has poor error handling** that exposes the full connection string when database connections fail
3. **Logs sensitive information** without sanitizing credentials first
4. **Displays technical details** that could reach monitoring systems, error tracking tools, or even end users

**Common places where these exposed connection strings appear:**

- Application startup logs when database is unavailable
- Exception stack traces in monitoring tools like Sentry, Rollbar, or CloudWatch
- Error messages displayed to users during maintenance windows
- CI/CD pipeline logs when deployment health checks fail
- Docker container logs during orchestration failures

**Real-world examples:**

- Applications that fail health checks during Kubernetes deployments
- Microservices that can't reach their database during startup
- Database migration scripts that fail with exposed connection details
- Development/testing environments where error verbosity is set too high

**How to trigger the error:**

Visit the `/error-demo/database-connection` endpoint to simulate a database connection failure. This endpoint attempts to connect to a database using a connection string with embedded credentials, and when it fails, it exposes the credentials in both the HTTP response and application logs.

Can you find the database password that gets exposed when the application tries to connect to the database?

**Hint:** Look for database connection error messages that reveal more than they should.
1 change: 1 addition & 0 deletions src/main/resources/explanations/challenge57_hint.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Try visiting the `/error-demo/database-connection` endpoint to trigger a database connection error. Look at both the HTTP response and the application logs - database connection failures often expose connection strings with embedded credentials in error messages.
43 changes: 43 additions & 0 deletions src/main/resources/explanations/challenge57_reason.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
=== What went wrong?

The application demonstrates several critical security mistakes commonly found in real-world applications:

**1. Embedded Credentials in Connection Strings**
The database connection string contains the username and password directly embedded:
```
jdbc:postgresql://db.example.com:5432/userdb?user=dbadmin&password=SuperSecretDB2024!&ssl=true
```

**2. Poor Error Handling**
When the database connection fails, the application exposes the entire connection string in the error message, revealing the embedded credentials to anyone who can see the error.

**3. Sensitive Information in Logs**
The error gets logged with the full connection string, meaning the credentials are now stored in log files that may be:
- Accessible to multiple team members
- Shipped to centralized logging systems
- Stored long-term in log archives
- Exposed through log monitoring tools

**4. No Secret Sanitization**
The application doesn't sanitize sensitive information before logging or displaying errors.

**How to fix this:**

1. **Use external secret management:** Store database credentials in environment variables, secret management systems (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault), or configuration files not checked into version control.

2. **Separate connection parameters:** Keep the connection URL separate from credentials:
```
DB_HOST=db.example.com
DB_PORT=5432
DB_NAME=userdb
DB_USER=dbadmin
DB_PASS=SuperSecretDB2024!
```

3. **Implement proper error handling:** Sanitize error messages before logging or displaying them. Never include connection strings or other sensitive data in error messages.

4. **Use connection pooling with secure configuration:** Modern frameworks like Spring Boot support secure configuration of connection pools without exposing credentials.

5. **Monitor and audit:** Regularly review logs and error messages to ensure sensitive information isn't being inadvertently exposed.

This type of credential exposure is extremely common in production applications and represents one of the most frequent ways database credentials are accidentally leaked.
13 changes: 13 additions & 0 deletions src/main/resources/wrong-secrets-configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -879,3 +879,16 @@ configurations:
category: *ai
ctf:
enabled: true

- name: Challenge 57
short-name: "challenge-57"
sources:
- class-name: "org.owasp.wrongsecrets.challenges.docker.Challenge57"
explanation: "explanations/challenge57.adoc"
hint: "explanations/challenge57_hint.adoc"
reason: "explanations/challenge57_reason.adoc"
environments: *all_envs
difficulty: *normal
category: *logging
ctf:
enabled: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.owasp.wrongsecrets.challenges.docker;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class Challenge57ControllerTest {

@Mock private Challenge57 challenge;

@InjectMocks private Challenge57Controller controller;

@Test
void triggerDatabaseErrorShouldReturnErrorMessage() {
// Given
String expectedError = "Database connection failed with connection string: ...";
org.mockito.Mockito.when(challenge.simulateDatabaseConnectionError()).thenReturn(expectedError);

// When
String result = controller.triggerDatabaseError();

// Then
assertThat(result).isEqualTo(expectedError);
org.mockito.Mockito.verify(challenge).simulateDatabaseConnectionError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.owasp.wrongsecrets.challenges.docker;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.owasp.wrongsecrets.challenges.Spoiler;

class Challenge57Test {

@Test
void spoilerShouldReturnCorrectAnswer() {
var challenge = new Challenge57();
assertThat(challenge.spoiler()).isEqualTo(new Spoiler("SuperSecretDB2024!"));
}

@Test
void answerCorrectShouldReturnTrueForCorrectAnswer() {
var challenge = new Challenge57();
assertThat(challenge.answerCorrect("SuperSecretDB2024!")).isTrue();
}

@Test
void answerCorrectShouldReturnFalseForIncorrectAnswer() {
var challenge = new Challenge57();
assertThat(challenge.answerCorrect("wronganswer")).isFalse();
assertThat(challenge.answerCorrect("")).isFalse();
assertThat(challenge.answerCorrect(null)).isFalse();
}

@Test
void answerCorrectShouldTrimWhitespace() {
var challenge = new Challenge57();
assertThat(challenge.answerCorrect(" SuperSecretDB2024! ")).isTrue();
}

@Test
void simulateDatabaseConnectionErrorShouldExposeConnectionString() {
var challenge = new Challenge57();
String errorMessage = challenge.simulateDatabaseConnectionError();

// Verify that the error message contains the exposed connection string
assertThat(errorMessage).contains("jdbc:postgresql://db.example.com:5432/userdb");
assertThat(errorMessage).contains("user=dbadmin");
assertThat(errorMessage).contains("password=SuperSecretDB2024!");
assertThat(errorMessage).contains("Database connection failed");
}
}
Loading