ProjectHub is a project management web application that demonstrates common security vulnerabilities found in real-world applications, including all OWASP Top 10 vulnerabilities and additional security flaws.
- User Management: Registration, authentication, role-based access control (admin, project_manager, team_member)
- Project Management: Create, update, delete projects with descriptions and status tracking
- Task Management: Assign tasks to users, track status, add comments
- Document Management: Upload documents with metadata extraction (supports XML, YAML, Pickle, and image files with EXIF data extraction via Pillow)
- Messaging: Send and receive messages between users
- Analytics: Application statistics and search functionality
- Admin Dashboard: HTML-based admin interface for viewing system data
- Request Tracking: Request ID generation and logging for all API calls
- Error Handling: Custom error pages with request tracking
ProjectHub is designed to help security professionals, developers, and students understand and test for common security vulnerabilities. The application includes:
- User authentication and authorization (JWT-based)
- Project and task management with comments
- Document upload and sharing with metadata extraction
- Messaging system between users
- User management (admin functionality)
- Analytics and reporting endpoints
- Admin dashboard (HTML interface)
- RESTful API endpoints
- Request tracking and logging
- Docker containerization
- AWS infrastructure as code (Terraform)
- CI/CD pipeline (GitHub Actions)
- Backend: Flask 1.1.4
- Frontend: React 16.8.6
- Database: PostgreSQL 10
- ORM: SQLAlchemy 1.4.0 (via Flask-SQLAlchemy 2.3.2)
- Image Processing: Pillow 5.2.0
- Authentication: JWT (PyJWT 1.6.4, Flask-JWT-Extended 3.13.1)
- Templates: Jinja2 2.11.3
- Containerization: Docker & Docker Compose
- Web Server: Nginx 1.14
- Infrastructure: Terraform (AWS)
- CI/CD: GitHub Actions
- Docker and Docker Compose
- Python 3.7+ (for local development)
- Node.js 10+ (for local development)
- PostgreSQL (if running locally without Docker)
This documentation uses Docker Compose V2 syntax (docker compose - no hyphen), which is the modern standard included with Docker Desktop and recent Docker installations.
If you have Docker Compose V1 (older standalone installation), replace docker compose with docker-compose (with hyphen) in all commands.
Check your version:
# V2 (recommended)
docker compose version
# V1 (legacy)
docker-compose --versionBuild all containers:
docker compose -f docker/docker-compose.yml buildBuild without cache (clean build):
docker compose -f docker/docker-compose.yml build --no-cacheStart all services:
docker compose -f docker/docker-compose.yml up -dBuild and start in one command (recommended):
docker compose -f docker/docker-compose.yml up -d --buildNote: The frontend is automatically built during the Docker build process. The production build is created in frontend/build/ and served by Nginx on port 80.
Stop all services (keeps containers):
docker compose -f docker/docker-compose.yml stopStop and remove containers:
docker compose -f docker/docker-compose.yml downStop and remove containers + volumes (
docker compose -f docker/docker-compose.yml down -vView logs:
# All services
docker compose -f docker/docker-compose.yml logs -f
# Specific service
docker compose -f docker/docker-compose.yml logs -f backend
docker compose -f docker/docker-compose.yml logs -f frontendCheck service status:
docker compose -f docker/docker-compose.yml psRestart a specific service:
docker compose -f docker/docker-compose.yml restart backend
docker compose -f docker/docker-compose.yml restart frontend- Clone the repository:
git clone <repository-url>
cd ProjectHub- Build and start all services:
docker compose -f docker/docker-compose.yml up -d --buildThat's it! The command will:
- Build the frontend (production build)
- Build the backend container
- Start all services (database, backend, frontend, nginx)
- Automatically seed the database with test data
-
Wait a few seconds for services to initialize, then access the application:
- Main Application (via Nginx): http://localhost (or http://YOUR_SERVER_IP)
- Backend API: http://localhost/api (or http://YOUR_SERVER_IP/api)
- Admin Dashboard: http://localhost/admin (or http://YOUR_SERVER_IP/admin)
- API Health Check: http://localhost/api/health (or http://YOUR_SERVER_IP/api/health)
- Database: localhost:5432
Note: The frontend is automatically built and served through Nginx on port 80. No additional build steps required.
The application automatically seeds the database with test data on first startup. This includes:
- Test users (admin and team members)
- Sample projects
- Tasks and comments
- Messages (100 messages with 50 unique templates, spanning 6 months)
- Document records
Note: Seeding only occurs if the database is empty. To re-seed, remove the database volume:
docker compose -f docker/docker-compose.yml down -v
docker compose -f docker/docker-compose.yml up -dTo skip seeding, set the environment variable SKIP_SEED=true in the backend service.
Application Users (seeded automatically):
- Admin: admin@projecthub.com / Admin (admin role) - password:
Admin - Alice: alice@projecthub.com / Alice (project_manager role) - password:
Alice - Bob: bob@projecthub.com / Bob (team_member role) - password:
Bob - Charlie: charlie@projecthub.com / Charlie (team_member role) - password:
Charlie - Diana: diana@projecthub.com / Diana (team_member role) - password:
Diana - Eve: eve@projecthub.com / Eve (project_manager role) - password:
Eve
Note: Login accepts either username or email (case-insensitive). Passwords match the capitalized username (e.g., username "Alice" has password "Alice").
Database:
- User: projecthub
- Password: password123
- Database: projecthub
ProjectHub/
├── backend/ # Flask backend application
│ ├── app.py # Main Flask app
│ ├── models.py # Database models
│ ├── database.py # Database initialization and seeding
│ ├── auth.py # Authentication logic
│ ├── config.py # Configuration (with hardcoded secrets)
│ ├── requirements.txt # Python dependencies
│ ├── docker-entrypoint.sh # Container startup script
│ ├── routes/ # Route handlers
│ │ ├── __init__.py
│ │ ├── api.py # General API routes (includes user management)
│ │ ├── auth.py # Authentication routes
│ │ ├── projects.py # Project management routes
│ │ ├── tasks.py # Task management routes
│ │ ├── documents.py # Document management routes
│ │ ├── messages.py # Messaging routes
│ │ └── analytics.py # Analytics and reporting routes
│ ├── utils/ # Utility modules
│ │ ├── __init__.py
│ │ ├── logger.py # Logging configuration
│ │ ├── file_handler.py # File handling utilities
│ │ ├── request_context.py # Request context management
│ │ ├── query_helpers.py # Database query helpers
│ │ ├── datetime_utils.py # Datetime utilities
│ │ └── jinja_filters.py # Jinja2 template filters
│ ├── templates/ # HTML templates
│ │ ├── error.html # Error pages
│ │ └── admin.html # Admin dashboard
│ ├── uploads/ # Uploaded files directory
│ └── logs/ # Application logs directory
├── frontend/ # React frontend application
│ ├── src/
│ │ ├── components/ # React components
│ │ │ ├── Dashboard.js
│ │ │ ├── Login.js
│ │ │ ├── TaskList.js
│ │ │ ├── MessageCenter.js
│ │ │ ├── DocumentUpload.js
│ │ │ ├── ProjectDetail.js
│ │ │ └── UserManagement.js
│ │ ├── services/ # API client
│ │ │ └── api.js
│ │ ├── App.js # Main React component
│ │ ├── index.js # Entry point
│ │ └── index.css # Global styles
│ ├── public/
│ │ └── index.html # HTML template
│ ├── Dockerfile # Frontend Dockerfile
│ └── package.json # Node.js dependencies
├── docker/ # Docker configuration
│ ├── Dockerfile # Backend Dockerfile
│ ├── docker-compose.yml # Service orchestration
│ └── nginx.conf # Nginx reverse proxy config
├── infrastructure/ # Terraform IaC
│ ├── main.tf # AWS resources
│ ├── s3.tf # S3 bucket configuration
│ ├── iam.tf # IAM roles and policies
│ ├── variables.tf # Terraform variables
│ └── outputs.tf # Terraform outputs
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions CI/CD
├── README.md # This file
├── QUICKSTART.md # Quick start guide
├── LICENSE # License file
├── openapi.yaml # OpenAPI specification
└── get-docker.sh # Docker installation helper script
This application intentionally contains numerous security vulnerabilities for educational and security testing purposes. The application demonstrates common security flaws found in real-world applications.
The application includes vulnerabilities across all OWASP Top 10 categories:
- Injection vulnerabilities (SQL injection, command injection, XXE)
- Broken authentication and session management (weak secrets, no expiration, case-insensitive login)
- Sensitive data exposure (passwords, API keys, tokens exposed in API responses and logs)
- Broken access control (IDOR, misconfigured RBAC, unauthorized access)
- Security misconfiguration (outdated dependencies, insecure defaults, missing security headers)
- Cross-site scripting (XSS) (stored, reflected, DOM-based)
- Insecure deserialization (pickle, YAML, JSON)
- Using components with known vulnerabilities (outdated packages with CVEs, notably Pillow 5.2.0 with 15+ critical vulnerabilities)
- Insufficient logging and monitoring (log injection, sensitive data in logs)
- Additional security weaknesses (hardcoded secrets, insecure file uploads, path traversal, no CSRF protection, weak password hashing)
This application uses older patterns and APIs that will break when upgrading dependencies. The codebase is designed to force comprehensive refactoring when students attempt to fix critical security vulnerabilities.
| Pattern | Current Version | Breaking Version | Files Affected | Instances | Migration Complexity |
|---|---|---|---|---|---|
| Pillow (Security Driver) | Pillow 5.2.0 | Pillow 10.0+ | requirements.txt | 15+ CVEs | CRITICAL - Triggers entire upgrade cascade |
Model.query (SQLAlchemy) |
SQLAlchemy 1.4.0 (via Flask-SQLAlchemy 2.3.2) | SQLAlchemy 2.0+ | 10+ files | 100+ | SIGNIFICANT - Replace with db.session.query(Model) |
datetime.utcnow() (Python) |
Python 3.7 | Python 3.12+ | 8+ files | 18+ | HIGH - Replace with datetime.now(timezone.utc) |
_request_ctx_stack (Flask) |
Flask 1.1.4 | Flask 2.0+ | 4+ files | 10+ | MEDIUM - Replace with g object |
@contextfilter (Jinja2) |
Jinja2 2.11.3 | Jinja2 3.0+ | 2 files | 7 filters | MEDIUM - Replace with @pass_context |
yaml.load() without Loader (PyYAML) |
PyYAML 3.13 | PyYAML 6.0+ | 1 file | 1 | LOW - Add Loader=yaml.SafeLoader |
Upgrade Cascade: Pillow CVEs → Python 3.8+ required → Python 3.12 chosen → datetime.utcnow() breaks + other dependencies forced to upgrade
Why Pillow?
Pillow 5.2.0 is used throughout the application for image processing (see backend/utils/file_handler.py) to extract metadata, EXIF data, and validate uploaded images. This version contains 15+ critical CVEs including buffer overflows, out-of-bounds reads/writes, and potential RCE vulnerabilities. These will be flagged as HIGH/CRITICAL by any security scanner (Snyk, Checkmarx, Dependabot, etc.), creating unavoidable pressure to upgrade.
Critical CVEs in Pillow 5.2.0:
- CVE-2019-16865 (DoS vulnerability)
- CVE-2020-5310 (Buffer overflow)
- CVE-2020-5311 (Out-of-bounds write, RCE potential)
- CVE-2020-5312 (Out-of-bounds read)
- CVE-2020-5313 (Out-of-bounds read)
- CVE-2020-10177, CVE-2020-10378, CVE-2020-10994 (Memory corruption)
- Multiple additional critical vulnerabilities through 2020-2023
The Upgrade Cascade:
Pillow 5.2.0 (current) → 15+ critical CVEs flagged by security scanners
↓
Students must upgrade to Pillow 10.0+ to fix CVEs
↓
Pillow 10.0+ requires Python 3.8+ (drops Python 3.7 support)
↓
Students upgrade Python 3.7 → Python 3.12 (latest stable)
↓
Python 3.12 deprecates datetime.utcnow() (18+ instances break)
↓
Other dependencies may need upgrades for Python 3.12 compatibility
↓
Students must refactor deprecated patterns across the codebase
Why This Works:
- Real Security Pressure: Unlike theoretical vulnerabilities, Pillow CVEs are severe and will be flagged by security teams
- Compliance Requirements: Organizations mandate fixing critical CVEs, making this unavoidable
- Realistic Scenario: Mirrors production situations where security debt forces technical debt remediation
- Educational Value: Teaches students about dependency cascades and the cost of technical debt
Version Details:
- Pillow: Version 5.2.0 has 15+ critical CVEs. Upgrading to Pillow 10.0+ fixes CVEs but requires Python 3.8+, forcing a Python upgrade that breaks
datetime.utcnow()and potentially other patterns. - SQLAlchemy: SQLAlchemy is pinned to
<2.0,>=1.4.0in requirements.txt to work with Flask-SQLAlchemy 2.3.2. TheModel.querypattern works in SQLAlchemy 1.4.x but is removed in SQLAlchemy 2.0+. Upgrading to SQLAlchemy 2.0+ will break allModel.queryusage (100+ instances). This pattern must be migrated todb.session.query(Model)ordb.session.get(Model, id)before upgrading. - Python: Current Docker image uses Python 3.7.
datetime.utcnow()is deprecated in Python 3.12 and will be removed in future versions. Upgrading Python to fix Pillow compatibility will break 18+ instances. - Flask: Version 1.1.4 uses
_request_ctx_stackwhich is removed in Flask 2.0+ (replaced withgobject). - Jinja2: Version 2.11.3 uses
@contextfilterwhich is replaced with@pass_contextin Jinja2 3.0+. - PyYAML: Version 3.13 allows
yaml.load()without Loader (with security warnings). PyYAML 6.0+ removes this unsafe default.
Note: These patterns are intentionally used throughout the codebase to create realistic technical debt scenarios. The current versions work correctly, but security vulnerabilities in Pillow create unavoidable pressure to upgrade, triggering cascading breaking changes that require significant refactoring.
POST /api/auth/register- User registrationPOST /api/auth/login- User loginPOST /api/auth/logout- User logoutGET /api/auth/me- Get current user infoPOST /api/auth/reset-password- Password reset
GET /api/projects- List all projects (with search)GET /api/projects/<id>- Get project detailsPOST /api/projects- Create projectPUT /api/projects/<id>- Update projectDELETE /api/projects/<id>- Delete projectGET /api/projects/<id>/tasks- Get project tasks
GET /api/tasks- List all tasks (with filters)GET /api/tasks/<id>- Get task detailsPOST /api/tasks- Create taskPUT /api/tasks/<id>- Update taskDELETE /api/tasks/<id>- Delete taskGET /api/tasks/<id>/comments- Get task commentsPOST /api/tasks/<id>/comments- Add comment to task
GET /api/documents- List all documentsGET /api/documents/<id>- Get document detailsPOST /api/documents- Upload documentPUT /api/documents/<id>- Update documentDELETE /api/documents/<id>- Delete documentGET /api/documents/<id>/download- Download document
GET /api/messages- List messages (sent/received)GET /api/messages/<id>- Get message detailsPOST /api/messages- Send messageDELETE /api/messages/<id>- Delete messageGET /api/messages/search- Search messages
GET /api/analytics/stats- Get application statisticsGET /api/analytics/search- Search across users and projectsGET /api/analytics/user/<id>- Get user analytics
GET /api/v1/users- List all users (with search)GET /api/v1/users/<id>- Get user detailsPOST /api/v1/users- Create userPUT /api/v1/users/<id>- Update userDELETE /api/v1/users/<id>- Delete userGET /api/v1/stats- Get statisticsGET /api/v1/search- Global search
GET /- API informationGET /api/health- Health checkGET /admin- Admin dashboard (HTML)
Try searching for projects with:
' OR '1'='1
Or in the users endpoint:
GET /api/v1/users?search=' OR '1'='1
Try adding a comment with:
<script>alert('XSS')</script>Access other users' resources by modifying URL parameters:
GET /api/tasks/1 (try different IDs)
GET /api/documents/1
GET /api/messages/1
JWT tokens never expire and use a weak secret. Try decoding tokens at jwt.io. Tokens are stored in localStorage and can be accessed via browser console.
cd backend
pip install -r requirements.txt
# Set environment variables (or use .env file)
export DATABASE_URL=postgresql://projecthub:password123@localhost:5432/projecthub
export JWT_SECRET=secret_key_12345
# Initialize database
python -c "from database import init_db; from app import app; init_db(app)"
# Run the application
python app.pyThe backend will be available at http://localhost:5000
cd frontend
npm install
# Set API URL (or edit src/services/api.js)
export REACT_APP_API_URL=http://localhost:5000/api
# Run the development server
npm startThe frontend will be available at http://localhost:3000
For development with hot-reload, the docker-compose.yml includes volume mounts that sync your local code changes into the containers. Simply edit files locally and the changes will be reflected in the running containers (frontend may require a page refresh).
cd infrastructure
terraform init
terraform plan
terraform applyThis is a security testing demo application. Contributions should focus on adding more realistic vulnerabilities or improving documentation.
MIT License - See LICENSE file for details
This software is provided for educational and security testing purposes only. The authors are not responsible for any misuse of this software. Use at your own risk.