diff --git a/.github/scripts/.bash_history b/.github/scripts/.bash_history
index f9e4e5963..ceba5fd33 100644
--- a/.github/scripts/.bash_history
+++ b/.github/scripts/.bash_history
@@ -347,7 +347,7 @@ rm -rf jdk-18_linux-x64_bin.deb
git rebase -i main
git rebase -i master
git stash
-export tempPassword="mVskm4vj9tBf4BqqQEyPaFtTAFJ+K9csVbQkwF3Kj04="
+export tempPassword="8S2PzZ7da3Jx9geda6JOqqfYlSDYzM7QbpUGyxM9umw="
mvn run tempPassword
k6
npx k6
diff --git a/.github/scripts/docker-create.sh b/.github/scripts/docker-create.sh
index 981b956dd..3ea416ec9 100755
--- a/.github/scripts/docker-create.sh
+++ b/.github/scripts/docker-create.sh
@@ -64,8 +64,11 @@ Heroku_publish_demo() {
heroku container:login
echo "heroku deployment to demo"
cd ../..
- heroku container:push web --arg argBasedVersion=${tag} --app arcane-scrubland-42646
- heroku container:release web --app arcane-scrubland-42646
+ git add Dockerfile.web
+ git commit --no-verify -m "Fix Heroku deploy"
+ git push heroku HEAD:master
+ # heroku container:push web --arg argBasedVersion=${tag} --app arcane-scrubland-42646
+ # heroku container:release web --app arcane-scrubland-42646
# heroku container:push --recursive --arg argBasedVersion=${tag}heroku,CTF_ENABLED=true,HINTS_ENABLED=false --app wrongsecrets-ctf
# heroku container:release web --app wrongsecrets-ctf
echo "wait for contianer to come up"
diff --git a/Dockerfile b/Dockerfile
index da5db174e..2f65c0f14 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
FROM bellsoft/liberica-openjre-debian:25-cds AS builder
WORKDIR /builder
-ARG argBasedVersion="1.13.1-alpha6"
+ARG argBasedVersion="1.13.1-alpha11"
COPY --chown=wrongsecrets target/wrongsecrets-${argBasedVersion}-SNAPSHOT.jar application.jar
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
diff --git a/Dockerfile.web b/Dockerfile.web
index 1c17230c9..d500fa204 100644
--- a/Dockerfile.web
+++ b/Dockerfile.web
@@ -1,5 +1,5 @@
-FROM jeroenwillemsen/wrongsecrets:1.13.1-alpha6-no-vault
-ARG argBasedVersion="1.13.1-alpha6-no-vault"
+FROM jeroenwillemsen/wrongsecrets:1.13.1-alpha11-no-vault
+ARG argBasedVersion="1.13.1-alpha11-no-vault"
ARG spring_profile="without-vault"
ARG CANARY_URLS="http://canarytokens.com/terms/about/s7cfbdakys13246ewd8ivuvku/post.jsp,http://canarytokens.com/terms/about/y0all60b627gzp19ahqh7rl6j/post.jsp"
ARG CTF_ENABLED=false
@@ -39,9 +39,12 @@ ENV default_aws_value_challenge_11=$CHALLENGE_11_VALUE
ENV BASTIONHOSTPATH="/home/wrongsecrets/.ssh"
ENV PROJECTSPECPATH="/var/helpers/project-specification.mdc"
ENV funnybunny="This is a funny bunny"
+# Keep memory usage within Heroku dyno limits (512MB dyno).
+# Hard cap heap to 250M, metaspace to 60M, disable expensive GC, exit on OOM immediately.
+ENV JAVA_TOOL_OPTIONS="-Xmx250M -Xms128M -XX:MetaspaceSize=40M -XX:MaxMetaspaceSize=60M -XX:CompressedClassSpaceSize=32M -XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof"
# Deploy WrongSecrets to Heroku
COPY .github/scripts/ /var/helpers
COPY src/test/resources/alibabacreds.kdbx /var/helpers
COPY src/test/resources/RSAprivatekey.pem /var/helpers
COPY .ssh/ /home/wrongsecrets/.ssh/
-CMD ["/bin/sh", "-c", "java -jar -XX:SharedArchiveFile=application.jsa -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} -Dserver.port=${PORT} -Dspringdoc.swagger-ui.enabled=${SPRINGDOC_UI} -Dspringdoc.api-docs.enabled=${SPRINGDOC_DOC} application.jar"]
+CMD ["/bin/sh", "-c", "java ${JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} -Dserver.port=${PORT} -Dspringdoc.swagger-ui.enabled=${SPRINGDOC_UI} -Dspringdoc.api-docs.enabled=${SPRINGDOC_DOC} -jar application.jar"]
diff --git a/Procfile b/Procfile
new file mode 100644
index 000000000..b8a0b667c
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: java -Xmx200M -Xms100M -XX:MetaspaceSize=30M -XX:MaxMetaspaceSize=50M -XX:CompressedClassSpaceSize=24M -XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:+ExitOnOutOfMemoryError -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} -Dserver.port=${PORT} -Dspringdoc.swagger-ui.enabled=${SPRINGDOC_UI} -Dspringdoc.api-docs.enabled=${SPRINGDOC_DOC} -jar target/application.jar
diff --git a/aws/k8s/secret-challenge-vault-deployment.yml b/aws/k8s/secret-challenge-vault-deployment.yml
index aba2e3a72..810a174a2 100644
--- a/aws/k8s/secret-challenge-vault-deployment.yml
+++ b/aws/k8s/secret-challenge-vault-deployment.yml
@@ -58,7 +58,7 @@ spec:
volumeAttributes:
secretProviderClass: "wrongsecrets-aws-secretsmanager"
containers:
- - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha6-k8s-vault
+ - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha11-k8s-vault
imagePullPolicy: IfNotPresent
name: secret-challenge
command: ["/bin/sh"]
diff --git a/azure/k8s/secret-challenge-vault-deployment.yml.tpl b/azure/k8s/secret-challenge-vault-deployment.yml.tpl
index 20801f416..0e9e1f7f4 100644
--- a/azure/k8s/secret-challenge-vault-deployment.yml.tpl
+++ b/azure/k8s/secret-challenge-vault-deployment.yml.tpl
@@ -61,7 +61,7 @@ spec:
volumeAttributes:
secretProviderClass: "azure-wrongsecrets-vault"
containers:
- - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha6-k8s-vault
+ - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha11-k8s-vault
imagePullPolicy: IfNotPresent
name: secret-challenge
command: ["/bin/sh"]
diff --git a/docs/CHALLENGE61_MULTI_INSTANCE_SETUP.md b/docs/CHALLENGE61_MULTI_INSTANCE_SETUP.md
new file mode 100644
index 000000000..44369d466
--- /dev/null
+++ b/docs/CHALLENGE61_MULTI_INSTANCE_SETUP.md
@@ -0,0 +1,141 @@
+# Challenge61 Multi-Instance Setup Guide
+
+This guide explains how to configure and run Challenge61, which demonstrates how hardcoded Telegram bot credentials can be discovered and exploited. The bot token is double-encoded in base64 to make it slightly more challenging but still discoverable through code inspection.
+
+## Overview
+
+This challenge supports running on multiple app instances (e.g., Arcane and WrongSecrets Heroku apps) using either polling (getUpdates) or webhooks.
+
+## Option 1: Polling with getUpdates (Default - Works Out of Box)
+
+The code uses update offsets to minimize conflicts between multiple app instances:
+- No configuration needed
+- Uses update offsets to minimize conflicts between instances
+- Multiple instances can run simultaneously
+- Less efficient but simpler setup
+- `timeout=0` - No long polling, quick responses
+- `limit=1` - Process one update at a time
+- Offset acknowledgment - Marks updates as processed
+
+**Status**: ✅ Code updated and tested
+
+## Option 2: Webhook Solution (Recommended for Production)
+
+### Step 1: Configure Each Heroku App
+
+For **WrongSecrets Heroku app**:
+```bash
+heroku config:set CHALLENGE61_WEBHOOK_ENABLED=true -a wrongsecrets-app
+heroku config:set CHALLENGE61_WEBHOOK_TOKEN=$(openssl rand -hex 32) -a wrongsecrets-app
+```
+
+For **Arcane Heroku app**:
+```bash
+heroku config:set CHALLENGE61_WEBHOOK_ENABLED=true -a arcane-app
+heroku config:set CHALLENGE61_WEBHOOK_TOKEN=$(openssl rand -hex 32) -a arcane-app
+```
+
+### Step 2: Choose ONE App for the Webhook
+
+You can only set ONE webhook URL per bot. Choose either WrongSecrets or Arcane:
+
+**Option A: Use WrongSecrets app**
+```bash
+# Get your webhook token
+WEBHOOK_TOKEN=$(heroku config:get CHALLENGE61_WEBHOOK_TOKEN -a wrongsecrets-app)
+
+# Set the webhook
+curl -X POST "https://api.telegram.org/bot8132866643:AAHJmvZqvvM9dI2rtBOu--WMZyMFTfHNo9I/setWebhook?url=https://your-wrongsecrets-app.herokuapp.com/telegram/webhook/challenge61&secret_token=$WEBHOOK_TOKEN"
+```
+
+**Option B: Use Arcane app**
+```bash
+# Get your webhook token
+WEBHOOK_TOKEN=$(heroku config:get CHALLENGE61_WEBHOOK_TOKEN -a arcane-app)
+
+# Set the webhook
+curl -X POST "https://api.telegram.org/bot8132866643:AAHJmvZqvvM9dI2rtBOu--WMZyMFTfHNo9I/setWebhook?url=https://your-arcane-app.herokuapp.com/telegram/webhook/challenge61&secret_token=$WEBHOOK_TOKEN"
+```
+
+### Step 3: Verify Webhook
+
+```bash
+curl "https://api.telegram.org/bot8132866643:AAHJmvZqvvM9dI2rtBOu--WMZyMFTfHNo9I/getWebhookInfo"
+```
+
+### Step 4: Test
+
+1. Open @WrongsecretsBot in Telegram
+2. Send `/start`
+3. Bot should respond: "Welcome! Your secret is: telegram_secret_found_in_channel"
+
+## Alternative: Use Both Apps with getUpdates (Current Setup)
+
+If you want both apps to be able to respond (not recommended but possible):
+
+1. **Keep webhook disabled** (default)
+2. **Accept that responses may be inconsistent** - whichever app polls first will respond
+3. **The improved getUpdates code** minimizes conflicts with offset handling
+
+## Troubleshooting
+
+### Check if webhook is active
+```bash
+curl "https://api.telegram.org/bot8132866643:AAHJmvZqvvM9dI2rtBOu--WMZyMFTfHNo9I/getWebhookInfo"
+```
+
+### Remove webhook (to go back to getUpdates)
+```bash
+curl -X POST "https://api.telegram.org/bot8132866643:AAHJmvZqvvM9dI2rtBOu--WMZyMFTfHNo9I/deleteWebhook"
+```
+
+### View Heroku logs
+```bash
+heroku logs --tail -a wrongsecrets-app | grep Challenge61
+heroku logs --tail -a arcane-app | grep Challenge61
+```
+
+## Recommendation
+
+For **production with multiple apps**: Use webhook on ONE primary app (WrongSecrets).
+
+For **development/testing**: The current getUpdates approach with offsets works fine.
+
+## BotFather Configuration (Optional but Recommended)
+
+### 1. Configure Commands
+
+- Send `/setcommands` to @BotFather
+- Select your bot
+- Add: `start - Get the secret message`
+
+### 2. Set Description
+
+- Send `/setdescription` to @BotFather
+- Select your bot
+- Add: "OWASP WrongSecrets Challenge 61 - Demonstrates hardcoded bot credentials. Send /start to receive the secret!"
+
+### 3. Set About Text
+
+- Send `/setabouttext` to @BotFather
+- Add: "Educational security challenge from OWASP WrongSecrets project"
+
+## Testing the Bot
+
+1. Find the bot: Search for @WrongsecretsBot in Telegram (or your bot username)
+2. Send: `/start`
+3. Receive: "Welcome! Your secret is: telegram_secret_found_in_channel"
+
+## Creating a New Bot
+
+If you need to create your own bot for testing:
+
+1. Message @BotFather in Telegram
+2. Send `/newbot`
+3. Follow prompts to choose name and username
+4. BotFather will provide a token like: `1234567890:ABCdefGHIjklMNOpqrsTUVwxyz`
+5. Double-encode the token for use in this challenge:
+ ```bash
+ echo -n "YOUR_TOKEN" | base64 | base64
+ ```
+6. Replace the `encodedToken` value in the `getBotToken()` method in Challenge61.java
diff --git a/docs/VERSION_MANAGEMENT.md b/docs/VERSION_MANAGEMENT.md
index fa9909d9c..35bd66306 100644
--- a/docs/VERSION_MANAGEMENT.md
+++ b/docs/VERSION_MANAGEMENT.md
@@ -12,9 +12,9 @@ The project maintains version consistency between:
## Version Schema
```
-pom.xml version: 1.13.1-alpha6-SNAPSHOT
-Dockerfile version: 1.13.1-alpha6
-Dockerfile.web version: 1.13.1-alpha6-no-vault
+pom.xml version: 1.13.1-alpha11-SNAPSHOT
+Dockerfile version: 1.13.1-alpha11
+Dockerfile.web version: 1.13.1-alpha11-no-vault
```
## Automated Solutions
diff --git a/fly.toml b/fly.toml
index ebebac4d4..1a127058c 100644
--- a/fly.toml
+++ b/fly.toml
@@ -8,7 +8,7 @@ app = "wrongsecrets"
primary_region = "ams"
[build]
- image = "docker.io/jeroenwillemsen/wrongsecrets:1.13.1-alpha6-no-vault"
+ image = "docker.io/jeroenwillemsen/wrongsecrets:1.13.1-alpha11-no-vault"
[env]
K8S_ENV = "Fly(Docker)"
diff --git a/gcp/k8s/secret-challenge-vault-deployment.yml.tpl b/gcp/k8s/secret-challenge-vault-deployment.yml.tpl
index d537184db..ea7052446 100644
--- a/gcp/k8s/secret-challenge-vault-deployment.yml.tpl
+++ b/gcp/k8s/secret-challenge-vault-deployment.yml.tpl
@@ -58,7 +58,7 @@ spec:
volumeAttributes:
secretProviderClass: "wrongsecrets-gcp-secretsmanager"
containers:
- - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha6-k8s-vault
+ - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha11-k8s-vault
imagePullPolicy: IfNotPresent
name: secret-challenge
command: ["/bin/sh"]
diff --git a/js/index.js b/js/index.js
index e266d6d9a..d2f3b8e80 100644
--- a/js/index.js
+++ b/js/index.js
@@ -1,5 +1,5 @@
function secret() {
- var password = "m2/lkfE=" + 9 + "DsPI" + 6 + "2yc=" + 2 + "BcHo" + 7;
+ var password = "UIz8ASo=" + 9 + "vCx1" + 6 + "DXw=" + 2 + "XaN4" + 7;
return password;
}
diff --git a/k8s/challenge53/secret-challenge53-sidecar.yml b/k8s/challenge53/secret-challenge53-sidecar.yml
index 84bd18354..14180fc8b 100644
--- a/k8s/challenge53/secret-challenge53-sidecar.yml
+++ b/k8s/challenge53/secret-challenge53-sidecar.yml
@@ -21,7 +21,7 @@ spec:
runAsGroup: 2000
fsGroup: 2000
containers:
- - image: jeroenwillemsen/wrongsecrets-challenge53:1.13.1-alpha6
+ - image: jeroenwillemsen/wrongsecrets-challenge53:1.13.1-alpha11
name: secret-challenge-53
imagePullPolicy: IfNotPresent
resources:
@@ -45,7 +45,7 @@ spec:
command: ["/bin/sh", "-c"]
args:
- cp /home/wrongsecrets/* /shared-data/ && exec /home/wrongsecrets/start-on-arch.sh
- - image: jeroenwillemsen/wrongsecrets-challenge53-debug:1.13.1-alpha6
+ - image: jeroenwillemsen/wrongsecrets-challenge53-debug:1.13.1-alpha11
name: sidecar
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c", "while true; do ls /shared-data; sleep 10; done"]
diff --git a/k8s/challenge53/secret-challenge53.yml b/k8s/challenge53/secret-challenge53.yml
index 63f7b00fc..e3e581a9f 100644
--- a/k8s/challenge53/secret-challenge53.yml
+++ b/k8s/challenge53/secret-challenge53.yml
@@ -21,7 +21,7 @@ spec:
runAsGroup: 2000
fsGroup: 2000
containers:
- - image: jeroenwillemsen/wrongsecrets-challenge53:1.13.1-alpha6
+ - image: jeroenwillemsen/wrongsecrets-challenge53:1.13.1-alpha11
name: secret-challenge-53
imagePullPolicy: IfNotPresent
resources:
diff --git a/k8s/secret-challenge-deployment.yml b/k8s/secret-challenge-deployment.yml
index a5788aea5..06c7c0c17 100644
--- a/k8s/secret-challenge-deployment.yml
+++ b/k8s/secret-challenge-deployment.yml
@@ -28,7 +28,7 @@ spec:
runAsGroup: 2000
fsGroup: 2000
containers:
- - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha6-no-vault
+ - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha11-no-vault
imagePullPolicy: IfNotPresent
name: secret-challenge
ports:
diff --git a/k8s/secret-challenge-vault-deployment.yml b/k8s/secret-challenge-vault-deployment.yml
index 7b0aeb467..0b4635637 100644
--- a/k8s/secret-challenge-vault-deployment.yml
+++ b/k8s/secret-challenge-vault-deployment.yml
@@ -50,7 +50,7 @@ spec:
type: RuntimeDefault
serviceAccountName: vault
containers:
- - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha6-k8s-vault
+ - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha11-k8s-vault
imagePullPolicy: IfNotPresent
name: secret-challenge
command: ["/bin/sh"]
diff --git a/okteto/k8s/secret-challenge-ctf-deployment.yml b/okteto/k8s/secret-challenge-ctf-deployment.yml
index 60b4bce17..a4d888f1a 100644
--- a/okteto/k8s/secret-challenge-ctf-deployment.yml
+++ b/okteto/k8s/secret-challenge-ctf-deployment.yml
@@ -28,7 +28,7 @@ spec:
runAsGroup: 2000
fsGroup: 2000
containers:
- - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha6-no-vault
+ - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha11-no-vault
name: secret-challenge-ctf
imagePullPolicy: IfNotPresent
securityContext:
diff --git a/okteto/k8s/secret-challenge-deployment.yml b/okteto/k8s/secret-challenge-deployment.yml
index 9d94cf77e..dc703cf3f 100644
--- a/okteto/k8s/secret-challenge-deployment.yml
+++ b/okteto/k8s/secret-challenge-deployment.yml
@@ -28,7 +28,7 @@ spec:
runAsGroup: 2000
fsGroup: 2000
containers:
- - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha6-no-vault
+ - image: jeroenwillemsen/wrongsecrets:1.13.1-alpha11-no-vault
name: secret-challenge
imagePullPolicy: IfNotPresent
securityContext:
diff --git a/pom.xml b/pom.xml
index 146ebea2f..d0fec6eba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
The bot token is double-encoded in base64 to make it slightly more challenging but still + * discoverable through code inspection. + * + *
This challenge supports running on multiple app instances using either polling (getUpdates) or + * webhooks. For detailed setup instructions including BotFather configuration, webhook setup, and + * creating a new bot, see: {@code docs/CHALLENGE61_MULTI_INSTANCE_SETUP.md} + * + *
Quick Start: Search for @WrongsecretsBot in Telegram and send {@code /start} to receive
+ * the secret.
+ */
@Component
public class Challenge61 implements Challenge {
@@ -81,6 +94,9 @@ private String getSecretFromTelegramChannel(String botToken) {
if (response != null && Boolean.TRUE.equals(response.get("ok"))) {
logger.info("Successfully authenticated with Telegram Bot API");
+ // Send start message with encoded secret
+ sendStartMessage(botToken);
+
// 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
@@ -96,6 +112,91 @@ private String getSecretFromTelegramChannel(String botToken) {
return null;
}
+ /**
+ * Sends a start message containing the secret to the bot. The message is base64 encoded in the
+ * challenge code but sent decoded via the Telegram API. This method checks for incoming /start
+ * commands and responds to them. Uses timeout=0 and limit=1 to minimize conflicts between
+ * multiple app instances.
+ *
+ * @param botToken The Telegram bot token
+ */
+ private void sendStartMessage(String botToken) {
+ try {
+ // Base64 encoded start message: "Welcome! Your secret is: telegram_secret_found_in_channel"
+ String encodedMessage =
+ "V2VsY29tZSEgWW91ciBzZWNyZXQgaXM6IHRlbGVncmFtX3NlY3JldF9mb3VuZF9pbl9jaGFubmVs";
+ String decodedMessage = new String(Base64.decode(encodedMessage), UTF_8);
+
+ logger.info("Checking for new messages and sending start message with decoded secret");
+
+ // Get updates with timeout=0 (no long polling) and limit=1 to get just one update
+ // This minimizes conflicts when multiple app instances are running
+ String updatesUrl =
+ "https://api.telegram.org/bot" + botToken + "/getUpdates?timeout=0&limit=1";
+ Map Webhooks are recommended for production deployments to replace polling with getUpdates. For
+ * detailed setup instructions including environment variables and webhook configuration, see:
+ * {@code docs/CHALLENGE61_MULTI_INSTANCE_SETUP.md}
+ */
+@RestController
+@ConditionalOnProperty(name = "challenge61.webhook.enabled", havingValue = "true")
+public class TelegramWebhookController {
+
+ private static final Logger logger = LoggerFactory.getLogger(TelegramWebhookController.class);
+ private final RestTemplate restTemplate;
+ private final String webhookToken;
+
+ public TelegramWebhookController(@Value("${challenge61.webhook.token:}") String webhookToken) {
+ var requestFactory = new SimpleClientHttpRequestFactory();
+ requestFactory.setConnectTimeout(Duration.ofSeconds(5));
+ requestFactory.setReadTimeout(Duration.ofSeconds(5));
+ this.restTemplate = new RestTemplate(requestFactory);
+ this.webhookToken = webhookToken;
+ logger.info("Challenge61 Telegram webhook controller enabled");
+ }
+
+ @PostMapping("/telegram/webhook/challenge61")
+ public ResponseEntity🎯 Learning Objectives