From 0358336566132cb8c3d501b947a88f47721512fd Mon Sep 17 00:00:00 2001 From: Marcin Nowak Date: Wed, 23 Mar 2022 21:46:25 +0100 Subject: [PATCH] #44 front end JavaScript library with key obfuscated --- js/index.js | 2 +- .../challenges/docker/Challenge15.java | 4 +- .../wrongsecrets/client/TokenController.java | 41 ------------- .../wrongsecrets/oauth/TokenController.java | 59 +++++++++++++++++++ .../resources/explanations/challenge15.adoc | 4 +- .../explanations/challenge15_hint.adoc | 8 +-- .../explanations/challenge15_reason.adoc | 2 +- src/main/resources/templates/index.html | 9 +-- .../challenges/docker/Challenge15Test.java | 53 +++++++++++++++++ 9 files changed, 127 insertions(+), 55 deletions(-) delete mode 100644 src/main/java/org/owasp/wrongsecrets/client/TokenController.java create mode 100644 src/main/java/org/owasp/wrongsecrets/oauth/TokenController.java create mode 100644 src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge15Test.java diff --git a/js/index.js b/js/index.js index 9326c7373..a6f4fd0bb 100644 --- a/js/index.js +++ b/js/index.js @@ -1,4 +1,4 @@ function secret() { - var password = 'wg2QIk2N' + 8 + 'YmTEd' + 3 + 'C/jnlkFGeIpdeGI+lKzK7rROePYU='; + var password = 'wg' + 2 + 'QIk' + 2 + 'N' + 8 + 'YmTEd' + 3 + 'C/jnlkFGeIpdeGI+lKzK' + 7 + 'rROePYU='; return password } diff --git a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge15.java b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge15.java index 90d34e899..af137eccb 100644 --- a/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge15.java +++ b/src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge15.java @@ -19,7 +19,7 @@ @Order(15) public class Challenge15 extends Challenge { - private String dockerMountPath; + private final String dockerMountPath; public Challenge15(ScoreCard scoreCard, @Value("${challengedockermtpath}") String dockerMountPath) { super(scoreCard); @@ -42,7 +42,7 @@ public List supportedRuntimeEnvironments() { return List.of(RuntimeEnvironment.Environment.DOCKER); } - private String getActualData() { + public String getActualData() { try { return Files.readString(Paths.get(dockerMountPath, "yourkey.txt")); } catch (Exception e) { diff --git a/src/main/java/org/owasp/wrongsecrets/client/TokenController.java b/src/main/java/org/owasp/wrongsecrets/client/TokenController.java deleted file mode 100644 index 107f99f63..000000000 --- a/src/main/java/org/owasp/wrongsecrets/client/TokenController.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.owasp.wrongsecrets.client; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; - -import java.util.UUID; - -@Controller -public class TokenController { - - @PostMapping(path = "/oauth/token", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}) - public ResponseEntity clientCredentialToken(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization, - TokenRequest tokenRequest) { - if ("Basic am9objp3ZzJRSWsyTjhZbVRFZDNDL2pubGtGR2VJcGRlR0krbEt6SzdyUk9lUFlVPQ==".equals(authorization) - && "client_credentials".equals(tokenRequest.grant_type()) - && "user_info".equals(tokenRequest.scope())) { - return ResponseEntity.ok( - new TokenResponse(UUID.randomUUID().toString(), "bearer", 54321L, "user_info") - ); - } - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .build(); - } - - public record TokenRequest(String grant_type, - String scope) { - } - - public record TokenResponse(@JsonProperty("access_token") String accessToken, - @JsonProperty("token_type") String tokenType, - @JsonProperty("expires_in") Long expiresIn, - String scope) { - } -} diff --git a/src/main/java/org/owasp/wrongsecrets/oauth/TokenController.java b/src/main/java/org/owasp/wrongsecrets/oauth/TokenController.java new file mode 100644 index 000000000..c9bcbcd45 --- /dev/null +++ b/src/main/java/org/owasp/wrongsecrets/oauth/TokenController.java @@ -0,0 +1,59 @@ +package org.owasp.wrongsecrets.oauth; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.UUID; + +@Slf4j +@Controller +public class TokenController { + + private final String dockerMountPath; + + public TokenController(@Value("${challengedockermtpath}") String dockerMountPath) { + this.dockerMountPath = dockerMountPath; + } + + + @PostMapping(path = "/token", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}) + public ResponseEntity clientCredentialToken(TokenRequest tokenRequest) { + if ("client_credentials".equals(tokenRequest.grant_type()) + && "WRONGSECRET_CLIENT_ID".equals(tokenRequest.client_id()) + && getActualData().equals(tokenRequest.client_secret())) { + return ResponseEntity.ok( + new TokenResponse(UUID.randomUUID().toString(), "bearer", 54321L, "user_info") + ); + } + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .build(); + } + + public record TokenRequest(String grant_type, + String client_id, + String client_secret) { + } + + public record TokenResponse(@JsonProperty("access_token") String accessToken, + @JsonProperty("token_type") String tokenType, + @JsonProperty("expires_in") Long expiresIn, + String scope) { + } + + public String getActualData() { + try { + return Files.readString(Paths.get(dockerMountPath, "yourkey.txt")); + } catch (Exception e) { + log.warn("Exception during file reading, defaulting to default without cloud environment", e); + return "if_you_see_this_please_use_docker_instead"; + } + } +} diff --git a/src/main/resources/explanations/challenge15.adoc b/src/main/resources/explanations/challenge15.adoc index 28d34c316..338371539 100644 --- a/src/main/resources/explanations/challenge15.adoc +++ b/src/main/resources/explanations/challenge15.adoc @@ -1,6 +1,8 @@ === Docker COPY and WORKDIR When we start new project usually we are focus on new festers implementation than on security aspect. -In such situation it easy to store secret or credential in front-end code. +Sometimes Single Page Application or mobile application need to access information for themself rather then on behalf of a user. +For this purpose OAuth provides the `client_credentials` flow to get access token. +In such situation it easy to store client secrets in front-end or mobile application code. What about looking for it in the Development Tools in browser? diff --git a/src/main/resources/explanations/challenge15_hint.adoc b/src/main/resources/explanations/challenge15_hint.adoc index 5b2af47d4..ec12b5f99 100644 --- a/src/main/resources/explanations/challenge15_hint.adoc +++ b/src/main/resources/explanations/challenge15_hint.adoc @@ -1,9 +1,7 @@ You can solve this challenge by the following steps: -1. Open main page in the browser +1. Open main page in the Chrome browser 2. Open development tools: - select Network tab -- find request with path `/oauth/token` -- find in the request `Authorization` header -- decode Base64 heder value after `Basic` -- the first part before `:` is user name and the second is password +- find request with path `/token` +- find in the request body `client_secret` diff --git a/src/main/resources/explanations/challenge15_reason.adoc b/src/main/resources/explanations/challenge15_reason.adoc index 6a864267d..6a36c1246 100644 --- a/src/main/resources/explanations/challenge15_reason.adoc +++ b/src/main/resources/explanations/challenge15_reason.adoc @@ -1,4 +1,4 @@ -*Why using Single Page Application or Mobile application to put secrets in is a bad idea* +*Why using Single Page Application or Mobile application to put client secret in is a bad idea* As you can tell by now, you can easily detect any secret that stored within a Single Page Application or Mobile application. Authorization Code Flow with Proof Key for Code Exchange (PKCE) diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 5ee3d2b88..53518399a 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -28,10 +28,11 @@ diff --git a/src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge15Test.java b/src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge15Test.java new file mode 100644 index 000000000..a3603601b --- /dev/null +++ b/src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge15Test.java @@ -0,0 +1,53 @@ +package org.owasp.wrongsecrets.challenges.docker; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.owasp.wrongsecrets.ScoreCard; +import org.owasp.wrongsecrets.challenges.Spoiler; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +@ExtendWith(MockitoExtension.class) +class Challenge15Test { + + @Mock + private ScoreCard scoreCard; + + @Test + void solveChallenge15WithoutFile(@TempDir Path dir) { + var challenge = new Challenge15(scoreCard, dir.toString()); + + Assertions.assertThat(challenge.answerCorrect("secretvalueWitFile")).isFalse(); + Assertions.assertThat(challenge.answerCorrect("if_you_see_this_please_use_docker_instead")).isTrue(); + } + + @Test + void solveChallenge15WithMNTFile(@TempDir Path dir) throws Exception { + var testFile = new File(dir.toFile(), "yourkey.txt"); + var secret = "secretvalueWitFile"; + Files.writeString(testFile.toPath(), secret); + + var challenge = new Challenge15(scoreCard, dir.toString()); + + Assertions.assertThat(challenge.answerCorrect("secretvalueWitFile")).isTrue(); + } + + @Test + void spoilShouldReturnCorrectAnswer(@TempDir Path dir) throws IOException { + var testFile = new File(dir.toFile(), "yourkey.txt"); + var secret = "secretvalueWitFile"; + Files.writeString(testFile.toPath(), secret); + + var challenge = new Challenge15(scoreCard, dir.toString()); + + Assertions.assertThat(challenge.spoiler()).isEqualTo(new Spoiler("secretvalueWitFile")); + } + +}