Skip to content

Commit 508799b

Browse files
committed
Add GraphQL Galaxy lab with Docker configuration and application code
- Introduced a new service `graphql-galaxy` in `docker-compose.yml` with specific network settings and port mapping. - Added application code for a GraphQL API in `additional-labs/GraphQL/app.py`, demonstrating vulnerabilities such as GraphQL Introspection and IDOR. - Created a Dockerfile for the GraphQL lab to facilitate containerization. - Updated `README.md` in the GraphQL lab directory to outline the lab's objectives, installation instructions, and challenges. - Revised main `README.md` to include the new lab's details, including its IP address and port.
1 parent aad4647 commit 508799b

10 files changed

Lines changed: 419 additions & 2 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,15 @@ Custom-built labs focusing on specific vulnerability categories. These labs are
7272
| **hydra-nexus** | 10.6.6.30 | 5010 | Multi-Vulnerability Gauntlet (SQLi, XSS, IDOR, XXE, etc.) |
7373
| **phantom-script** | 10.6.6.31 | 5011 | Cross-Site Scripting (Reflected, Stored, DOM) |
7474
| **trojan-relay** | 10.6.6.32 | 5012 | Server-Side Request Forgery (SSRF) |
75-
| **sqli-breach** | 10.6.6.33 | 5000 | SQL Injection |
75+
| **sqli-breach** | 10.6.6.33 | 5001 | SQL Injection |
7676
| **shell-inject** | 10.6.6.34 | 5002 | OS Command Injection |
7777
| **maze-walker** | 10.6.6.35 | 5003 | Path/Directory Traversal |
7878
| **entity-smuggler** | 10.6.6.36 | 5013 | XML External Entity (XXE) Injection |
7979
| **token-tower** | 10.6.6.40 | 5020 | JWT Vulnerability |
8080
| **render-reign** | 10.6.6.41 | 5021 | Server-Side Template Injection |
8181
| **deserial-gate** | 10.6.6.42 | 5022 | Insecure Deserialization |
8282
| **redis-rogue** | 10.6.6.43 | - | DEF CON 31 Challenge |
83+
| **graphql-galaxy** | 10.6.6.44 | 5023 | GraphQL Introspection, IDOR |
8384

8485
---
8586

@@ -133,7 +134,8 @@ docker compose build --no-cache [container_name]
133134
│ ├── token-tower 10.6.6.40 │
134135
│ ├── render-reign 10.6.6.41 │
135136
│ ├── deserial-gate 10.6.6.42 │
136-
│ └── redis-rogue 10.6.6.43 │
137+
│ ├── redis-rogue 10.6.6.43 │
138+
│ └── graphql-galaxy 10.6.6.44 │
137139
└─────────────────────────────────────────────────────────────┘
138140
```
139141

additional-labs/GraphQL/Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3.9-slim
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt .
6+
RUN pip install --no-cache-dir -r requirements.txt
7+
8+
COPY . .
9+
10+
EXPOSE 5023
11+
12+
CMD ["python", "app.py"]
13+

additional-labs/GraphQL/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# GraphQL Galaxy: API Hacking Lab
2+
3+
**Lab Name:** `graphql-galaxy`
4+
**Vulnerability:** GraphQL Introspection, Information Disclosure, IDOR
5+
6+
## Description
7+
This lab simulates a futuristic communications hub that exposes a GraphQL API. The API is misconfigured to allow full schema introspection, revealing sensitive fields and internal types. Additionally, the resolvers lack proper authorization checks, allowing users to access data they shouldn't (IDOR).
8+
9+
## Learning Objectives
10+
- Understand GraphQL Introspection and how to use it for reconnaissance.
11+
- Identify sensitive fields in a GraphQL schema.
12+
- Exploit IDOR vulnerabilities in GraphQL resolvers.
13+
- Learn how to use GraphiQL to interact with the API.
14+
15+
## Installation
16+
This lab is part of the WebSploit Labs framework.
17+
To run it individually:
18+
```bash
19+
docker build -t graphql-galaxy .
20+
docker run -p 5023:5023 graphql-galaxy
21+
```
22+
23+
## Challenge
24+
1. **Reconnaissance:** Use the GraphiQL interface to inspect the available queries and types.
25+
2. **Introspection:** Find the hidden `api_token` field in the `User` type.
26+
3. **Exploitation:** Query the `user` field with the ID of the administrator (`1`) and request the `api_token`.
27+
4. **Flag:** The flag is the value of the administrator's `api_token`.
28+

additional-labs/GraphQL/app.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env python3
2+
import graphene
3+
from flask import Flask
4+
from flask_graphql import GraphQLView
5+
6+
# --- Mock Database ---
7+
users_db = [
8+
{
9+
"id": "1",
10+
"username": "admin",
11+
"email": "admin@galactic.gov",
12+
"api_token": "FLAG{GRAPHQL_INTROSPECTION_MASTER}",
13+
"is_admin": True,
14+
"notes": "System Administrator with full access."
15+
},
16+
{
17+
"id": "2",
18+
"username": "skywalker",
19+
"email": "luke@rebel.alliance",
20+
"api_token": "force-user-001",
21+
"is_admin": False,
22+
"notes": "Pilot. Keep an eye on him."
23+
},
24+
{
25+
"id": "3",
26+
"username": "vader",
27+
"email": "darth@empire.gov",
28+
"api_token": "dark-side-999",
29+
"is_admin": False,
30+
"notes": "Lord Vader."
31+
}
32+
]
33+
34+
# --- GraphQL Schema ---
35+
36+
class User(graphene.ObjectType):
37+
id = graphene.ID()
38+
username = graphene.String()
39+
email = graphene.String()
40+
api_token = graphene.String(description="The API Token for the user. Restricted access.")
41+
is_admin = graphene.Boolean()
42+
notes = graphene.String()
43+
44+
class SystemInfo(graphene.ObjectType):
45+
version = graphene.String()
46+
status = graphene.String()
47+
debug_mode = graphene.Boolean()
48+
49+
class Query(graphene.ObjectType):
50+
user = graphene.Field(User, id=graphene.ID(required=True), description="Get a user by ID")
51+
users = graphene.List(User, description="List all users")
52+
system_status = graphene.Field(SystemInfo, description="Check the system status")
53+
54+
def resolve_user(self, info, id):
55+
# Insecure Direct Object Reference (IDOR) equivalent in GraphQL
56+
# No auth check here!
57+
for user in users_db:
58+
if user["id"] == id:
59+
return User(
60+
id=user["id"],
61+
username=user["username"],
62+
email=user["email"],
63+
api_token=user["api_token"],
64+
is_admin=user["is_admin"],
65+
notes=user["notes"]
66+
)
67+
return None
68+
69+
def resolve_users(self, info):
70+
# Returns all users but filters sensitive info in this view (mocking a "public" list)
71+
# But if they query 'user(id: "1")' directly, they get everything!
72+
safe_users = []
73+
for user in users_db:
74+
safe_users.append(User(
75+
id=user["id"],
76+
username=user["username"],
77+
email=user["email"],
78+
api_token="[REDACTED]", # Redacted in the list view
79+
is_admin=user["is_admin"],
80+
notes=user["notes"]
81+
))
82+
return safe_users
83+
84+
def resolve_system_status(self, info):
85+
return SystemInfo(version="2.0.1-alpha", status="Operational", debug_mode=True)
86+
87+
class CreateUser(graphene.Mutation):
88+
class Arguments:
89+
username = graphene.String(required=True)
90+
email = graphene.String(required=True)
91+
92+
user = graphene.Field(lambda: User)
93+
94+
def mutate(self, info, username, email):
95+
user = User(username=username, email=email, id=str(len(users_db) + 1))
96+
return CreateUser(user=user)
97+
98+
class Mutation(graphene.ObjectType):
99+
create_user = CreateUser.Field()
100+
101+
schema = graphene.Schema(query=Query, mutation=Mutation)
102+
103+
# --- Flask App ---
104+
app = Flask(__name__)
105+
app.debug = True
106+
107+
# Add the GraphQL endpoint
108+
app.add_url_rule(
109+
'/graphql',
110+
view_func=GraphQLView.as_view(
111+
'graphql',
112+
schema=schema,
113+
graphiql=True # Enable the GraphiQL interface
114+
)
115+
)
116+
117+
@app.route('/')
118+
def index():
119+
return """
120+
<html>
121+
<head>
122+
<title>Galactic Communications Hub</title>
123+
<style>
124+
body { font-family: sans-serif; background: #0f172a; color: white; text-align: center; padding: 50px; }
125+
h1 { color: #3b82f6; }
126+
a { color: #60a5fa; text-decoration: none; font-size: 1.2em; }
127+
a:hover { text-decoration: underline; }
128+
</style>
129+
</head>
130+
<body>
131+
<h1>Galactic Communications Hub</h1>
132+
<p>Welcome to the secure internal API.</p>
133+
<p>Access the GraphiQL Interface below:</p>
134+
<br>
135+
<a href="/graphql">>> Enter GraphiQL Console <<</a>
136+
</body>
137+
</html>
138+
"""
139+
140+
if __name__ == '__main__':
141+
app.run(host='0.0.0.0', port=5023)
142+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
flask==2.3.3
2+
graphene==3.3.0
3+
flask-graphql==2.0.1
4+
werkzeug==2.3.7
5+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM node:14-alpine
2+
3+
WORKDIR /usr/src/app
4+
5+
COPY package*.json ./
6+
7+
RUN npm install
8+
9+
COPY . .
10+
11+
EXPOSE 3000
12+
13+
CMD [ "node", "server.js" ]
14+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "prototype-pollution-lab",
3+
"version": "1.0.0",
4+
"description": "Lab for Prototype Pollution and DOM Clobbering",
5+
"main": "server.js",
6+
"scripts": {
7+
"start": "node server.js"
8+
},
9+
"dependencies": {
10+
"express": "^4.17.1",
11+
"body-parser": "^1.19.0"
12+
}
13+
}
14+
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Client-Side Logic Lab</title>
7+
<style>
8+
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
9+
.lab-section { border: 1px solid #ccc; padding: 20px; margin-bottom: 20px; border-radius: 5px; }
10+
h2 { color: #d32f2f; }
11+
pre { background: #f4f4f4; padding: 10px; border-radius: 5px; }
12+
button { background: #007bff; color: white; border: none; padding: 10px 20px; cursor: pointer; }
13+
button:hover { background: #0056b3; }
14+
input { padding: 10px; width: 300px; }
15+
.result { margin-top: 10px; font-weight: bold; }
16+
</style>
17+
</head>
18+
<body>
19+
<h1>Client-Side Logic & Prototype Pollution</h1>
20+
21+
<!-- Lab 1: DOM Clobbering -->
22+
<div class="lab-section">
23+
<h2>Lab 1: DOM Clobbering</h2>
24+
<p>Goal: Inject HTML to clobber the global <code>config</code> variable and force the application to load a malicious script (or simulate it).</p>
25+
<p>The application runs this logic:</p>
26+
<pre>
27+
window.onload = function() {
28+
let scriptUrl = window.config?.scriptUrl || 'default.js';
29+
document.getElementById('current-script').innerText = "Loading script: " + scriptUrl;
30+
};
31+
</pre>
32+
33+
<!-- Vulnerable Output Sink -->
34+
<div id="user-content">
35+
<!-- This simulates user-controlled content being rendered without proper sanitization -->
36+
<!-- In a real scenario, this would be an XSS sink or content injection point -->
37+
<!-- For this lab, enter HTML below to see it rendered here -->
38+
</div>
39+
40+
<h3>Inject HTML:</h3>
41+
<textarea id="html-input" rows="4" cols="50" placeholder="Enter HTML here (e.g. <a id=config>...</a>)"></textarea><br>
42+
<button onclick="renderUserHtml()">Inject & Reload</button>
43+
44+
<p>Current Status: <span id="current-script">Waiting...</span></p>
45+
</div>
46+
47+
<!-- Lab 2: Prototype Pollution -->
48+
<div class="lab-section">
49+
<h2>Lab 2: Server-Side Prototype Pollution</h2>
50+
<p>Goal: Pollute the server-side Object prototype to gain Admin access.</p>
51+
<p>Send a JSON payload to merge settings. If successful, <code>isAdmin</code> will become true for all objects.</p>
52+
53+
<h3>Update Settings:</h3>
54+
<textarea id="json-input" rows="4" cols="50">{"theme": "dark"}</textarea><br>
55+
<button onclick="sendUpdate()">Send Update</button>
56+
57+
<div id="server-response" class="result"></div>
58+
<br>
59+
<button onclick="checkAdmin()">Check Admin Access</button>
60+
<div id="admin-status" class="result"></div>
61+
</div>
62+
63+
<script>
64+
// Vulnerable DOM Clobbering Logic
65+
function renderUserHtml() {
66+
const input = document.getElementById('html-input').value;
67+
// DANGEROUS: Directly setting innerHTML (simulating an XSS/injection sink)
68+
document.getElementById('user-content').innerHTML = input;
69+
70+
// Re-run the vulnerable logic to see if 'config' was clobbered
71+
setTimeout(() => {
72+
let scriptUrl = 'default.js';
73+
74+
// Vulnerable check
75+
if (window.config && window.config.scriptUrl) {
76+
scriptUrl = window.config.scriptUrl;
77+
78+
// Note: In real DOM clobbering with <a> tags, toString() gives the href.
79+
// We simulate this behavior check.
80+
if (typeof scriptUrl === 'object') { // It might be an element collection if clobbered
81+
scriptUrl = scriptUrl.toString();
82+
}
83+
}
84+
85+
document.getElementById('current-script').innerText = "Loading script: " + scriptUrl;
86+
87+
if (scriptUrl.includes('malicious')) {
88+
alert("DOM Clobbering Successful! Malicious script path detected.");
89+
}
90+
}, 100);
91+
}
92+
93+
// Prototype Pollution Logic
94+
async function sendUpdate() {
95+
const input = document.getElementById('json-input').value;
96+
try {
97+
const json = JSON.parse(input);
98+
const response = await fetch('/api/update-settings', {
99+
method: 'POST',
100+
headers: { 'Content-Type': 'application/json' },
101+
body: JSON.stringify(json)
102+
});
103+
const result = await response.json();
104+
document.getElementById('server-response').innerText = JSON.stringify(result, null, 2);
105+
} catch (e) {
106+
document.getElementById('server-response').innerText = "Error: " + e.message;
107+
}
108+
}
109+
110+
async function checkAdmin() {
111+
try {
112+
const response = await fetch('/api/admin-check');
113+
const result = await response.json();
114+
document.getElementById('admin-status').innerText = result.message;
115+
if(result.flag) {
116+
document.getElementById('admin-status').innerText += "\n" + result.flag;
117+
document.getElementById('admin-status').style.color = "green";
118+
}
119+
} catch (e) {
120+
document.getElementById('admin-status').innerText = "Error: " + e.message;
121+
}
122+
}
123+
</script>
124+
</body>
125+
</html>
126+

0 commit comments

Comments
 (0)