Skip to content

Commit dac9b45

Browse files
authored
Merge pull request #2464 from OWASP/copilot/use-mcp-server-google-service-account
Add Challenge 62: MCP privilege escalation via Google Service Account on Google Drive
2 parents 9de1dcf + 8967c75 commit dac9b45

34 files changed

+1304
-78
lines changed

.github/scripts/.bash_history

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ rm -rf jdk-18_linux-x64_bin.deb
347347
git rebase -i main
348348
git rebase -i master
349349
git stash
350-
export tempPassword="mO5vAFh3aK4tBr54zX8P9BS8LpT96gJWcKL5r0yZxhE="
350+
export tempPassword="xlhyzFAFKJnjmzPtnM+q9ezt0xiZO5seUT+f4t/46SY="
351351
mvn run tempPassword
352352
k6
353353
npx k6

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ src/main/resources/executables/wrongsecrets-dotnet*
8282
k8s/challenge53/executables/wrongsecrets-challenge53-c
8383
k8s/challenge53/executables/wrongsecrets-challenge53-c*
8484

85+
# Challenge 62
86+
challenge62-key.json
87+
8588
# Node JS
8689
js/node/
8790
js/node_modules/

.lycheeignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,6 @@ https://github.com/topics/secrets-detection
3333

3434
# Helm docs are flaky in CI (connection resets)
3535
https://helm.sh/docs/intro/install/
36+
37+
# Google Docs require authentication and always return 401 to link checkers
38+
https://docs.google.com/document/*

Dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
FROM bellsoft/liberica-openjre-debian:25-cds AS builder
22
WORKDIR /builder
33

4-
ARG argBasedVersion="1.13.1"
4+
ARG argBasedVersion="1.13.2alpha1"
55

66
COPY --chown=wrongsecrets target/wrongsecrets-${argBasedVersion}-SNAPSHOT.jar application.jar
77
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
@@ -19,6 +19,10 @@ ENV DOCKER_ENV_PASSWORD="This is it"
1919
ENV AZURE_KEY_VAULT_ENABLED=false
2020
ENV CHALLENGE59_SLACK_WEBHOOK_URL=$challenge59_webhook_url
2121
ENV WRONGSECRETS_MCP_SECRET=MCPStolenSecret42!
22+
ARG GOOGLE_SERVICE_ACCOUNT_KEY="if_you_see_this_configure_the_google_service_account_properly"
23+
ARG GOOGLE_DRIVE_DOCUMENT_ID="1PlZkwEd7GouyY4cdOxBuczm6XumQeuZN31LR2BXRgPs"
24+
ENV GOOGLE_SERVICE_ACCOUNT_KEY=$GOOGLE_SERVICE_ACCOUNT_KEY
25+
ENV GOOGLE_DRIVE_DOCUMENT_ID=$GOOGLE_DRIVE_DOCUMENT_ID
2226
ENV SPRINGDOC_UI=false
2327
ENV SPRINGDOC_DOC=false
2428
ENV BASTIONHOSTPATH="/home/wrongsecrets/.ssh"

Dockerfile.web

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
FROM jeroenwillemsen/wrongsecrets:1.13.1-no-vault
2-
ARG argBasedVersion="1.13.1-no-vault"
1+
FROM jeroenwillemsen/wrongsecrets:1.13.2alpha1-no-vault
2+
ARG argBasedVersion="1.13.2alpha1-no-vault"
33
ARG spring_profile="without-vault"
44
ARG CANARY_URLS="http://canarytokens.com/terms/about/s7cfbdakys13246ewd8ivuvku/post.jsp,http://canarytokens.com/terms/about/y0all60b627gzp19ahqh7rl6j/post.jsp"
55
ARG CTF_ENABLED=false
@@ -39,6 +39,10 @@ ENV default_aws_value_challenge_11=$CHALLENGE_11_VALUE
3939
ENV BASTIONHOSTPATH="/home/wrongsecrets/.ssh"
4040
ENV PROJECTSPECPATH="/var/helpers/project-specification.mdc"
4141
ENV funnybunny="This is a funny bunny"
42+
ARG GOOGLE_SERVICE_ACCOUNT_KEY="if_you_see_this_configure_the_google_service_account_properly"
43+
ARG GOOGLE_DRIVE_DOCUMENT_ID="1PlZkwEd7GouyY4cdOxBuczm6XumQeuZN31LR2BXRgPs"
44+
ENV GOOGLE_SERVICE_ACCOUNT_KEY=$GOOGLE_SERVICE_ACCOUNT_KEY
45+
ENV GOOGLE_DRIVE_DOCUMENT_ID=$GOOGLE_DRIVE_DOCUMENT_ID
4246
# Keep memory usage within Heroku dyno limits (512MB dyno).
4347
# Hard cap heap to 250M, metaspace to 60M, disable expensive GC, exit on OOM immediately.
4448
ENV JAVA_TOOL_OPTIONS="-Xmx250M -Xms128M -XX:MetaspaceSize=40M -XX:MaxMetaspaceSize=60M -XX:CompressedClassSpaceSize=32M -XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof"

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,12 @@ docker run -p 8080:8080 -p 8090:8090 ghcr.io/owasp/wrongsecrets/wrongsecrets-mas
161161
⚠️ **Warning**: This is a development version built from the latest master branch and may contain experimental features or instabilities.
162162

163163
**📝 Note on Ports:**
164-
- Port **8080**: Main application (challenges 0-61)
164+
- Port **8080**: Main application (challenges 0-62)
165165
- Port **8090**: MCP server (required for Challenge 60)
166166

167+
**📝 Note on Challenge 62 (Google Drive MCP):**
168+
Challenge 62 requires a Google Service Account to be configured for full functionality. See [docs/CHALLENGE62_GOOGLE_DRIVE_SETUP.md](docs/CHALLENGE62_GOOGLE_DRIVE_SETUP.md) for setup instructions. Without configuration, the challenge will show a placeholder message.
169+
167170
Now you can try to find the secrets by means of solving the challenge offered at the links below
168171
<details>
169172
<summary>all the links for docker challenges (click triangle to open the block).
@@ -218,6 +221,7 @@ Now you can try to find the secrets by means of solving the challenge offered at
218221
- [localhost:8080/challenge/challenge-59](http://localhost:8080/challenge/challenge-59)
219222
- [localhost:8080/challenge/challenge-60](http://localhost:8080/challenge/challenge-60)
220223
- [localhost:8080/challenge/challenge-61](http://localhost:8080/challenge/challenge-61)
224+
- [localhost:8080/challenge/challenge-62](http://localhost:8080/challenge/challenge-62)
221225
</details>
222226

223227
Note that these challenges are still very basic, and so are their explanations. Feel free to file a PR to make them look
@@ -246,7 +250,7 @@ If you want to host WrongSecrets on Railway, you can do so by deploying [this on
246250

247251
## Basic K8s exercise
248252

249-
_Can be used for challenges 0-6, 8, 12-43, 48-61_
253+
_Can be used for challenges 0-6, 8, 12-43, 48-62_
250254

251255
### Minikube based
252256

@@ -341,7 +345,7 @@ This is because if you run the start script again it will replace the secret in
341345

342346
## Cloud Challenges
343347

344-
_Can be used for challenges 0-61_
348+
_Can be used for challenges 0-62_
345349

346350
**READ THIS**: Given that the exercises below contain IAM privilege escalation exercises,
347351
never run this on an account which is related to your production environment or can influence your account-over-arching

aws/k8s/secret-challenge-vault-deployment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ spec:
5858
volumeAttributes:
5959
secretProviderClass: "wrongsecrets-aws-secretsmanager"
6060
containers:
61-
- image: jeroenwillemsen/wrongsecrets:1.13.1-k8s-vault
61+
- image: jeroenwillemsen/wrongsecrets:1.13.2alpha1-k8s-vault
6262
imagePullPolicy: IfNotPresent
6363
name: secret-challenge
6464
command: ["/bin/sh"]

azure/k8s/secret-challenge-vault-deployment.yml.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ spec:
6161
volumeAttributes:
6262
secretProviderClass: "azure-wrongsecrets-vault"
6363
containers:
64-
- image: jeroenwillemsen/wrongsecrets:1.13.1-k8s-vault
64+
- image: jeroenwillemsen/wrongsecrets:1.13.2alpha1-k8s-vault
6565
imagePullPolicy: IfNotPresent
6666
name: secret-challenge
6767
command: ["/bin/sh"]
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Challenge 62: Google Service Account Setup Guide
2+
3+
This guide explains how to configure Challenge 62, which demonstrates privilege escalation via an MCP (Model Context Protocol) server using a Google Service Account to access restricted Google Drive documents.
4+
5+
## Overview
6+
7+
Challenge 62 shows how an MCP server configured with an overly-privileged Google Service Account allows callers to read Google Drive documents they are not directly authorized to access. The service account acts as a privilege escalation proxy.
8+
9+
## Runtime Behavior Notes
10+
11+
- The challenge answer is parsed from document content between `<secret>` and `</secret>`.
12+
- The parsed answer is cached once in `Challenge62` and reused for answer validation.
13+
- `Challenge62McpController` caches Drive documents to reduce repeated API calls.
14+
- Cache policy: always retain the configured default document (`GOOGLE_DRIVE_DOCUMENT_ID`) plus up to 20 additional document ids.
15+
16+
## Prerequisites
17+
18+
- A Google Cloud project
19+
- Owner or Editor role on the Google Cloud project (to create service accounts)
20+
- A Google Drive document containing a secret
21+
22+
## Step 1: Create a Google Cloud Project (if needed)
23+
24+
If you don't have a Google Cloud project:
25+
26+
```bash
27+
gcloud projects create YOUR_PROJECT_ID --name="WrongSecrets Challenge 62"
28+
gcloud config set project YOUR_PROJECT_ID
29+
```
30+
31+
## Step 2: Enable the Google Drive API
32+
33+
```bash
34+
gcloud services enable drive.googleapis.com
35+
```
36+
37+
## Step 3: Create a Service Account
38+
39+
```bash
40+
gcloud iam service-accounts create wrongsecrets-challenge62 \
41+
--display-name="WrongSecrets Challenge 62 Drive Reader" \
42+
--description="Service account for WrongSecrets Challenge 62 - demonstrates MCP privilege escalation"
43+
```
44+
45+
## Step 4: Create and Download a Service Account Key
46+
47+
```bash
48+
gcloud iam service-accounts keys create challenge62-key.json \
49+
--iam-account=wrongsecrets-challenge62@YOUR_PROJECT_ID.iam.gserviceaccount.com
50+
```
51+
52+
**⚠️ Security Warning**: Service account key files are sensitive credentials. Handle them carefully:
53+
- Do not commit key files to version control
54+
- Delete the key file after encoding it
55+
- Rotate keys regularly
56+
57+
## Step 5: Create a Google Drive Document with the Secret
58+
59+
1. Go to [Google Drive](https://drive.google.com) and create a new Google Doc
60+
2. Add your challenge secret as the document content (e.g., `my_wrongsecrets_challenge62_answer`)
61+
- Recommended format: `<secret>my_wrongsecrets_challenge62_answer</secret>`
62+
3. Note the document ID from the URL:
63+
- URL format: `https://docs.google.com/document/d/DOCUMENT_ID/edit`
64+
- Copy the `DOCUMENT_ID` part
65+
66+
## Step 6: Share the Document with the Service Account
67+
68+
Share the Google Drive document with the service account's email address:
69+
70+
1. Open the document in Google Drive
71+
2. Click **Share**
72+
3. Add the service account email: `wrongsecrets-challenge62@YOUR_PROJECT_ID.iam.gserviceaccount.com`
73+
4. Set the permission to **Viewer**
74+
5. Click **Send**
75+
76+
Alternatively, use the Drive API via the CLI:
77+
```bash
78+
# Get the document ID from the URL
79+
DOCUMENT_ID="your_document_id_here"
80+
SA_EMAIL="wrongsecrets-challenge62@YOUR_PROJECT_ID.iam.gserviceaccount.com"
81+
82+
# Share using the Drive API (requires OAuth2 token)
83+
curl -X POST "https://www.googleapis.com/drive/v3/files/${DOCUMENT_ID}/permissions" \
84+
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
85+
-H "Content-Type: application/json" \
86+
-d "{\"role\": \"reader\", \"type\": \"user\", \"emailAddress\": \"${SA_EMAIL}\"}"
87+
```
88+
89+
## Step 7: Encode the Service Account Key
90+
91+
Base64-encode the service account key file:
92+
93+
```bash
94+
# On Linux/macOS:
95+
SERVICE_ACCOUNT_KEY_B64=$(base64 -w 0 challenge62-key.json)
96+
97+
# On macOS (if the above doesn't work):
98+
SERVICE_ACCOUNT_KEY_B64=$(base64 -i challenge62-key.json | tr -d '\n')
99+
100+
echo "Your base64-encoded key (use this as GOOGLE_SERVICE_ACCOUNT_KEY):"
101+
echo "${SERVICE_ACCOUNT_KEY_B64}"
102+
```
103+
104+
## Step 8: Configure WrongSecrets
105+
106+
Set the following environment variables when **running** WrongSecrets. These must be provided at container start time — do **not** bake real credentials into the image via `--build-arg`, as that embeds them in the image layer history.
107+
108+
| Variable | Description | Default (placeholder) | Example override |
109+
|----------|-------------|----------------------|-----------------|
110+
| `GOOGLE_SERVICE_ACCOUNT_KEY` | Base64-encoded service account JSON key | `if_you_see_this_configure_the_google_service_account_properly` | `eyJ0eXBlIjoic2VydmljZV9hY2...` |
111+
| `GOOGLE_DRIVE_DOCUMENT_ID` | Google Drive document ID | `1PlZkwEd7GouyY4cdOxBuczm6XumQeuZN31LR2BXRgPs` | your document id |
112+
| `WRONGSECRETS_MCP_GOOGLEDRIVE_SECRET` | *(optional)* Static override — skips live Drive fetch | *(none — live fetch used)* | `my_wrongsecrets_challenge62_answer` |
113+
114+
> **Why runtime-only?**
115+
> The `Dockerfile` and `Dockerfile.web` ship harmless placeholder defaults via `ENV`. Real credentials should only be injected at `docker run` time so they never appear in image layers or build logs.
116+
117+
### Running with Docker (explicit values)
118+
119+
```bash
120+
export SERVICE_ACCOUNT_KEY_B64=$(base64 -i challenge62-key.json | tr -d '\n')
121+
export DOCUMENT_ID="your_document_id_here"
122+
123+
docker run -p 8080:8080 -p 8090:8090 \
124+
-e GOOGLE_SERVICE_ACCOUNT_KEY="${SERVICE_ACCOUNT_KEY_B64}" \
125+
-e GOOGLE_DRIVE_DOCUMENT_ID="${DOCUMENT_ID}" \
126+
ghcr.io/owasp/wrongsecrets/wrongsecrets:latest-no-vault
127+
```
128+
129+
### Running with Docker (inherit from host shell)
130+
131+
If the variables are already exported in your shell, pass them through without a value — Docker inherits from the host:
132+
133+
```bash
134+
export GOOGLE_SERVICE_ACCOUNT_KEY="${SERVICE_ACCOUNT_KEY_B64}"
135+
export GOOGLE_DRIVE_DOCUMENT_ID="your_document_id_here"
136+
137+
docker run -p 8080:8080 -p 8090:8090 \
138+
-e GOOGLE_SERVICE_ACCOUNT_KEY \
139+
-e GOOGLE_DRIVE_DOCUMENT_ID \
140+
ghcr.io/owasp/wrongsecrets/wrongsecrets:latest-no-vault
141+
```
142+
143+
### Running with Docker using an env file
144+
145+
Create a `.env` file (add it to `.gitignore`):
146+
147+
```bash
148+
GOOGLE_SERVICE_ACCOUNT_KEY=<base64_encoded_key>
149+
GOOGLE_DRIVE_DOCUMENT_ID=<document_id>
150+
```
151+
152+
Then run:
153+
154+
```bash
155+
docker run -p 8080:8080 -p 8090:8090 \
156+
--env-file .env \
157+
ghcr.io/owasp/wrongsecrets/wrongsecrets:latest-no-vault
158+
```
159+
160+
### Running with Spring Boot (local development)
161+
162+
Set environment variables in your shell before running:
163+
164+
```bash
165+
export GOOGLE_SERVICE_ACCOUNT_KEY="${SERVICE_ACCOUNT_KEY_B64}"
166+
export GOOGLE_DRIVE_DOCUMENT_ID="your_document_id"
167+
./mvnw spring-boot:run
168+
```
169+
170+
Or add them to a **local-only** properties file that is not committed to version control:
171+
172+
```properties
173+
# application-local.properties (keep out of git)
174+
GOOGLE_SERVICE_ACCOUNT_KEY=<base64_encoded_key>
175+
GOOGLE_DRIVE_DOCUMENT_ID=<document_id>
176+
```
177+
178+
## Step 9: Clean Up the Key File
179+
180+
After encoding the key, delete the local key file:
181+
182+
```bash
183+
rm challenge62-key.json
184+
```
185+
186+
## Using the Default OWASP Document (for testing)
187+
188+
The default document ID configured in the application is the OWASP WrongSecrets Google Drive document:
189+
- Document: https://docs.google.com/document/d/1PlZkwEd7GouyY4cdOxBuczm6XumQeuZN31LR2BXRgPs/edit
190+
191+
To use this document, your service account must have been granted read access to it by the OWASP WrongSecrets maintainers. For your own deployment, we recommend creating your own document as described above.
192+
193+
## Security Notes
194+
195+
1. **This is intentionally insecure for educational purposes**: In a real system, you should always authenticate and authorize MCP callers before granting access to external resources.
196+
197+
2. **Least Privilege**: The service account used in this challenge demonstrates what happens when you violate least privilege. In production, ensure service accounts only have the minimum permissions necessary.
198+
199+
3. **Never use production credentials**: Do not use service accounts that have access to production data for this challenge.
200+
201+
4. **Key rotation**: Regularly rotate service account keys to limit the window of exposure if a key is compromised.
202+
203+
## Verification
204+
205+
After configuration, verify the challenge works by calling the MCP endpoint:
206+
207+
```bash
208+
curl -s -X POST http://localhost:8080/mcp62 \
209+
-H 'Content-Type: application/json' \
210+
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"read_google_drive_document","arguments":{}}}'
211+
```
212+
213+
The response should contain the document content with your secret.
214+
215+
## Tests and Code References
216+
217+
- Main challenge logic: `src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge62.java`
218+
- MCP controller and cache logic: `src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge62McpController.java`
219+
- Challenge tests: `src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge62Test.java`
220+
- MCP controller tests: `src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge62McpControllerTest.java`

docs/VERSION_MANAGEMENT.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ The project maintains version consistency between:
1212
## Version Schema
1313

1414
```
15-
pom.xml version: 1.13.1-SNAPSHOT
16-
Dockerfile version: 1.13.1
17-
Dockerfile.web version: 1.13.1-no-vault
15+
pom.xml version: 1.13.2alpha1-SNAPSHOT
16+
Dockerfile version: 1.13.2alpha1
17+
Dockerfile.web version: 1.13.2alpha1-no-vault
1818
```
1919

2020
## Automated Solutions

0 commit comments

Comments
 (0)