Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add challenge 37 for ZAP configuration with authenticated endpoint #941

Merged
merged 56 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
56c66d2
Fist setups
commjoen Aug 20, 2023
b6ea0bd
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Aug 20, 2023
ce6a030
Fist setups part 2
commjoen Aug 21, 2023
3046160
Fist setups part 2
commjoen Aug 21, 2023
f49db81
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Aug 21, 2023
a5b8107
fix: add HTTP basic authentication for challenge 37
Aug 29, 2023
1dcbbdb
fix: configure basic-auth username/password
Aug 29, 2023
c7e9904
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Aug 29, 2023
8faec85
fix: update Canary token security configuration
Aug 29, 2023
2968234
Adding basic docs
commjoen Aug 29, 2023
03468e7
fix: update Heroku security config
Aug 29, 2023
12aeb83
Merge branch 'master' into feature-zap-scan-leak
commjoen Sep 1, 2023
35be7ca
Merge branch 'master' into feature-zap-scan-leak
commjoen Sep 4, 2023
f62c9fd
silly setup for getting challenge to work
commjoen Sep 4, 2023
6cabc87
additional fix: no deprecated api
commjoen Sep 4, 2023
61c3d2b
additional fix: no deprecated api - permit the rest
commjoen Sep 4, 2023
e434ffb
Update dast-zap-test.yml to no longer contain: authorization
commjoen Sep 4, 2023
57eeb6c
Test with an options file
commjoen Sep 5, 2023
0eb276a
Merge branch 'master' into feature-zap-scan-leak
commjoen Sep 5, 2023
d5c072c
Test with an options file
commjoen Sep 5, 2023
8821631
migrate to checkout v4
commjoen Sep 5, 2023
0327b82
Merge branch 'master' into feature-zap-scan-leak
commjoen Sep 5, 2023
03d4dce
Merge branch 'master' into feature-zap-scan-leak
commjoen Sep 5, 2023
d3d6532
possible fix to ref config file
commjoen Sep 5, 2023
a098d51
fix: security configuration
Sep 5, 2023
6576482
Fix merge conflicts
Sep 5, 2023
fec4a36
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Sep 5, 2023
c4b2db3
Update rule-config.tsv
commjoen Sep 5, 2023
f55bbb4
Added missing docs and stable answer key
commjoen Sep 6, 2023
c625e08
Added missing docs and todos for challenge37
commjoen Sep 10, 2023
835ee84
Added missing tests challeenge7
commjoen Sep 10, 2023
cb60f75
extending missing tests
commjoen Sep 10, 2023
19e3846
extending missing vars
commjoen Sep 10, 2023
66351a6
extending missing vars
commjoen Sep 10, 2023
afaf394
fix: security configuration
Sep 12, 2023
012b213
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Sep 12, 2023
25fb757
Apply suggestions from code review
commjoen Sep 13, 2023
2eb95f4
Merge branch 'master' into feature-zap-scan-leak
commjoen Sep 13, 2023
084f628
Merge branch 'master' into feature-zap-scan-leak
commjoen Sep 15, 2023
9bca2ec
Merge branch 'master' into feature-zap-scan-leak
commjoen Sep 27, 2023
a35d014
Fixed ZAP auth issue
commjoen Sep 29, 2023
7faaa21
Fixed ZAP auth issue
commjoen Sep 29, 2023
e25cf0f
Fixed ZAP auth issue
commjoen Sep 29, 2023
7fcc093
Fixed ZAP auth issue
commjoen Sep 29, 2023
dd6e843
Fixed ZAP auth issue
commjoen Sep 29, 2023
03c6314
retry fix ZAP auth issue
commjoen Sep 29, 2023
4e0ca66
retry fix ZAP auth issue
commjoen Sep 29, 2023
b7aa351
retry fix ZAP auth issue
commjoen Sep 29, 2023
30dd292
retry fix ZAP auth issue
commjoen Sep 29, 2023
b2622ce
retry fix ZAP auth issue
commjoen Sep 29, 2023
c6ba1cf
retry fix ZAP auth issue
commjoen Sep 30, 2023
8501750
retry fix ZAP auth issue
commjoen Sep 30, 2023
9d5d1a6
retry fix ZAP auth issue
commjoen Sep 30, 2023
c2670e8
retry fix ZAP auth issue
commjoen Sep 30, 2023
84115c3
retry fix ZAP auth issue
commjoen Sep 30, 2023
e8546d8
Merge branch 'master' into feature-zap-scan-leak
commjoen Oct 2, 2023
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
3 changes: 3 additions & 0 deletions .github/workflows/dast-zap-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ jobs:
run: nohup ./mvnw spring-boot:run -Dspring-boot.run.profiles=without-vault &
- name: ZAP Scan
uses: zaproxy/[email protected]
env:
ZAP_AUTH_HEADER_VALUE: "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
with:
allow_issue_writing: false
docker_name: "owasp/zap2docker-stable"
target: "http://localhost:8080"
rules_file_name: config/zap/rule-config.tsv
fail_action: true
cmd_options: '-z "-configFile /zap/wrk/config/zap/options.prop"'
6 changes: 6 additions & 0 deletions config/zap/options.prop
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
replacer.full_list(0).description=auth1
replacer.full_list(0).enabled=true
replacer.full_list(0).matchtype=REQ_HEADER
replacer.full_list(0).matchstr=Authorization
replacer.full_list(0).regex=false
replacer.full_list(0).replacement=Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
1 change: 1 addition & 0 deletions config/zap/rule-config.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
90033 IGNORE (Loosely Scoped Cookie)
10096 IGNORE (Timestamp Disclosure - Unix)
10112 IGNORE Session Management Response Identified
10105 IGNORE Authentication Credentials Captured
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@SpringBootApplication
@EnableConfigurationProperties(Vaultpassword.class)
@Slf4j
@EnableWebSecurity(debug = false)
public class WrongSecretsApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ public class TokenCallbackSecurityConfiguration {
"There is no need for the token & canaries endpoints to have CSRF as it is only used for"
+ " callbacks by canarytokens.org")
@Bean
@Order(0)
@Order(2)
public SecurityFilterChain configureTokenCallbackSecurity(HttpSecurity http) throws Exception {
http.securityMatcher(
r ->
r.getRequestURL().toString().contains("canaries")
|| r.getRequestURL().toString().contains("token"))
.csrf()
.disable();
return http.build();
return http.csrf(
csrf ->
csrf.ignoringRequestMatchers(
"/canaries/tokencallback", "/canaries/tokencallbackdebug"))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.owasp.wrongsecrets.ScoreCard;
import org.owasp.wrongsecrets.challenges.docker.Challenge0;
import org.owasp.wrongsecrets.challenges.docker.Challenge30;
import org.owasp.wrongsecrets.challenges.docker.Challenge37;
import org.owasp.wrongsecrets.challenges.docker.Challenge8;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -54,6 +55,9 @@ public class ChallengesController {
@Value("${challenge_thirty_ctf_to_provide_to_host_value}")
private String keyToProvideToHostForChallenge30;

@Value("${challenge_rando_key_ctf_to_provide_to_host_value}")
private String getKeyToProvideToHostChallenge37;

@Value("${CTF_SERVER_ADDRESS}")
private String ctfServerAddress;

Expand Down Expand Up @@ -189,6 +193,19 @@ public String postController(
+ "for which you get your code: "
+ keyToProvideToHostForChallenge30);
}
} else if (challenge.getChallenge() instanceof Challenge37) {
if (!Strings.isNullOrEmpty(getKeyToProvideToHostChallenge37)
&& !keyToProvideToHostForChallenge30.equals(
"not_set")) { // this means that it was overriden with a code that needs to be
// returned to the ctf key exchange hos
model.addAttribute(
"answerCorrect",
"Your answer is correct! "
+ "fill in the following answer in the CTF instance at "
+ ctfServerAddress
+ "for which you get your code: "
+ getKeyToProvideToHostChallenge37);
}
} else {
model.addAttribute(
"answerCorrect",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.owasp.wrongsecrets.challenges.docker;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Base64;
import org.owasp.wrongsecrets.RuntimeEnvironment;
import org.owasp.wrongsecrets.ScoreCard;
import org.owasp.wrongsecrets.challenges.Challenge;
import org.owasp.wrongsecrets.challenges.ChallengeTechnology;
import org.owasp.wrongsecrets.challenges.Difficulty;
import org.owasp.wrongsecrets.challenges.Spoiler;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
* This is a challenge based on the idea of leaking a secret for an authenticated endpoint through a
* ZAP configuration file.
*/
@Slf4j
@Component
@Order(37)
public class Challenge37 extends Challenge {

private String secret;
private String password = "WWpOQ2JHSnBRbnBhV0U1b1lsZFZTd289Cg==";

public Challenge37(ScoreCard scoreCard) {
super(scoreCard);
secret = UUID.randomUUID().toString();
}

@Override
public boolean canRunInCTFMode() {
return true;
}

@Override
public Spoiler spoiler() {
return new Spoiler(secret);
}

@Override
public boolean answerCorrect(String answer) {
return secret.equals(answer);
}

@Override
public int difficulty() {
return Difficulty.NORMAL;
}

/** {@inheritDoc} This is a CICD type of challenge */
@Override
public String getTech() {
return ChallengeTechnology.Tech.CICD.id;
}

@Override
public boolean isLimitedWhenOnlineHosted() {
return false;
}

@Override
public List<RuntimeEnvironment.Environment> supportedRuntimeEnvironments() {
return List.of(RuntimeEnvironment.Environment.DOCKER);
}

public String getPassword() {

return new String(Base64.decode(Base64.decode(Base64.decode(password))), StandardCharsets.UTF_8)
.replaceAll("\\r|\\n", "");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.owasp.wrongsecrets.challenges.docker.authchallenge;

import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.owasp.wrongsecrets.challenges.docker.Challenge37;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class AuthenticatedRestControllerChallenge37 {

private final Challenge37 challenge37;

public AuthenticatedRestControllerChallenge37(Challenge37 challenge37) {
this.challenge37 = challenge37;
}

@Operation(summary = "Endpoint for interaction at challenge 37")
@GetMapping("/authenticated/challenge37")
public String getAuthSecret() {
return challenge37.spoiler().solution();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.owasp.wrongsecrets.challenges.docker.authchallenge;

import org.owasp.wrongsecrets.challenges.docker.Challenge37;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

/**
* Ensures the need for basic auth for the endpoint having the actual Secret
*
* <p>Be careful with the order of the SecurityFilterChain beans. The first one that matches the
* request will be used. You can see it in action if you put a breakpoint at {@link
* org.springframework.security.web.FilterChainProxy#getFilters(javax.servlet.http.HttpServletRequest)}.
*/
@Configuration
public class AuthenticationChallengeSecurityConfig {

private final Challenge37 challenge37;

public AuthenticationChallengeSecurityConfig(Challenge37 challenge37) {
this.challenge37 = challenge37;
}

@Bean
@Order(3)
public SecurityFilterChain configureBasicAuthForChallenge(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(
r -> r.requestMatchers("/authenticated/**").authenticated().anyRequest().permitAll())
.httpBasic(Customizer.withDefaults())
.build();
}

@Bean
public UserDetailsService userDetailsService() {
UserDetails admin =
User.builder()
.username("Aladdin")
.password("{noop}" + challenge37.getPassword())
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
package org.owasp.wrongsecrets.securityconfig;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.SecurityFilterChain;

/** Used to implement https redirect for our Heroku-hosted workload. */
@Configuration
@EnableWebSecurity
public class HerokuWebSecurityConfig {

@Bean
@Order(1)
public SecurityFilterChain configureHerokuWebSecurity(HttpSecurity http) throws Exception {
http.requiresChannel()
.requestMatchers(
public SecurityFilterChain configureHerokuWebSecurity(
HttpSecurity http, ObjectProvider<PortMapper> portMapperProvider) throws Exception {
var portMapper = portMapperProvider.getIfAvailable();
if (portMapper != null) {
http.portMapper(configurer -> configurer.portMapper(portMapper));
}
http.securityMatcher(
r ->
r.getRequestURL().toString().contains("heroku")
&& (r.getHeader("x-forwarded-proto") != null
|| r.getHeader("X-Forwarded-Proto") != null))
.requiresSecure();
&& r.getHeader("x-forwarded-proto") != null)
.requiresChannel((requiresChannel) -> requiresChannel.anyRequest().requiresSecure());
return http.build();
}
}
1 change: 1 addition & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ hints_enabled=true
ctf_enabled=false
spoiling_enabled=true
ctf_key=TRwzkRJnHOTckssAeyJbysWgP!Qc2T
challenge_rando_key_ctf_to_provide_to_host_value=not_set
challenge_thirty_ctf_to_provide_to_host_value=not_set
challenge_acht_ctf_to_provide_to_host_value=not_set
challenge_acht_ctf_host_value=not_set
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/explanations/challenge37.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
=== Giving your security tests access

Given all the daft findings we already have with this project, we decided to implement automated scanning using ZAP. But for that we do need to be able to fuzz the endpoint of this challenge: `authenticated/challenge37` and thus configure basic auth for ZAP. Can you find the actual secret returned at the endpoint?

Tip: we have a Github Action for ZAP which has its basic auth pre-configured.
commjoen marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 12 additions & 0 deletions src/main/resources/explanations/challenge37_hint.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
This is a CI/CD configuration challenge. You can find the answer by authenticating to the protected endpoint with basic auth and get the actual value:

1. Use a browser to get the secret:
- First go to the https://github.com/OWASP/wrongsecrets/blob/master/.github/workflows/dast-zap-test.yml[Github Workflow]
- Find the environment variable with which we configure basic auth for ZAP (`ZAP_AUTH_HEADER_VALUE`)
- Decode the base64 encoded value of the header
- Navigate to `/authenticated/challenge37` and fill in the username and password as you retrieved from the previous step.

2. Use CURL to get the secret
- First go to the https://github.com/OWASP/wrongsecrets/blob/master/.github/workflows/dast-zap-test.yml[Github Workflow]
- Find the environment variable with which we configure basic auth for ZAP (`ZAP_AUTH_HEADER_VALUE`)
- In your terminal, do `curl <domain where this is run>/authenticated/challenge37 -H "<basic_auth header of previous step>"` where `<basic_auth header of previous step>` is the value of the `ZAP_AUTH_HEADER_VALUE` you found.
commjoen marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 5 additions & 0 deletions src/main/resources/explanations/challenge37_reason.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*Why we need to be careful with security credentials in CI/CD*

People who can access the configuration of your security checks in your CI/CD environment, can easily get access to the credentials. These credentials can then be used for anything and not just for security checks.

This is why security should be very careful in managing their own secrets: just because the credential is used by (a) security (tool), does not mean it should be secured less ;-).
commjoen marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import org.springframework.web.client.ResourceAccessException;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Import({ConventionPortMapper.class, PortConfiguration.class})
@Import(ConventionPortMapper.class)
class HerokuWebSecurityConfigTest {

@LocalServerPort private int port;
Expand Down
27 changes: 0 additions & 27 deletions src/test/java/org/owasp/wrongsecrets/PortConfiguration.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.owasp.wrongsecrets.challenges.docker;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.owasp.wrongsecrets.ScoreCard;

public class Challenge37Test {
@Mock private ScoreCard scoreCard;

@Test
void spoilerShouldGiveAnswer() {
var challenge = new Challenge37(scoreCard);
Assertions.assertThat(challenge.spoiler().solution()).isNotEmpty();
Assertions.assertThat(challenge.answerCorrect(challenge.spoiler().solution())).isTrue();
}

@Test
void incorrectAnswerShouldNotSolveChallenge() {
var challenge = new Challenge37(scoreCard);
Assertions.assertThat(challenge.solved("wrong answer")).isFalse();
}
}
Loading