-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding OWASP Code #1
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
MONGODB_URI=mongodb://localhost:27017 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
venv/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# Educational Vulnerable Application | ||
|
||
**WARNING: This application is intentionally vulnerable and meant for educational purposes only. DO NOT deploy this in any production environment.** | ||
|
||
## Overview | ||
This application demonstrates common security vulnerabilities based on OWASP Top 10 (2021). It consists of two microservices: | ||
- Auth Service: Handles user authentication with intentional vulnerabilities | ||
- Profile Service: Manages user profile data with intentional vulnerabilities | ||
|
||
## Intentional Vulnerabilities | ||
|
||
### 1. Broken Access Control (A01:2021) | ||
- No role-based access control implementation | ||
- Direct object references without verification | ||
- Location: `auth_service/routes.py` - endpoint `/api/user/<id>` | ||
|
||
### 2. Cryptographic Failures (A02:2021) | ||
- Passwords stored with weak hashing (MD5) | ||
- Sensitive data transmitted without encryption | ||
- Location: `auth_service/utils.py` - `hash_password()` function | ||
|
||
### 3. Injection (A03:2021) | ||
- SQL injection vulnerability in login query | ||
- NoSQL injection in profile lookup | ||
- Location: `auth_service/routes.py` - `/login` endpoint | ||
- Location: `profile_service/routes.py` - `/profile` endpoint | ||
|
||
### 4. Insecure Design (A04:2021) | ||
- No rate limiting on login attempts | ||
- Password reset without verification | ||
- Location: `auth_service/routes.py` - all endpoints | ||
|
||
### 5. Security Misconfiguration (A05:2021) | ||
- Debug mode enabled | ||
- Default/weak credentials | ||
- Location: `config.py` - all configuration settings | ||
|
||
### 6. Vulnerable Components (A06:2021) | ||
- Outdated dependencies in requirements.txt | ||
- Known vulnerable versions of packages | ||
|
||
### 7. Authentication Failures (A07:2021) | ||
- Weak password requirements | ||
- Session tokens without expiry | ||
- Location: `auth_service/utils.py` - `validate_password()` function | ||
|
||
### 8. Software and Data Integrity Failures (A08:2021) | ||
- No integrity checks on uploaded files | ||
- Unsecured deserialization | ||
- Location: `profile_service/routes.py` - `/upload` endpoint | ||
|
||
### 9. Security Logging Failures (A09:2021) | ||
- No logging of security events | ||
- Sensitive data in logs | ||
- Location: Both services lack proper logging | ||
|
||
### 10. Server-Side Request Forgery (A10:2021) | ||
- Unvalidated URL inputs | ||
- Location: `profile_service/routes.py` - `/fetch-avatar` endpoint | ||
|
||
## Setup Instructions | ||
|
||
1. Create virtual environment: | ||
```bash | ||
python -m venv venv | ||
source venv/bin/activate # Linux/Mac | ||
venv\Scripts\activate # Windows | ||
``` | ||
|
||
2. Install dependencies: | ||
```bash | ||
pip install -r requirements.txt | ||
``` | ||
|
||
3. Set up MongoDB: | ||
- Use local MongoDB instance or | ||
- Create free MongoDB Atlas cluster | ||
|
||
4. Configure environment: | ||
```bash | ||
cp .env.example .env | ||
# Edit .env with your MongoDB URI | ||
``` | ||
|
||
5. Run services: | ||
```bash | ||
# Terminal 1 | ||
python auth_service/app.py | ||
|
||
# Terminal 2 | ||
python profile_service/app.py | ||
``` | ||
|
||
## Testing Vulnerabilities | ||
|
||
1. SQL Injection: | ||
``` | ||
Username: admin' OR '1'='1 | ||
Password: anything | ||
``` | ||
|
||
2. NoSQL Injection: | ||
```javascript | ||
{"$gt": ""} in username field | ||
``` | ||
|
||
3. Weak Passwords: | ||
``` | ||
Any password with length > 1 is accepted | ||
``` | ||
|
||
4. SSRF Test: | ||
``` | ||
/fetch-avatar?url=file:///etc/passwd | ||
``` | ||
|
||
Comment on lines
+94
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance testing instructions with safety measures and expected outcomes The testing instructions should include:
## Testing Vulnerabilities
+
+> ⚠️ SAFETY FIRST:
+> - Use only in isolated development environment
+> - Never test these payloads against real systems
+> - Clean up test data after experiments
1. SQL Injection:
-```
+```sql
Username: admin' OR '1'='1
Password: anything +Expected outcome: Unauthorized access to admin account
{"$gt": ""} in username field +Expected outcome: Bypass authentication
+Expected outcome: Unauthorized file read
|
||
## Automated Testing | ||
Run security scanners against http://localhost:5000 and http://localhost:5001 to detect vulnerabilities. | ||
|
||
## Disclaimer | ||
This application is for educational purposes only. It contains intentional security vulnerabilities to demonstrate common security issues. DO NOT use any of this code in production environments. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Dockerfile for auth-service | ||
FROM node:14 | ||
|
||
WORKDIR /app | ||
COPY package.json package-lock.json ./ | ||
RUN npm install | ||
|
||
COPY . . | ||
CMD ["node", "server.js"] |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,91 @@ | ||||||||||||||||||||||||
from flask import Flask, request, jsonify | ||||||||||||||||||||||||
from flask_cors import CORS # Import CORS | ||||||||||||||||||||||||
from utils import hash_password, generate_token | ||||||||||||||||||||||||
import sqlite3 | ||||||||||||||||||||||||
import json | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
app = Flask(__name__) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
# Enable CORS for all routes | ||||||||||||||||||||||||
CORS(app) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
app.config['DEBUG'] = True | ||||||||||||||||||||||||
app.secret_key = 'xuysoe54Puj990' | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid hardcoding secret keys; use environment variables instead Hardcoding secret keys in the source code can lead to security vulnerabilities if the code is exposed. It's safer to load the secret key from an environment variable or a configuration file not checked into version control. Apply this change to use an environment variable for the secret key: -import json
+import os
app = Flask(__name__)
# Enable CORS for all routes
CORS(app)
app.config['DEBUG'] = True
-app.secret_key = 'xuysoe54Puj990'
+app.secret_key = os.environ.get('SECRET_KEY', 'your-default-secret-key') Ensure that the
🧰 Tools🪛 Gitleaks (8.21.2)13-13: Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (generic-api-key) |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
@app.route('/', methods=['GET']) | ||||||||||||||||||||||||
def entry(): | ||||||||||||||||||||||||
return jsonify({"error": "Invalid credentials"}), 401 | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@app.route('/login', methods=['POST']) | ||||||||||||||||||||||||
def login(): | ||||||||||||||||||||||||
data = request.get_json() | ||||||||||||||||||||||||
username = data.get('username') | ||||||||||||||||||||||||
password = data.get('password') | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
# Verify user credentials (hash password comparison) | ||||||||||||||||||||||||
query = f"SELECT * FROM users WHERE username='{username}' AND password='{hash_password(password)}'" | ||||||||||||||||||||||||
conn = sqlite3.connect('users.db') | ||||||||||||||||||||||||
cursor = conn.cursor() | ||||||||||||||||||||||||
user = cursor.execute(query).fetchone() | ||||||||||||||||||||||||
Comment on lines
+26
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent SQL Injection by using parameterized queries in login The login query concatenates user input directly into the SQL statement, making the application vulnerable to SQL injection attacks. Using parameterized queries protects against this vulnerability. Apply this diff to use parameterized queries: - query = f"SELECT * FROM users WHERE username='{username}' AND password='{hash_password(password)}'"
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
- user = cursor.execute(query).fetchone()
+ cursor.execute(
+ "SELECT * FROM users WHERE username=? AND password=?",
+ (username, hash_password(password))
+ )
+ user = cursor.fetchone() 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
if user: | ||||||||||||||||||||||||
# Generate JWT token | ||||||||||||||||||||||||
token = generate_token(username) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
# Decode token to string and return | ||||||||||||||||||||||||
return jsonify({"token": token.decode('utf-8')}) # Decode bytes to string | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unnecessary decode when returning JWT token The Apply this diff to fix the return statement: - return jsonify({"token": token.decode('utf-8')}) # Decode bytes to string
+ return jsonify({"token": token}) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||
return jsonify({"error": "Invalid credentials"}), 401 | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
@app.route('/register', methods=['POST']) | ||||||||||||||||||||||||
def register(): | ||||||||||||||||||||||||
data = request.get_json() | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if len(data.get('password', '')) > 1: | ||||||||||||||||||||||||
hashed_password = hash_password(data['password']) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
# Create connection to SQLite database | ||||||||||||||||||||||||
conn = sqlite3.connect('users.db') | ||||||||||||||||||||||||
cursor = conn.cursor() | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
# Ensure the users table is created if it does not exist | ||||||||||||||||||||||||
cursor.execute(''' | ||||||||||||||||||||||||
CREATE TABLE IF NOT EXISTS users ( | ||||||||||||||||||||||||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||||||||||||||||||||||
username TEXT UNIQUE NOT NULL, | ||||||||||||||||||||||||
password TEXT NOT NULL | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
''') | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
try: | ||||||||||||||||||||||||
# Use parameterized queries to prevent SQL injection | ||||||||||||||||||||||||
cursor.execute( | ||||||||||||||||||||||||
'INSERT INTO users (username, password) VALUES (?, ?)', | ||||||||||||||||||||||||
(data['username'], hashed_password) | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
conn.commit() | ||||||||||||||||||||||||
return jsonify({"message": "User registered successfully"}) | ||||||||||||||||||||||||
except sqlite3.IntegrityError: | ||||||||||||||||||||||||
# Handle unique constraint violation (duplicate username) | ||||||||||||||||||||||||
return jsonify({"error": "Username already exists"}), 400 | ||||||||||||||||||||||||
finally: | ||||||||||||||||||||||||
conn.close() | ||||||||||||||||||||||||
else: | ||||||||||||||||||||||||
return jsonify({"error": "Invalid password"}), 400 | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@app.route('/api/user/<id>', methods=['GET']) | ||||||||||||||||||||||||
def get_user(id): | ||||||||||||||||||||||||
conn = sqlite3.connect('users.db') | ||||||||||||||||||||||||
cursor = conn.cursor() | ||||||||||||||||||||||||
user = cursor.execute(f"SELECT * FROM users WHERE id={id}").fetchone() | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevent SQL Injection by using parameterized queries in The Apply this diff to use parameterized queries: - user = cursor.execute(f"SELECT * FROM users WHERE id={id}").fetchone()
+ cursor.execute("SELECT * FROM users WHERE id=?", (id,))
+ user = cursor.fetchone() 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
if user: | ||||||||||||||||||||||||
return jsonify({ | ||||||||||||||||||||||||
"id": user[0], | ||||||||||||||||||||||||
"username": user[1], | ||||||||||||||||||||||||
"password_hash": user[2] | ||||||||||||||||||||||||
}) | ||||||||||||||||||||||||
return jsonify({"error": "User not found"}), 404 | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
if __name__ == '__main__': | ||||||||||||||||||||||||
app.run(port=5000, debug=True) |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,15 @@ | ||||||||||||||||||||||||||||
import hashlib | ||||||||||||||||||||||||||||
import jwt | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def hash_password(password): | ||||||||||||||||||||||||||||
return hashlib.md5(password.encode()).hexdigest() | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a stronger password hashing algorithm The Consider using the -import hashlib
+from werkzeug.security import generate_password_hash
-def hash_password(password):
- return hashlib.md5(password.encode()).hexdigest()
+def hash_password(password):
+ return generate_password_hash(password) You'll need to install pip install Werkzeug Also, update the verification logic accordingly where passwords are checked. |
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def generate_token(username): | ||||||||||||||||||||||||||||
return jwt.encode( | ||||||||||||||||||||||||||||
{'username': username}, | ||||||||||||||||||||||||||||
'xuysoe54Puj990', | ||||||||||||||||||||||||||||
algorithm='HS256' | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
Comment on lines
+8
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid hardcoding secret keys in Hardcoding the secret key within the function is a security risk. Use an environment variable to store the secret key securely. Apply this change to use an environment variable: -import jwt
+import jwt
+import os
def generate_token(username):
return jwt.encode(
{'username': username},
- 'xuysoe54Puj990',
+ os.environ.get('SECRET_KEY', 'your-default-secret-key'),
algorithm='HS256'
) Ensure the 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def validate_password(password): | ||||||||||||||||||||||||||||
return len(password) > 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
version: '3.8' | ||
services: | ||
frontend: | ||
build: | ||
context: ./frontend | ||
dockerfile: Dockerfile | ||
ports: | ||
- "3000:3000" | ||
environment: | ||
- REACT_APP_AUTH_SERVICE_URL=http://auth-service:5000 | ||
- REACT_APP_PROFILE_SERVICE_URL=http://profile-service:5001 | ||
depends_on: | ||
- auth-service | ||
- profile-service | ||
auth-service: | ||
build: | ||
context: ./auth_service | ||
dockerfile: Dockerfile | ||
ports: | ||
- "5000:5000" | ||
environment: | ||
- MONGO_URI=mongodb://mongo:27017/ | ||
depends_on: | ||
- mongo | ||
profile-service: | ||
build: | ||
context: ./profile_service | ||
dockerfile: Dockerfile | ||
ports: | ||
- "5001:5001" | ||
environment: | ||
- MONGO_URI=mongodb://mongo:27017/ | ||
depends_on: | ||
- mongo | ||
mongo: | ||
image: mongo:4.4 | ||
ports: | ||
- "27017:27017" | ||
volumes: | ||
- mongodb_data:/data/db | ||
volumes: | ||
mongodb_data: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Dockerfile for frontend | ||
FROM node:14 | ||
|
||
WORKDIR /app | ||
COPY package.json package-lock.json ./ | ||
RUN npm install | ||
|
||
COPY . . | ||
RUN npm run build | ||
|
||
CMD ["npm", "start"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance setup instructions with security considerations
While the setup instructions are functional, consider adding: