Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ src/main/resources/executables/decrypt/decrypt
src/main/resources/executables/wrongsecrets-dotnet
src/main/resources/executables/wrongsecrets-dotnet*

# Challenge 65/66
!src/main/resources/executables/wrongsecrets-java.jar
!src/main/resources/executables/wrongsecrets-java-ctf.jar
!src/main/resources/executables/wrongsecrets-java-obfuscated.jar
!src/main/resources/executables/wrongsecrets-java-obfuscated-ctf.jar

# Challenge 59
k8s/challenge53/executables/wrongsecrets-challenge53-c
k8s/challenge53/executables/wrongsecrets-challenge53-c*
Expand Down
1 change: 1 addition & 0 deletions Dockerfile_webdesktop
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ RUN --mount=type=secret,id=mysecret \
WORKDIR /config/Desktop

COPY src/main/resources/executables/*linux-mus* /var/tmp/wrongsecrets/
COPY src/main/resources/executables/wrongsecrets-java*.jar /var/tmp/wrongsecrets/
COPY src/main/resources/executables/decrypt/ /var/tmp/wrongsecrets/decrypt/
COPY src/main/resources/executables/wrongsecrets-advanced-c-windows.exe /var/tmp/wrongsecrets/
COPY src/main/resources/executables/secrchallenge.md /var/tmp/wrongsecrets/
Expand Down
1 change: 1 addition & 0 deletions Dockerfile_webdesktopk8s
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ RUN --mount=type=secret,id=mysecret \
WORKDIR /config/Desktop

COPY src/main/resources/executables/*linux-mus* /var/tmp/wrongsecrets/
COPY src/main/resources/executables/wrongsecrets-java*.jar /var/tmp/wrongsecrets/
COPY src/main/resources/executables/decrypt/ /var/tmp/wrongsecrets/decrypt/
COPY src/main/resources/executables/wrongsecrets-advanced-c-windows.exe /var/tmp/wrongsecrets/
COPY src/main/resources/executables/secrchallenge.md /var/tmp/wrongsecrets/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.owasp.wrongsecrets.challenges.docker;

import org.owasp.wrongsecrets.challenges.FixedAnswerChallenge;
import org.owasp.wrongsecrets.challenges.docker.binaryexecution.BinaryExecutionHelper;
import org.owasp.wrongsecrets.challenges.docker.binaryexecution.MuslDetectorImpl;
import org.springframework.stereotype.Component;

/** This challenge is about finding a secret hardcoded in a plain Java CLI JAR. */
@Component
public class Challenge65 extends FixedAnswerChallenge {

@Override
public String getAnswer() {
BinaryExecutionHelper binaryExecutionHelper =
new BinaryExecutionHelper(65, new MuslDetectorImpl());
return binaryExecutionHelper.executeJavaJar("", "wrongsecrets-java.jar");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.owasp.wrongsecrets.challenges.docker;

import org.owasp.wrongsecrets.challenges.FixedAnswerChallenge;
import org.owasp.wrongsecrets.challenges.docker.binaryexecution.BinaryExecutionHelper;
import org.owasp.wrongsecrets.challenges.docker.binaryexecution.MuslDetectorImpl;
import org.springframework.stereotype.Component;

/** This challenge is about finding a secret hidden in an obfuscated Java CLI JAR. */
@Component
public class Challenge66 extends FixedAnswerChallenge {

@Override
public String getAnswer() {
BinaryExecutionHelper binaryExecutionHelper =
new BinaryExecutionHelper(66, new MuslDetectorImpl());
return binaryExecutionHelper.executeJavaJar("", "wrongsecrets-java-obfuscated.jar");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ResourceUtils;

/** Helper for classes to execute binaries as part of the Binary challenges. */
Expand Down Expand Up @@ -111,6 +116,37 @@ public String executeCommand(String guess, String fileName) {
}
}

/**
* Execute a Java CLI packaged as a JAR for either secret retrieval or guess validation.
*
* @param guess containing the guess
* @param fileName of the JAR to be used (pre-defined, make sure it is never user input
* controlled)
* @return the actual answer
*/
public String executeJavaJar(String guess, String fileName) {
BinaryInstructionForFile binaryInstructionForFile;
if (Strings.isNullOrEmpty(guess)) {
binaryInstructionForFile = BinaryInstructionForFile.Spoil;
} else {
binaryInstructionForFile = BinaryInstructionForFile.Guess;
}
try {
File jarFile = createTempJar(fileName);
String result = executeJavaJar(jarFile, binaryInstructionForFile, guess);
deleteFile(jarFile);
log.info(
"stdout challenge {}: {}",
challengeNumber,
result.lines().collect(Collectors.joining("")));
return result;
} catch (Exception e) {
log.warn("Error executing Java JAR:", e);
executionException = e;
return ERROR_EXECUTION;
}
}

@SuppressFBWarnings(
value = "COMMAND_INJECTION",
justification = "We check for various injection methods and counter those")
Expand Down Expand Up @@ -146,6 +182,34 @@ private String executeCommand(
}
}

@SuppressFBWarnings(
value = "COMMAND_INJECTION",
justification = "We check for various injection methods and counter those")
private String executeJavaJar(
File jarFile, BinaryInstructionForFile binaryInstructionForFile, String guess)
throws IOException, InterruptedException {
if (!jarFile.getPath().contains("wrongsecrets")
|| stringContainsCommandChainToken(jarFile.getPath())
|| stringContainsCommandChainToken(guess)) {
return BinaryExecutionHelper.ERROR_EXECUTION;
}

ProcessBuilder ps;
if (binaryInstructionForFile.equals(BinaryInstructionForFile.Spoil)) {
ps = new ProcessBuilder("java", "-jar", jarFile.getPath(), "spoil");
} else {
ps = new ProcessBuilder("java", "-jar", jarFile.getPath(), guess);
}
ps.redirectErrorStream(true);
Process pr = ps.start();
try (BufferedReader in =
new BufferedReader(new InputStreamReader(pr.getInputStream(), StandardCharsets.UTF_8))) {
String result = in.readLine();
pr.waitFor();
return result;
}
}

private boolean stringContainsCommandChainToken(String testString) {
String[] tokens = {"!", "&", "|", "<", ">", ";"};
boolean found = false;
Expand Down Expand Up @@ -248,6 +312,20 @@ private File createTempExecutable(String fileName) throws IOException {
return execFile;
}

@SuppressFBWarnings(
value = "PATH_TRAVERSAL_IN",
justification = "The jar file name is hardcoded at the caller level")
private File createTempJar(String fileName) throws IOException {
File execFile = File.createTempFile("java-jar-" + fileName.replace('.', '-'), ".jar");
try {
FileUtils.copyInputStreamToFile(
new ClassPathResource("executables/" + fileName).getInputStream(), execFile);
} catch (IOException e) {
FileUtils.copyFile(retrieveFile(fileName), execFile);
}
return execFile;
}

@SuppressFBWarnings(
value = "COMMAND_INJECTION",
justification = "We check for various injection methods and counter those")
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
10 changes: 10 additions & 0 deletions src/main/resources/explanations/challenge65.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
=== Hiding in binaries part 6: the plain Java CLI

Plain strings inside a Java CLI are easy to recover once the JAR is downloaded. Can you find the secret hidden in our plain Java CLI?

To solve it:

. Download and inspect https://github.com/OWASP/wrongsecrets/raw/master/src/main/resources/executables/wrongsecrets-java.jar[wrongsecrets-java.jar].
. Decompile the JAR or inspect the main class with a Java decompiler such as CFR, JADX, IntelliJ IDEA, or `javap`.
. Look for the class that returns the secret and identify the plain string embedded in the CLI.
. Once you recover the exact secret value, you can submit it in the box below or validate it with `java -jar wrongsecrets-java.jar <your recovered secret>`.
18 changes: 18 additions & 0 deletions src/main/resources/explanations/challenge65_hint.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
This challenge uses a plain Java CLI JAR.

You can solve it by:

1. Find the compiled class that holds the secret:
- Download `wrongsecrets-java.jar`.
- Run `jar tf wrongsecrets-java.jar` and locate `io/github/owasp/wrongsecrets/WrongSecretsPlain.class`.
- Open that class in CFR, JADX, IntelliJ IDEA, or another decompiler.

2. Inspect how the secret is stored:
- Run `javap -c -p -classpath wrongsecrets-java.jar io.github.owasp.wrongsecrets.WrongSecretsPlain`.
- Find the `getSecret()` method and look at the `ldc` instruction it uses.
- Notice that the secret is stored as a plain string constant instead of being obfuscated.

3. Recover the value and submit it:
- Copy the string returned by `getSecret()` from the decompiler or bytecode output.
- If you want another quick check, tools like `strings wrongsecrets-java.jar` can also help expose readable constants.
- Submit the recovered string as the answer.
7 changes: 7 additions & 0 deletions src/main/resources/explanations/challenge65_reason.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*Why runnable JARs and Android APKs should not be used to hide secrets.*

Runnable JARs are not a safe place to hide secrets. Just like Android APKs, they are archives that ship bytecode and resources directly to the attacker, which makes embedded strings, constants, and helper methods straightforward to inspect with common reverse-engineering tools.

If a client-side Java artifact needs a secret to work, assume that secret can be extracted once the file is downloaded. Keep real secrets on a trusted backend and only release them after proper authentication and authorization.

If you want more Java and Android reverse-engineering practice, explore the https://github.com/OWASP/MASTG-Hacking-Playground[OWASP MASTG Hacking Playground].
10 changes: 10 additions & 0 deletions src/main/resources/explanations/challenge66.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
=== Hiding in binaries part 7: the obfuscated Java CLI

Obfuscation might slow someone down, but it does not stop them from recovering embedded secrets. Can you find the harder secret in our obfuscated Java CLI?

To solve it:

. Download and inspect https://github.com/OWASP/wrongsecrets/tree/master/src/main/resources/executables/wrongsecrets-java-obfuscated.jar[wrongsecrets-java-obfuscated.jar].
. Decompile the JAR with a Java decompiler such as CFR, JADX, or IntelliJ IDEA and trace the main class.
. Look for the encoded byte array, XOR key, and helper methods that reconstruct the secret at runtime.
. Once you recover the secret, submit it with `java -jar wrongsecrets-java-obfuscated.jar <your answer>`.
20 changes: 20 additions & 0 deletions src/main/resources/explanations/challenge66_hint.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
This challenge uses an obfuscated Java CLI JAR.

You can solve it by:

1. Find where the obfuscated data lives:
- Download `wrongsecrets-java-obfuscated.jar`.
- Run `jar tf wrongsecrets-java-obfuscated.jar` and locate `io/github/owasp/wrongsecrets/WrongSecretsObfuscated.class`.
- Open that class in CFR, JADX, IntelliJ IDEA, or another decompiler.
- Look for the static fields that hold the XOR key and the encoded secret bytes.

2. Inspect the exact decoding logic:
- Run `javap -c -p -classpath wrongsecrets-java-obfuscated.jar io.github.owasp.wrongsecrets.WrongSecretsObfuscated`.
- In the output, find the `static { ... }` block that fills `XOR_KEY_CHARS` and `ENCODED_SECRET`.
- Then find `decodeSecret()` and note that each encoded byte is XORed with one byte from the key, repeating the key with modulo arithmetic.

3. Rebuild the secret yourself:
- Convert the `XOR_KEY_CHARS` values into bytes.
- Copy the `ENCODED_SECRET` byte values from the bytecode or decompiled source.
- XOR each encoded byte with the matching key byte, wrapping around when you reach the end of the key.
- Decode the resulting byte array as UTF-8 to recover the secret, then submit that value as the answer.
5 changes: 5 additions & 0 deletions src/main/resources/explanations/challenge66_reason.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*Why obfuscation is only a speed bump.*

Encoding, reflection, and light obfuscation can make reverse engineering less convenient, but they do not create real secrecy. The executable still contains everything it needs to recover the secret.

If the application can derive the secret locally, a determined attacker can do the same. Protect secrets by moving trust decisions and secret material to controlled server-side systems.
26 changes: 26 additions & 0 deletions src/main/resources/wrong-secrets-configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -987,3 +987,29 @@ configurations:
category: *bin
ctf:
enabled: true

- name: Challenge 65
short-name: "challenge-65"
sources:
- class-name: "org.owasp.wrongsecrets.challenges.docker.Challenge65"
explanation: "explanations/challenge65.adoc"
hint: "explanations/challenge65_hint.adoc"
reason: "explanations/challenge65_reason.adoc"
environments: *all_envs
difficulty: *normal
category: *bin
ctf:
enabled: true

- name: Challenge 66
short-name: "challenge-66"
sources:
- class-name: "org.owasp.wrongsecrets.challenges.docker.Challenge66"
explanation: "explanations/challenge66.adoc"
hint: "explanations/challenge66_hint.adoc"
reason: "explanations/challenge66_reason.adoc"
environments: *all_envs
difficulty: *master
category: *bin
ctf:
enabled: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.owasp.wrongsecrets.challenges.docker;

import static org.assertj.core.api.Assertions.assertThat;
import static org.owasp.wrongsecrets.Challenges.ErrorResponses.EXECUTION_ERROR;

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

class Challenge65Test {

@Test
void spoilerShouldNotCrash() {
var challenge = new Challenge65();

assertThat(challenge.spoiler()).isNotEqualTo(new Spoiler(EXECUTION_ERROR));
assertThat(challenge.answerCorrect(challenge.spoiler().solution())).isTrue();
}

@Test
void incorrectAnswerShouldNotSolveChallenge() {
var challenge = new Challenge65();

assertThat(challenge.answerCorrect("wrong answer")).isFalse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.owasp.wrongsecrets.challenges.docker;

import static org.assertj.core.api.Assertions.assertThat;
import static org.owasp.wrongsecrets.Challenges.ErrorResponses.EXECUTION_ERROR;

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

class Challenge66Test {

@Test
void spoilerShouldNotCrash() {
var challenge = new Challenge66();

assertThat(challenge.spoiler()).isNotEqualTo(new Spoiler(EXECUTION_ERROR));
assertThat(challenge.answerCorrect(challenge.spoiler().solution())).isTrue();
}

@Test
void incorrectAnswerShouldNotSolveChallenge() {
var challenge = new Challenge66();

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