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
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
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.
5 changes: 5 additions & 0 deletions src/main/resources/explanations/challenge65.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
=== Hiding in binaries part 6: the plain Java CLI

Compiled Java applications can hide secrets too, even when they are only distributed as runnable JARs. Can you find the secret in our plain Java CLI?

Try downloading and inspecting https://github.com/OWASP/wrongsecrets/tree/master/src/main/resources/executables/wrongsecrets-java.jar[wrongsecrets-java.jar]. Run it locally with `java -jar wrongsecrets-java.jar spoil` or `java -jar wrongsecrets-java.jar <your answer>`.
7 changes: 7 additions & 0 deletions src/main/resources/explanations/challenge65_hint.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This challenge uses a plain Java CLI JAR.

You can solve it by:

1. Extracting the JAR and looking at the compiled classes or bundled metadata.
2. Using tools like `strings`, CFR, `javap`, JADX, or IntelliJ's decompiler.
3. Running the JAR with `spoil` or trying to understand how it compares your input.
5 changes: 5 additions & 0 deletions src/main/resources/explanations/challenge65_reason.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*Why shipping a secret inside a plain JAR is still shipping the secret to the attacker.*

Java bytecode is straightforward to decompile. If a secret is embedded in a class, an attacker can recover it from the JAR with static analysis or by observing the program at runtime.

If a client-side executable needs a secret to do its job, assume the secret can be extracted. Prefer retrieving secrets from a trusted backend after proper authentication and authorization.
5 changes: 5 additions & 0 deletions src/main/resources/explanations/challenge66.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
=== 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?

Try downloading and inspecting https://github.com/OWASP/wrongsecrets/tree/master/src/main/resources/executables/wrongsecrets-java-obfuscated.jar[wrongsecrets-java-obfuscated.jar]. Run it locally with `java -jar wrongsecrets-java-obfuscated.jar spoil` or `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. Decompile the JAR with a Java decompiler such as CFR, JADX, or IntelliJ IDEA:
- Download `wrongsecrets-java-obfuscated.jar`.
- Open it in your decompiler of choice.
- Find the main class and trace what happens when the program receives the `spoil` argument.
- Look for helper methods that rebuild the secret at runtime.

2. Inspect the bytecode from the command line:
- Run `jar tf wrongsecrets-java-obfuscated.jar` to list the classes in the JAR.
- Extract it with `jar xf wrongsecrets-java-obfuscated.jar`.
- Run `javap -c -p io/github/owasp/wrongsecrets/WrongSecretsObfuscated.class` on the extracted class.
- Look for the encoded byte array, the XOR key, and the method that decodes the secret.

3. Run the JAR locally and observe its behavior:
- Execute `java -jar wrongsecrets-java-obfuscated.jar spoil`.
- If you want to understand the guessing flow, also run `java -jar wrongsecrets-java-obfuscated.jar wronganswer`.
- Compare the runtime behavior with what you found in the decompiled code to recover the secret.
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