Skip to content

Commit da0353e

Browse files
committed
standalone lab for insecure deserialization
1 parent 3b3fad5 commit da0353e

9 files changed

Lines changed: 488 additions & 0 deletions

File tree

insec_des_lab/.dockerignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
__pycache__
2+
*.pyc
3+
*.pyo
4+
*.pyd
5+
.Python
6+
env/
7+
.venv/
8+
.env
9+
*.log

insec_des_lab/Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM python:3.12-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+
ENV FLASK_APP=main.py
11+
ENV FLASK_RUN_HOST=0.0.0.0
12+
13+
EXPOSE 8080
14+
15+
CMD ["flask", "run", "--debug", "--port=8080"]

insec_des_lab/docker-compose.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
services:
2+
insec_des_lab:
3+
build: .
4+
ports:
5+
- "8080:8080"
6+
volumes:
7+
- .:/app
8+
environment:
9+
- FLASK_ENV=development
10+
restart: unless-stopped

insec_des_lab/main.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from flask import Flask, render_template, request, make_response
2+
import pickle
3+
import base64
4+
from dataclasses import dataclass
5+
6+
app = Flask(__name__)
7+
8+
@dataclass
9+
class User:
10+
username: str
11+
is_admin: bool = False
12+
13+
def __reduce__(self):
14+
# Intentionally vulnerable __reduce__ method to match PyGoat
15+
return (User, (self.username, self.is_admin))
16+
17+
@app.route('/')
18+
def index():
19+
return render_template('index.html')
20+
21+
@app.route('/serialize', methods=['POST'])
22+
def serialize_data():
23+
username = request.form.get('username', 'guest')
24+
# Create regular user with admin=False
25+
user = User(username=username, is_admin=False)
26+
# Match PyGoat's serialization format
27+
serialized = base64.b64encode(pickle.dumps(user)).decode()
28+
return render_template('result.html', serialized=serialized)
29+
30+
@app.route('/deserialize', methods=['POST'])
31+
def deserialize_data():
32+
try:
33+
serialized_data = request.form.get('serialized_data', '')
34+
decoded_data = base64.b64decode(serialized_data)
35+
# Intentionally vulnerable deserialization, matching PyGoat
36+
user = pickle.loads(decoded_data)
37+
38+
if isinstance(user, User):
39+
if user.is_admin:
40+
message = f"Welcome Admin {user.username}! Here's the secret admin content: ADMIN_KEY_123"
41+
else:
42+
message = f"Welcome {user.username}. Only admins can see the secret content."
43+
else:
44+
message = "Invalid user data"
45+
46+
return render_template('result.html', message=message)
47+
except Exception as e:
48+
return render_template('result.html', message=f"Error: {str(e)}")
49+
50+
if __name__ == '__main__':
51+
app.run(host='0.0.0.0', port=8080)
52+
53+

insec_des_lab/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Flask==3.0.0
2+
Werkzeug==3.0.1

insec_des_lab/static/style.css

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/* Theme variables */
2+
:root {
3+
--bg-color: #f8f9fa;
4+
--card-bg: #fff;
5+
--text-color: #212529;
6+
--border-color: #dee2e6;
7+
--card-shadow: 0 2px 4px rgba(0,0,0,0.1);
8+
--code-bg: #2c3e50;
9+
--code-color: #ecf0f1;
10+
--button-bg: #3498db;
11+
--button-hover: #2980b9;
12+
--section-bg: #f8f9fa;
13+
--input-bg: #ffffff;
14+
--input-color: #2c3e50;
15+
--input-border: #ddd;
16+
}
17+
18+
/* Dark theme */
19+
[data-theme="dark"] {
20+
--bg-color: #212529;
21+
--card-bg: #343a40;
22+
--text-color: #f8f9fa;
23+
--border-color: #495057;
24+
--card-shadow: 0 2px 4px rgba(0,0,0,0.3);
25+
--code-bg: #1a1a1a;
26+
--code-color: #ecf0f1;
27+
--button-bg: #2980b9;
28+
--button-hover: #3498db;
29+
--section-bg: #2d3338;
30+
--input-bg: #2d3338;
31+
--input-color: #f8f9fa;
32+
--input-border: #495057;
33+
}
34+
35+
/* Reset margins and padding */
36+
* {
37+
margin: 0;
38+
padding: 0;
39+
box-sizing: border-box;
40+
}
41+
42+
html, body {
43+
margin: 0;
44+
padding: 0;
45+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
46+
background: var(--bg-color);
47+
color: var(--text-color);
48+
line-height: 1.6;
49+
font-size: 16px;
50+
min-height: 100vh;
51+
transition: all 0.3s ease;
52+
}
53+
54+
.content {
55+
max-width: 1200px;
56+
margin: 0 auto;
57+
padding: 0;
58+
}
59+
60+
.box {
61+
background: var(--card-bg);
62+
border-radius: 8px;
63+
padding: 25px;
64+
margin: 20px 0;
65+
box-shadow: var(--card-shadow);
66+
border: 1px solid var(--border-color);
67+
}
68+
69+
.container {
70+
max-width: 800px;
71+
margin: 0 auto;
72+
background: var(--card-bg);
73+
border-radius: 8px;
74+
box-shadow: var(--card-shadow);
75+
padding: 20px;
76+
}
77+
78+
main {
79+
padding: 0;
80+
}
81+
82+
h1, h2, h3, h4 {
83+
color: var(--text-color);
84+
}
85+
86+
/* Updated heading styles */
87+
h1 {
88+
font-size: 2.75rem;
89+
margin: 1rem 0;
90+
color: var(--text-color);
91+
text-align: center;
92+
font-weight: 600;
93+
}
94+
95+
h2 {
96+
font-size: 2.25rem;
97+
margin: 0.875rem 0;
98+
color: var(--text-color);
99+
font-weight: 600;
100+
}
101+
102+
h3 {
103+
font-size: 2rem;
104+
margin: 2rem 0 1rem;
105+
color: var(--text-color);
106+
font-weight: 600;
107+
border-bottom: 2px solid var(--border-color);
108+
padding-bottom: 0.5rem;
109+
}
110+
111+
h4 {
112+
font-size: 1.5rem;
113+
margin: 1.5rem 0 1rem;
114+
color: var(--text-color);
115+
font-weight: 600;
116+
}
117+
118+
.section {
119+
background: var(--section-bg);
120+
margin: 25px 0;
121+
padding: 20px;
122+
border-radius: 8px;
123+
border: 1px solid var(--border-color);
124+
}
125+
126+
.section h2 {
127+
margin-top: 0;
128+
color: var(--text-color);
129+
font-size: 1.75rem;
130+
}
131+
132+
input[type="text"], textarea {
133+
width: 100%;
134+
padding: 12px;
135+
margin: 8px 0;
136+
border: 1px solid var(--input-border);
137+
border-radius: 4px;
138+
background: var(--input-bg);
139+
color: var(--input-color);
140+
font-size: 1rem;
141+
transition: all 0.3s ease;
142+
}
143+
144+
button, .button {
145+
background: var(--button-bg);
146+
color: white;
147+
padding: 12px 24px;
148+
border: none;
149+
border-radius: 4px;
150+
cursor: pointer;
151+
text-decoration: none;
152+
display: inline-block;
153+
margin: 5px 0;
154+
font-size: 1rem;
155+
transition: all 0.3s ease;
156+
}
157+
158+
button:hover, .button:hover {
159+
background: var(--button-hover);
160+
transform: translateY(-1px);
161+
}
162+
163+
.code-block {
164+
background: var(--code-bg);
165+
color: var(--code-color);
166+
padding: 15px;
167+
border-radius: 4px;
168+
overflow-x: auto;
169+
margin: 10px 0;
170+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
171+
font-size: 0.95rem;
172+
line-height: 1.4;
173+
}
174+
175+
pre {
176+
margin: 0;
177+
white-space: pre-wrap;
178+
font-size: 0.95rem;
179+
}
180+
181+
.info {
182+
background: var(--section-bg);
183+
padding: 20px;
184+
border-left: 4px solid var(--button-bg);
185+
margin: 20px 0;
186+
border-radius: 0 8px 8px 0;
187+
font-size: 1.1rem;
188+
}
189+
190+
.lab {
191+
background: var(--section-bg);
192+
border-left: 4px solid var(--button-bg);
193+
padding: 20px;
194+
margin: 20px 0;
195+
border-radius: 0 8px 8px 0;
196+
font-size: 1.1rem;
197+
}
198+
199+
.lab .bp {
200+
margin: 1rem 0;
201+
font-weight: 500;
202+
}
203+
204+
.bp {
205+
font-size: 1.1rem;
206+
line-height: 1.7;
207+
margin: 1rem 0;
208+
color: var(--text-color);
209+
}
210+
211+
ul {
212+
padding-left: 25px;
213+
}
214+
215+
li {
216+
margin: 10px 0;
217+
line-height: 1.5;
218+
font-size: 1.1rem;
219+
}
220+
221+
.theme-toggle {
222+
position: fixed;
223+
top: 20px;
224+
right: 20px;
225+
z-index: 1000;
226+
width: 40px;
227+
height: 40px;
228+
border-radius: 50%;
229+
border: 2px solid var(--border-color);
230+
background-color: var(--card-bg);
231+
color: var(--text-color);
232+
cursor: pointer;
233+
transition: all 0.3s ease;
234+
display: flex;
235+
align-items: center;
236+
justify-content: center;
237+
padding: 0;
238+
font-size: 1.2rem;
239+
}
240+
241+
.theme-toggle:hover {
242+
transform: scale(1.1);
243+
opacity: 0.9;
244+
}

insec_des_lab/templates/base.html

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE html>
2+
<html data-theme="light">
3+
<head>
4+
<meta charset="UTF-8">
5+
{% block title %}{% endblock %}
6+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
7+
</head>
8+
<body><button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme">🌙</button>{% block content %}{% endblock %}
9+
<script>
10+
function toggleTheme() {
11+
const html = document.documentElement;
12+
const currentTheme = html.getAttribute('data-theme');
13+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
14+
15+
requestAnimationFrame(() => {
16+
html.setAttribute('data-theme', newTheme);
17+
localStorage.setItem('theme', newTheme);
18+
19+
const themeToggle = document.querySelector('.theme-toggle');
20+
themeToggle.innerHTML = newTheme === 'dark' ? '☀️' : '🌙';
21+
});
22+
}
23+
24+
// Set theme on page load
25+
document.addEventListener('DOMContentLoaded', () => {
26+
const savedTheme = localStorage.getItem('theme') || 'light';
27+
const html = document.documentElement;
28+
29+
requestAnimationFrame(() => {
30+
html.setAttribute('data-theme', savedTheme);
31+
const themeToggle = document.querySelector('.theme-toggle');
32+
themeToggle.innerHTML = savedTheme === 'dark' ? '☀️' : '🌙';
33+
});
34+
});
35+
</script></body>
36+
</html>

0 commit comments

Comments
 (0)