Skip to content

Commit 712a79a

Browse files
Merge pull request adeyosemanputra#256 from meprajwal/master
Added dockerized lab for sensitive data exposure
2 parents bfd2e4a + eb28d2c commit 712a79a

File tree

24 files changed

+1567
-0
lines changed

24 files changed

+1567
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM python:3.9-slim
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt .
6+
7+
RUN pip install --no-cache-dir -r requirements.txt
8+
9+
COPY . .
10+
11+
# Add script to make sure migrations run properly
12+
COPY entrypoint.sh /entrypoint.sh
13+
RUN chmod +x /entrypoint.sh
14+
15+
EXPOSE 8000
16+
17+
# Use the entrypoint script
18+
ENTRYPOINT ["/entrypoint.sh"]
19+
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Sensitive Data Exposure Lab
2+
3+
Hey there! 👋 This is a standalone lab I built for "Sensitive Data Exposure" - one of the OWASP Top 10 security vulnerabilities. It's a hands-on way to learn about how data leaks happen and how to prevent them.
4+
5+
## What's This All About?
6+
7+
This lab demonstrates how sensitive information (like credit cards, SSNs, and API keys) can accidentally get exposed in web apps. I've deliberately built in several security flaws so you can practice finding them - it's like a security treasure hunt.
8+
9+
## Getting Started
10+
11+
### What You'll Need
12+
13+
- Docker and Docker Compose (that's it!)
14+
15+
### How to Run It
16+
17+
Just follow these steps:
18+
19+
1. Clone this repo
20+
2. CD into the project folder
21+
3. Fire up Docker:
22+
23+
```bash
24+
docker-compose up -d
25+
```
26+
27+
4. Open your browser and head to http://localhost:8000
28+
29+
### Quick Testing
30+
31+
Don't want to create an account? No worries! Use the demo login:
32+
- Username: `demo`
33+
- Password: `demopass`
34+
35+
### Ugh, It's Not Working!
36+
37+
If you hit database errors, try this:
38+
39+
```bash
40+
# The nuclear option - rebuild everything
41+
docker-compose down
42+
docker-compose up --build
43+
44+
# Or just run the migrations
45+
docker-compose exec web python manage.py makemigrations dataexposure
46+
docker-compose exec web python manage.py migrate
47+
```
48+
49+
## What You'll Learn
50+
51+
This lab will help you:
52+
1. Spot different ways sensitive data gets leaked
53+
2. Understand why proper data protection matters
54+
3. Learn how hackers find and exploit these issues
55+
4. Discover best practices for keeping sensitive data safe
56+
57+
## The Security Flaws
58+
59+
I've hidden several security problems for you to find (don't peek at this list until you've tried!):
60+
61+
1. Comments in HTML with sensitive data (check the page source!)
62+
2. JavaScript that exposes API keys
63+
3. Unprotected API endpoints anyone can access
64+
4. Data stored in localStorage (check your browser)
65+
5. Partial data masking that's easy to bypass
66+
6. A public API that leaks EVERYONE'S data (yikes!)
67+
7. Missing access controls that let you see other people's info
68+
69+
## Fair Warning ⚠️
70+
71+
This app is INTENTIONALLY INSECURE. Don't put any real personal info in it! And definitely don't deploy it to a public server unless you want to give hackers a practice playground.
72+
73+
---
74+
75+
Happy hacking (ethically, of course)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This file is intentionally left empty
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django import forms
2+
from django.contrib.auth.forms import UserCreationForm
3+
4+
class UserLoginForm(forms.Form):
5+
username = forms.CharField(max_length=150)
6+
password = forms.CharField(widget=forms.PasswordInput)
7+
8+
class UserRegisterForm(UserCreationForm):
9+
class Meta(UserCreationForm.Meta):
10+
fields = ['username', 'password1', 'password2']
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Generated by Django 3.2.18 on 2025-03-27 18:10
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
initial = True
11+
12+
dependencies = [
13+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name='UserData',
19+
fields=[
20+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21+
('credit_card', models.CharField(max_length=16)),
22+
('ssn', models.CharField(max_length=9)),
23+
('api_key', models.CharField(max_length=32)),
24+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
25+
],
26+
),
27+
]

dockerized_labs/sensitive_data_exposure/dataexposure/migrations/__init__.py

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from django.db import models
2+
from django.contrib.auth.models import User
3+
4+
class UserData(models.Model):
5+
user = models.ForeignKey(User, on_delete=models.CASCADE)
6+
credit_card = models.CharField(max_length=16) # probly should encrypt this lol
7+
ssn = models.CharField(max_length=9) # this 2, security risk!
8+
api_key = models.CharField(max_length=32) # generated on registration
9+
10+
def __str__(self):
11+
return f"Data for {self.user.username}"
12+
13+
# TODO: add method to mask card number except last 4 digits
14+
# Will do it later when have more time
15+
16+
# IMPORTANT: If u see OperationalError about missing tables,
17+
# run these commands manually:
18+
# python manage.py makemigrations dataexposure
19+
# python manage.py migrate
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from django.urls import path
2+
from . import views
3+
4+
urlpatterns = [
5+
path('', views.index, name='index'),
6+
path('about/', views.about, name='about'),
7+
path('login/', views.login_view, name='login'),
8+
path('register/', views.register_view, name='register'),
9+
path('profile/', views.profile_view, name='profile'),
10+
11+
# API endpoints demonstrating insecure data exposure (for educational purposes)
12+
path('api/user-data/', views.api_data_view, name='api_data'),
13+
path('api/all-users/', views.all_users_data_view, name='all_users_data'),
14+
15+
path('logout/', views.logout_view, name='logout'),
16+
path('lesson/', views.sensitive_data_exposure_lesson, name='lesson'),
17+
18+
# TODO - additional URLs to implement:
19+
# path('api/v2/user-data/', views.api_data_view_v2, name='api_data_v2'), # secure version
20+
# path('settings/', views.user_settings, name='settings'),
21+
# path('admin-dashboard/', views.admin_dashboard, name='admin_dashboard'),
22+
]
23+
24+
# This lab intentionally contains insecure endpoints to demonstrate
25+
# sensitive data exposure vulnerabilities. In a real application,
26+
# these endpoints would require proper authorization checks.
27+
28+
# Note: we should probably organize these better and use routers
29+
# but this is fine for now
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
from django.shortcuts import render, redirect
2+
from django.contrib.auth.models import User
3+
from django.contrib.auth.decorators import login_required
4+
from django.contrib.auth import authenticate, login, logout
5+
from django.http import JsonResponse
6+
from django.contrib import messages
7+
from .models import UserData
8+
from .forms import UserLoginForm, UserRegisterForm
9+
import random
10+
import string
11+
12+
def index(request):
13+
# main landing pg
14+
return render(request, 'index.html')
15+
16+
def about(request):
17+
# about pg nothing special
18+
return render(request, 'about.html')
19+
20+
def login_view(request):
21+
if request.method == 'POST':
22+
form = UserLoginForm(request.POST)
23+
if form.is_valid():
24+
username = form.cleaned_data.get('username')
25+
password = form.cleaned_data.get('password')
26+
user = authenticate(username=username, password=password)
27+
if user is not None:
28+
login(request, user)
29+
messages.success(request, f'Welcome back, {username}! You are now logged in.')
30+
return redirect('profile')
31+
else:
32+
messages.error(request, 'Invalid username or password. Please try again.')
33+
else:
34+
form = UserLoginForm()
35+
36+
return render(request, 'login.html', {'form': form})
37+
38+
def generate_api_key():
39+
# generate a random api key
40+
# I should probably use a better method but this works for now
41+
chars = string.ascii_lowercase + string.digits
42+
return ''.join(random.choices(chars, k=16)) # 16 chars should be enough right?
43+
44+
def register_view(request):
45+
if request.method == 'POST':
46+
form = UserRegisterForm(request.POST)
47+
if form.is_valid():
48+
username = form.cleaned_data.get('username')
49+
password = form.cleaned_data.get('password1')
50+
user = User.objects.create_user(username=username, password=password)
51+
52+
# Creating sensitive data for the user
53+
# Yeah i know this is dummy data but works for demo
54+
UserData.objects.create(
55+
user=user,
56+
credit_card='4111111111111111', # test visa card number lol
57+
ssn='123456789', # not a real SSN obvs
58+
api_key=generate_api_key() # not very secure api key but whatever
59+
)
60+
61+
messages.success(request, f'Account created for {username}! You can now log in.')
62+
return redirect('login')
63+
else:
64+
form = UserRegisterForm()
65+
66+
return render(request, 'register.html', {'form': form})
67+
68+
@login_required
69+
def profile_view(request):
70+
# show user profile with some data masked
71+
try:
72+
user_data = UserData.objects.get(user=request.user)
73+
# TODO: add audit logging here someday
74+
except UserData.DoesNotExist:
75+
# If no user data exists, create some dummy data for demo
76+
# This should never happen but just in case
77+
print(f"Creating missing user data for {request.user.username}") # debugging stuff
78+
user_data = UserData.objects.create(
79+
user=request.user,
80+
credit_card='4111111111111111', # test visa card number lol
81+
ssn='123456789', # not a real SSN obvs
82+
api_key=generate_api_key() # not very secure api key but whatever
83+
)
84+
return render(request, 'profile.html', {'user_data': user_data})
85+
86+
@login_required
87+
def api_data_view(request):
88+
# FIXME: this is insecure AF - just for demo purposes!!
89+
# sends all user data as json - bad practice!!
90+
try:
91+
user_data = UserData.objects.get(user=request.user)
92+
except UserData.DoesNotExist:
93+
# If no user data exists, create some dummy data for demo
94+
user_data = UserData.objects.create(
95+
user=request.user,
96+
credit_card='4111111111111111', # test card number
97+
ssn='123456789', # demo SSN
98+
api_key=generate_api_key() # simple api key
99+
)
100+
101+
data = {
102+
'username': request.user.username,
103+
'credit_card': user_data.credit_card,
104+
'ssn': user_data.ssn,
105+
'api_key': user_data.api_key
106+
}
107+
# Intentionally exposing sensitive data through API
108+
# cuz we're teaching about data exposure, duh!
109+
return JsonResponse(data)
110+
111+
# Intentionally insecure - for teaching purposes!
112+
def all_users_data_view(request):
113+
# MAJOR SECURITY FLAW: This endpoint allows ANY user (even unauthenticated ones!)
114+
# to see ALL users' sensitive data!
115+
# This demonstrates why proper authentication/authorization is essential.
116+
117+
# Note for students: Notice how there are no checks for who's requesting the data!
118+
119+
all_users_data = []
120+
for user_data in UserData.objects.all():
121+
all_users_data.append({
122+
'username': user_data.user.username,
123+
'credit_card': user_data.credit_card, # Completely exposed! No masking!
124+
'ssn': user_data.ssn, # Sending full SSN - terrible practice!
125+
'api_key': user_data.api_key # API keys should never be exposed like this
126+
})
127+
128+
# In a secure application, we would add:
129+
# if not request.user.is_authenticated or not request.user.is_staff:
130+
# return JsonResponse({'error': 'Unauthorized'}, status=401)
131+
132+
return JsonResponse({'users': all_users_data})
133+
134+
def logout_view(request):
135+
# simple logout nothing fancy
136+
logout(request)
137+
messages.info(request, 'You have been logged out successfully.')
138+
return redirect('index')
139+
140+
def sensitive_data_exposure_lesson(request):
141+
# lessons page
142+
return render(request, 'lesson.html')
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
version: '3'
2+
3+
services:
4+
web:
5+
build: .
6+
ports:
7+
- "8000:8000"
8+
volumes:
9+
- .:/app
10+
command: >
11+
sh -c "python manage.py makemigrations dataexposure &&
12+
python manage.py migrate &&
13+
python manage.py runserver 0.0.0.0:8000"
14+
# hey dont forget to rebuild if u change requirements!!
15+
# fixed db issues - make sure migrations run before server starts!!
16+
# if still broken try: docker-compose down && docker-compose up --build
17+
18+
# NOTE TO SELF: try on different port if 8000 is taken
19+
# also need to implement persistant volume for db
20+
# ports:
21+
# - "8001:8000" # alternate port

0 commit comments

Comments
 (0)