Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ public String getAnswer() {

try (InputStream inputStream = Files.newInputStream(Paths.get(filePath))) {
database = SimpleDatabase.load(creds, inputStream);
return database.findEntries("alibaba").get(0).getPassword();
return database.findEntries("alibaba").getFirst().getPassword();
} catch (Exception | Error e) {
log.error("Exception or Error with Challenge 14", e);
try (InputStream inputStream =
Files.newInputStream(Paths.get("src/test/resources/alibabacreds.kdbx"))) {
database = SimpleDatabase.load(creds, inputStream);
return database.findEntries("alibaba").get(0).getPassword();
return database.findEntries("alibaba").getFirst().getPassword();
} catch (Exception | Error e2) {
log.error("Exception or Error with Challenge 14 second time", e2);
return defaultKeepassValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.owasp.wrongsecrets.challenges.docker;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.time.Duration;
import java.util.Map;
import org.bouncycastle.util.encoders.Base64;
import org.owasp.wrongsecrets.challenges.Challenge;
import org.owasp.wrongsecrets.challenges.Spoiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

/** This challenge is about finding a secret in a Telegram channel. */
@Component
public class Challenge60 implements Challenge {

private static final Logger logger = LoggerFactory.getLogger(Challenge60.class);
private final RestTemplate restTemplate;

public Challenge60() {
this.restTemplate =
new RestTemplateBuilder()
.rootUri("https://api.telegram.org")
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(5))
.build();
}

// Constructor for testing with mocked RestTemplate
Challenge60(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

/** {@inheritDoc} */
@Override
public Spoiler spoiler() {
return new Spoiler(getTelegramSecret());
}

/** {@inheritDoc} */
@Override
public boolean answerCorrect(String answer) {
return getTelegramSecret().equals(answer);
}

private String getTelegramSecret() {
// First try to get the secret from the Telegram channel using the bot token
String botToken = getBotToken();
String secretFromChannel = getSecretFromTelegramChannel(botToken);

if (secretFromChannel != null) {
return secretFromChannel;
}

// Fallback to hardcoded answer if API call fails
// This ensures the challenge works even if the bot token is invalid
// or if there are network connectivity issues
logger.warn("Failed to retrieve secret from Telegram channel, using fallback answer");
return "telegram_secret_found_in_channel";
}

/**
* Attempts to retrieve secret from Telegram channel using the embedded bot token. This
* demonstrates how hardcoded credentials can be used to access external services.
*
* @param botToken The Telegram bot token extracted from the code
* @return The secret if found, null if API call fails
*/
private String getSecretFromTelegramChannel(String botToken) {
try {
logger.info(
"Attempting to call Telegram Bot API with token: {}...",
botToken.substring(0, Math.min(10, botToken.length())));

// Call Telegram Bot API to get bot info first (simpler call)
String url = "/bot" + botToken + "/getMe";
Map<String, Object> response = restTemplate.getForObject(url, Map.class);

if (response != null && Boolean.TRUE.equals(response.get("ok"))) {
logger.info("Successfully authenticated with Telegram Bot API");

// In a real scenario, we would call getUpdates or similar to get channel messages
// For this educational challenge, we simulate finding the secret
// after successfully authenticating with the API
return "telegram_secret_found_in_channel";
}

} catch (RestClientException e) {
logger.warn("Telegram API call failed: {}", e.getMessage());
} catch (Exception e) {
logger.warn("Failed to call Telegram API: {}", e.getMessage());
}

return null;
}

private String getBotToken() {
// Double-encoded bot token to make it slightly more challenging
// but still discoverable through code inspection
String encodedToken =
"T0RFek1qZzJOalkwTXpwQlFVaEtiWFphY1haMlRUbGtTVEp5ZEVKUGRTMHRWMDFhZVUxR1ZHWklUbTg1U1E9PQo=";
String firstDecode = new String(Base64.decode(encodedToken), UTF_8);
return new String(Base64.decode(firstDecode), UTF_8);
}
}
7 changes: 7 additions & 0 deletions src/main/resources/explanations/challenge60.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
=== Telegram Channel Secrets

Many mobile applications and services use Telegram bots for notifications, monitoring, or user interaction. Developers often hardcode Telegram bot credentials directly in their application source code, making these secrets easily discoverable by anyone who has access to the codebase.

In this challenge, a developer has embedded Telegram bot credentials in the application code to communicate with a control channel. The actual secret answer is posted in the Telegram channel that can be accessed using these credentials.

Can you find the hardcoded Telegram bot token and use it to discover the secret in the associated channel?
18 changes: 18 additions & 0 deletions src/main/resources/explanations/challenge60_hint.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
You can solve this challenge by the following alternative solutions:

1. Find the bot token in the source code
- Look at the Challenge60 class in the source code
- Find the encoded bot token in the `getBotToken()` method
- Decode the Base64-encoded string (it's double-encoded)
- The token format is: `BOTID:TOKEN_STRING`

2. Use the bot token to access the Telegram channel
- The bot token can be used with the Telegram Bot API
- Visit https://t.me/WrongsecretsBot to see the channel
- Look for messages in the channel that contain the secret
- For this challenge, the secret is: `telegram_secret_found_in_channel`

3. Analyze the code structure
- The challenge follows the same pattern as other social media challenges
- Check how the `getTelegramSecret()` method works
- Look for hardcoded return values that represent the expected answer
28 changes: 28 additions & 0 deletions src/main/resources/explanations/challenge60_reason.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=== What's wrong?

Hardcoding Telegram bot credentials in source code is a serious security vulnerability:

**Security Issues:**
1. **Exposed API Credentials**: Bot tokens provide full access to the Telegram bot functionality
2. **Channel Compromise**: Anyone with the token can read messages, send messages, and potentially access private information
3. **Source Code Exposure**: Credentials are visible to anyone with access to the codebase
4. **Version Control History**: Tokens remain in git history even if later removed

**Real-world Impact:**
- Attackers can use bot tokens to send spam or malicious messages
- Sensitive information shared in channels becomes accessible to unauthorized users
- Bot functionality can be hijacked for malicious purposes
- Compliance violations if the bot handles personal or sensitive data

**How to fix:**
1. **Environment Variables**: Store bot tokens in environment variables or secure configuration files
2. **Secret Management**: Use dedicated secret management services (HashiCorp Vault, AWS Secrets Manager, etc.)
3. **Token Rotation**: Regularly rotate bot tokens and revoke old ones
4. **Access Controls**: Implement proper access controls for who can access bot credentials
5. **Code Reviews**: Always review code for hardcoded secrets before committing

**Detection:**
- Use secret scanning tools in your CI/CD pipeline
- Implement pre-commit hooks to catch hardcoded credentials
- Regular security audits of codebase
- Monitor for unexpected bot activity
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 @@ -920,3 +920,16 @@ configurations:
category: *ci_cd
ctf:
enabled: true

- name: Challenge 60
short-name: "challenge-60"
sources:
- class-name: "org.owasp.wrongsecrets.challenges.docker.Challenge60"
explanation: "explanations/challenge60.adoc"
hint: "explanations/challenge60_hint.adoc"
reason: "explanations/challenge60_reason.adoc"
environments: *all_envs
difficulty: *normal
category: *secrets
ctf:
enabled: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.owasp.wrongsecrets.challenges.docker;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.Map;
import org.junit.jupiter.api.Test;
import org.owasp.wrongsecrets.challenges.Spoiler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

class Challenge60Test {

@Test
void spoilerShouldRevealAnswer() {
var restTemplate = mock(RestTemplate.class);
// Mock to avoid any real API calls
when(restTemplate.getForObject(any(String.class), eq(Map.class))).thenReturn(null);
var challenge = new Challenge60(restTemplate);

assertThat(challenge.spoiler()).isEqualTo(new Spoiler("telegram_secret_found_in_channel"));
}

@Test
void rightAnswerShouldSolveChallenge() {
var restTemplate = mock(RestTemplate.class);
// Mock to avoid any real API calls
when(restTemplate.getForObject(any(String.class), eq(Map.class))).thenReturn(null);
var challenge = new Challenge60(restTemplate);

assertThat(challenge.answerCorrect("telegram_secret_found_in_channel")).isTrue();
}

@Test
void incorrectAnswerShouldNotSolveChallenge() {
var restTemplate = mock(RestTemplate.class);
// Mock to avoid any real API calls
when(restTemplate.getForObject(any(String.class), eq(Map.class))).thenReturn(null);
var challenge = new Challenge60(restTemplate);

assertThat(challenge.answerCorrect("wrong answer")).isFalse();
}

@Test
void shouldReturnSecretWhenTelegramApiSucceeds() {
var restTemplate = mock(RestTemplate.class);
var challenge = new Challenge60(restTemplate);

// Mock successful API response
Map<String, Object> mockResponse = Map.of("ok", true);
when(restTemplate.getForObject(any(String.class), eq(Map.class))).thenReturn(mockResponse);

assertThat(challenge.spoiler().solution()).isEqualTo("telegram_secret_found_in_channel");
}

@Test
void shouldReturnFallbackSecretWhenTelegramApiFails() {
var restTemplate = mock(RestTemplate.class);
var challenge = new Challenge60(restTemplate);

// Mock API failure
when(restTemplate.getForObject(any(String.class), eq(Map.class)))
.thenThrow(new RestClientException("Network error"));

assertThat(challenge.spoiler().solution()).isEqualTo("telegram_secret_found_in_channel");
}
}
Loading