Skip to content

Commit

Permalink
Merge pull request #1189 from nwolniak/command-injection-vault-template
Browse files Browse the repository at this point in the history
New Challenge - Vault Template Injection
  • Loading branch information
commjoen committed Jan 17, 2024
2 parents 877acfb + bb46ad7 commit a25e703
Show file tree
Hide file tree
Showing 16 changed files with 171 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/scripts/.bash_history
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ rm -rf jdk-18_linux-x64_bin.deb
git rebase -i main
git rebase -i master
git stash
export tempPassword="1f94QXGi8zGUNiT91bconrnPLl44bCY59Y8itGyN6Yg="
export tempPassword="dWTZkr5BPvnJYw+8sXtwQX8bCVTtsCrAL//mgrzeYTY="
mvn run tempPassword
k6
npx k6
Expand Down
6 changes: 6 additions & 0 deletions k8s-vault-minkube-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ kubectl exec vault-0 -n vault -- vault secrets enable -path=secret kv-v2
echo "Putting a secret in"
kubectl exec vault-0 -n vault -- vault kv put secret/secret-challenge vaultpassword.password="$(openssl rand -base64 16)"

echo "Putting a challenge key in"
kubectl exec vault-0 -n vault -- vault kv put secret/injected vaultinjected.value="$(openssl rand -base64 16)"

echo "Putting a subkey issue in"
kubectl exec vault-0 -n vault -- vault kv put secret/wrongsecret aaaauser."$(openssl rand -base64 8)"="$(openssl rand -base64 16)"

Expand Down Expand Up @@ -115,6 +118,9 @@ path "secret/data/wrongsecret" {
path "secret/data/application" {
capabilities = ["read"]
}
path "secret/data/injected" {
capabilities = ["read"]
}
EOF'

kubectl exec vault-0 -n vault -- /bin/sh -c 'vault policy write standard_sre - <<EOF
Expand Down
3 changes: 3 additions & 0 deletions k8s/helm-vault-values.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ server:
affinity:
ha:
enabled: true

injector:
enabled: true
18 changes: 17 additions & 1 deletion k8s/secret-challenge-vault-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ spec:
type: RollingUpdate
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/tls-skip-verify: 'true'
vault.hashicorp.com/agent-inject-status: "update"
vault.hashicorp.com/namespace: "default"
vault.hashicorp.com/log-level: debug
vault.hashicorp.com/agent-inject-secret-challenge46: "secret/data/injected"
vault.hashicorp.com/agent-inject-template-challenge46: |
{{ with secret "/secret/data/injected" }}
{{ range $k, $v := .Data.data }}
{{ printf "%s=%s" $k $v }}
{{ end }}
{{ end }}
vault.hashicorp.com/role: "secret-challenge"
labels:
app: secret-challenge
name: secret-challenge
Expand All @@ -28,9 +42,11 @@ spec:
fsGroup: 2000
runAsGroup: 2000
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
serviceAccountName: vault
containers:
- image: jeroenwillemsen/wrongsecrets:1.8.2-k8s-vault
- image: jeroenwillemsen/wrongsecrets:vaultinjection-2-test-k8s-vault
imagePullPolicy: IfNotPresent
name: secret-challenge
securityContext:
Expand Down
6 changes: 6 additions & 0 deletions scripts/install-vault.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ kubectl exec vault-0 -n vault -- vault secrets enable -path=secret kv-v2
echo "Putting a secret in"
kubectl exec vault-0 -n vault -- vault kv put secret/secret-challenge vaultpassword.password="$(openssl rand -base64 16)"

echo "Putting a challenge key in"
kubectl exec vault-0 -n vault -- vault kv put secret/injected vaultinjected.value="$(openssl rand -base64 16)"

echo "Putting a subkey issue in"
kubectl exec vault-0 -n vault -- vault kv put secret/wrongsecret aaaauser."$(openssl rand -base64 8)"="$(openssl rand -base64 16)"

Expand Down Expand Up @@ -81,6 +84,9 @@ path "secret/data/wrongsecret" {
path "secret/data/application" {
capabilities = ["read"]
}
path "secret/data/injected" {
capabilities = ["read"]
}
EOF'

kubectl exec vault-0 -n vault -- /bin/sh -c 'vault policy write standard_sre - <<EOF
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.owasp.wrongsecrets;

import org.owasp.wrongsecrets.challenges.kubernetes.Vaultinjected;
import org.owasp.wrongsecrets.challenges.kubernetes.Vaultpassword;
import org.owasp.wrongsecrets.definitions.ChallengeDefinitionsConfiguration;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -11,7 +12,7 @@
import org.springframework.context.annotation.ScopedProxyMode;

@SpringBootApplication
@EnableConfigurationProperties(Vaultpassword.class)
@EnableConfigurationProperties({Vaultpassword.class, Vaultinjected.class})
public class WrongSecretsApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.owasp.wrongsecrets.challenges.kubernetes;

import com.google.common.base.Strings;
import org.owasp.wrongsecrets.challenges.FixedAnswerChallenge;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/** This challenge is about having a secrets injected via Vault template. */
@Component
public class Challenge46 extends FixedAnswerChallenge {

private final Vaultinjected vaultinjected;
private final String mockedAnswer;

public Challenge46(Vaultinjected vaultinjected, @Value("${vaultinjected}") String mockedAnswer) {
this.vaultinjected = vaultinjected;
this.mockedAnswer = mockedAnswer;
}

@Override
public String getAnswer() {
return vaultinjected != null && !Strings.isNullOrEmpty(vaultinjected.getValue())
? vaultinjected.getValue()
: mockedAnswer;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.owasp.wrongsecrets.challenges.kubernetes;

import org.springframework.boot.context.properties.ConfigurationProperties;

/** Class used to get value from vault using the springboot cloud integration with vault. */
@ConfigurationProperties("vaultinjected")
public class Vaultinjected {

private String value;

public void setValue(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
5 changes: 3 additions & 2 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ CHALLENGE33=if_you_see_this_please_use_k8s
ARG_BASED_PASSWORD=if_you_see_this_please_use_docker_instead
DOCKER_ENV_PASSWORD=if_you_see_this_please_use_docker_instead
vaultpassword=if_you_see_this_please_use_K8S_and_Vault
vaultinjected=if_you_see_this_please_use_K8S_and_Vault
spring.cloud.vault.uri=https://tobediefined.org
spring.cloud.vault.authentication=NONE
spring.cloud.vault.role=none
Expand Down Expand Up @@ -75,7 +76,7 @@ management.endpoints.web.exposure.include=auditevents,info,health
#---
spring.config.activate.on-profile=kubernetes-vault
wrongsecretvalue=wrongsecret
spring.config.import=vault://secret/secret-challenge
spring.config.import=vault://secret/secret-challenge,vault://secret/injected
spring.application.name=secret-challenge
spring.cloud.vault.scheme=https://tobediefined.org
spring.cloud.vault.enabled=true
Expand All @@ -92,7 +93,7 @@ asciidoctor.enabled=true
#---
spring.config.activate.on-profile=local-vault
wrongsecretvalue=wrongsecret
spring.config.import=vault://secret/secret-challenge
spring.config.import=vault://secret/secret-challenge,vault://secret/injected
spring.application.name=secret-challenge
spring.cloud.vault.scheme=http
spring.cloud.vault.enabled=true
Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/explanations/challenge46.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
=== HashiCorp Vault Template Injection

Vault template injection via agent injection typically involves injecting a sidecar container,
known as the Vault Agent, alongside your main application container.
The Vault Agent is responsible for interacting with HashiCorp Vault to retrieve secrets and inject them into the application's runtime environment.

Can you find secret injected into application environment?
8 changes: 8 additions & 0 deletions src/main/resources/explanations/challenge46_hint.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
This challenge can be solved using the following steps:

1. Run `kubectl get pods -A` and find secret-challenge-xxx pod name
2. Run `kubectl exec secret-challenge-xxx -c secret-challenge -n default -- cat vault/secrets/challenge46` where `xxx` is the rest of the randomly generated pod name.
to print injected secrets from vault.
Note: if you are running this on a hosted environment, where you do not have access to the Kubernetes cluster, ask the organizer of the hosted solution to execute the commands for you and return the results.
16 changes: 16 additions & 0 deletions src/main/resources/explanations/challenge46_reason.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
*Why Vault Template Injection is not always a good idea?*

While Vault agent injection via templates can be a convenient way to manage secrets in certain scenarios,
there are situations where it might not be the best approach.

Templates might accidentally expose sensitive information in logs or temporary files.
If not properly configured, secrets could end up in places where they are accessible by unauthorized users or processes.

Let's consider an example involving a template injection attack in a scripted language like PHP:

. Imagine a scenario where PHP application uses a template with sensitive information
* where template can look like this: $password = "'; system('rm -rf /'); //"
. When the template is processed it can become:
* $connection = "password='; system('rm -rf /'); //"

To prevent such issues its crucial to ensure that the values retrieved from Vault are properly validated.
14 changes: 14 additions & 0 deletions src/main/resources/wrong-secrets-configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -738,3 +738,17 @@ configurations:
ctf:
enabled: true
missing_environment: "explanations/missing_vault.adoc"

- name: Challenge 46
short-name: "challenge-46"
sources:
- class-name: "org.owasp.wrongsecrets.challenges.kubernetes.Challenge46"
explanation: "explanations/challenge46.adoc"
hint: "explanations/challenge46_hint.adoc"
reason: "explanations/challenge46_reason.adoc"
environments: [ *k8s_vault ]
difficulty: *expert
category: *vault
ctf:
enabled: true
missing_environment: "explanations/missing_vault.adoc"
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"SPECIAL_K8S_SECRET=test5",
"SPECIAL_SPECIAL_K8S_SECRET=test6",
"vaultpassword=test7",
"vaultinjected=test46",
"secretmountpath=nothere",
"default_aws_value_challenge_9=ACTUAL_ANSWER_CHALLENGE9",
"default_aws_value_challenge_10=ACTUAL_ANSWER_CHALLENGE10",
Expand Down Expand Up @@ -52,6 +53,10 @@ void shouldSpoilExercises() throws Exception {
@Test
void shouldNotShowDisabledChallengeAnywhere() throws Exception {
for (var challenge : challenges.getChallengeDefinitions()) {
var shortname = challenge.name().shortName();
if (shortname.contains("46")) {
continue;
}
mvc.perform(get("/challenge/%s".formatted(challenge.name().shortName())))
.andExpect(status().isOk())
.andExpect(content().string(not(containsString("This challenge has been disabled."))));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.owasp.wrongsecrets.challenges.kubernetes;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

public class Challenge46Test {

@Test
void spoilerShouldGiveAnswerWithVault() {
var vaultInjected = new Vaultinjected();
vaultInjected.setValue("answer");
var challenge = new Challenge46(vaultInjected, "");
assertThat(challenge.spoiler().solution()).isNotEmpty();
assertThat(challenge.answerCorrect(challenge.spoiler().solution())).isTrue();
}

@Test
void spoilerShouldGiveAnswer() {
var vaultInjected = new Vaultinjected();
vaultInjected.setValue("");
var challenge = new Challenge46(vaultInjected, "answer");
assertThat(challenge.spoiler().solution()).isEqualTo("answer");
assertThat(challenge.answerCorrect(challenge.spoiler().solution())).isTrue();
}

@Test
void incorrectAnswerShouldNotSolveChallenge() {
var vaultInjected = new Vaultinjected();
vaultInjected.setValue("answer");
var challenge = new Challenge46(vaultInjected, "");
assertThat(challenge.answerCorrect("wrong answer")).isFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"SPECIAL_K8S_SECRET=test5",
"SPECIAL_SPECIAL_K8S_SECRET=test6",
"vaultpassword=test7",
"vaultinjected=test46",
"secretmountpath=nothere"
},
classes = WrongSecretsApplication.class)
Expand Down Expand Up @@ -55,7 +56,8 @@ void shouldNotShowDisabledChallengeAnywhere() throws Exception {
|| shortname.contains("10")
|| shortname.contains("11")
|| shortname.contains("44")
|| shortname.contains("45")) {
|| shortname.contains("45")
|| shortname.contains("46")) {
continue;
}
mvc.perform(get("/challenge/%s".formatted(challenge.name().shortName())))
Expand All @@ -81,7 +83,8 @@ void shouldEnableK8sExercises() throws Exception {
.andExpect(content().string(not(containsString("challenge-6_disabled-link"))))
.andExpect(
content().string(containsString("challenge-7_disabled-link"))) // vault is not visible
.andExpect(content().string(not(containsString("challenge-33_disabled-link"))));
.andExpect(content().string(not(containsString("challenge-33_disabled-link"))))
.andExpect(content().string(containsString("challenge-46_disabled-link")));
}

@Test
Expand Down

0 comments on commit a25e703

Please sign in to comment.