We will examine in detail the most critical web application security risks identified by OWASP (Open Web Application Security Project). These risks represent the most common and devastating security vulnerabilities in modern web applications.
1. Injection
SQL, NoSQL, OS, and LDAP injection attacks occur when untrusted data is interpreted as commands or queries. These types of attacks can lead to data loss, data corruption, or unauthorized access.
SQL Injection Example
Below you can see insecure and secure code examples:
// Insecure code example - Vulnerable to SQL Injection
username = request.get('username')
password = request.get('password')
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)
// Attacker can send the following value:
// username: admin' OR '1'='1' --
// In this case, the query becomes:
// SELECT * FROM users WHERE username = 'admin' OR '1'='1' --' AND password = '...'
// Secure code example - Using parameterized queries
query = "SELECT * FROM users WHERE username = ? AND password = ?"
cursor.execute(query, (username, password))
NoSQL Injection
NoSQL databases like MongoDB can also be vulnerable to injection attacks:
\\ Insecure MongoDB query
const user = db.users.findOne({
username: req.body.username,
password: req.body.password
});
\\ Attacker can send the following JSON:
\\ { "username": {"$ne": null}, "password": {"$ne": null} }
\\ Secure usage
const user = db.users.findOne({
username: String(req.body.username),
password: String(req.body.password)
});
2. Broken Authentication
When authentication and session management application functions are implemented incorrectly, attackers can impersonate other users or compromise session information.
Common Mistakes and Solutions
Problem | Risk | Solution |
---|---|---|
Weak password policy | Brute force attacks | Minimum 12 characters, special characters required |
Predictable Session ID | Session hijacking | Cryptographically secure random IDs |
No session timeout | Unauthorized access | Auto logout after 15-30 minutes of inactivity |
Secure Authentication Implementation
import bcrypt
import secrets
from datetime import datetime, timedelta
class SecureAuth:
def hash_password(self, password):
// Hash password securely
salt = bcrypt.gensalt()
return bcrypt.hashpw(password.encode('utf-8'), salt)
def verify_password(self, password, hashed):
// Verify password
return bcrypt.checkpw(password.encode('utf-8'), hashed)
def generate_session_token(self):
// Generate secure session token
return secrets.token_urlsafe(32)
def create_session(self, user_id):
token = self.generate_session_token()
expiry = datetime.utcnow() + timedelta(hours=2)
// Save session to database
session = {
'token': token,
'user_id': user_id,
'created_at': datetime.utcnow(),
'expires_at': expiry,
'ip_address': request.remote_addr,
'user_agent': request.headers.get('User-Agent')
}
return session
3. Sensitive Data Exposure
Security vulnerability that occurs when sensitive data (credit card information, personal data, passwords) is not adequately protected.
"Use encryption to protect your data, but also store your encryption keys securely. A chain is only as strong as its weakest link."
Data Protection Principles
- Data Minimization: Only collect the data you need
- Encryption: Encrypt data both in transit and at rest
- Key Management: Store encryption keys securely
- Data Classification: Know which data is sensitive
Encryption Implementation Example
from cryptography.fernet import Fernet
import os
import base64
class DataEncryption:
def __init__(self):
// Generate or load encryption key
self.key = self.get_or_create_key()
self.cipher = Fernet(self.key)
def get_or_create_key(self):
key_file = 'encryption.key'
if os.path.exists(key_file):
with open(key_file, 'rb') as file:
return file.read()
else:
key = Fernet.generate_key()
with open(key_file, 'wb') as file:
file.write(key)
return key
def encrypt_data(self, data):
if isinstance(data, str):
data = data.encode()
encrypted = self.cipher.encrypt(data)
return base64.urlsafe_b64encode(encrypted).decode()
def decrypt_data(self, encrypted_data):
encrypted_bytes = base64.urlsafe_b64decode(encrypted_data.encode())
decrypted = self.cipher.decrypt(encrypted_bytes)
return decrypted.decode()
// Usage example
encryptor = DataEncryption()
sensitive_data = "Credit Card: 1234-5678-9012-3456"
encrypted = encryptor.encrypt_data(sensitive_data)
print(f"Encrypted: {encrypted}")
decrypted = encryptor.decrypt_data(encrypted)
print(f"Decrypted: {decrypted}")
4. XML External Entities (XXE)
Security vulnerability that occurs when misconfigured XML processors process external entity references.
<"!-- Dangerous XML example --">
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>
<user>&xxe;</user>
</data>
XXE Protection Methods
import defusedxml.ElementTree as ET
// Safe XML parsing
def safe_parse_xml(xml_string):
try:
// defusedxml library provides protection against XXE attacks
tree = ET.fromstring(xml_string)
return tree
except ET.ParseError as e:
logger.error(f"XML parsing error: {e}")
return None
// Alternative: Use JSON instead of XML
import json
def parse_json_safely(json_string):
try:
data = json.loads(json_string)
// JSON is naturally secure against XXE
return data
except json.JSONDecodeError as e:
logger.error(f"JSON parsing error: {e}")
return None
5. Broken Access Control
Access control vulnerabilities occur when users can act outside of their intended permissions. This can result in unauthorized information disclosure, modification, or destruction.
Common Access Control Failures
- Vertical Privilege Escalation: User gains admin privileges
- Horizontal Privilege Escalation: User accesses other users' data
- IDOR (Insecure Direct Object References): Direct access via URL manipulation
- Missing Function Level Access Control: API endpoints without proper checks
Vulnerable Code Example
// Vulnerable - No access control check
@app.route('/api/user/<int:user_id>/profile')
def get_user_profile(user_id):
user = User.query.get(user_id)
return jsonify(user.to_dict())
// Attacker can access any user's profile:
// GET /api/user/123/profile
// GET /api/user/456/profile
// Secure - Proper access control
@app.route('/api/user/<int:user_id>/profile')
@login_required
def get_user_profile(user_id):
// Check if current user can access this profile
if current_user.id != user_id and not current_user.is_admin:
abort(403) // Forbidden
user = User.query.get_or_404(user_id)
return jsonify(user.to_dict())
6. Security Misconfiguration
Security misconfiguration occurs when security settings are not properly defined, implemented, or maintained. This includes insecure default configurations, incomplete or ad-hoc configurations, open cloud storage, misconfigured HTTP headers.
Common Misconfigurations
Component | Misconfiguration | Impact |
---|---|---|
Web Server | Default credentials, unnecessary services enabled | Full server compromise |
Database | Default passwords, open to internet | Data breach |
Cloud Storage | Public read/write permissions | Data exposure |
HTTP Headers | Missing security headers | XSS, Clickjacking attacks |
Security Headers Implementation
from flask import Flask, make_response
app = Flask(__name__)
@app.after_request
def set_security_headers(response):
// Prevent XSS attacks
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
// HTTPS enforcement
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
// Content Security Policy
response.headers['Content-Security-Policy'] = "default-src 'self'"
// Referrer Policy
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
return response
// Environment-specific configuration
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key-change-in-production'
DEBUG = False
TESTING = False
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
// Never use debug mode in production!
DEBUG = False
// Use strong secret key from environment
SECRET_KEY = os.environ.get('SECRET_KEY')
if not SECRET_KEY:
raise ValueError("No SECRET_KEY set for production!")
7. Cross-Site Scripting (XSS)
XSS flaws occur when an application includes untrusted data in a new web page without proper validation or escaping. XSS allows attackers to execute scripts in the victim's browser which can hijack user sessions, deface web sites, or redirect users.
Types of XSS
- Reflected XSS: Malicious script echoed back from web server
- Stored XSS: Malicious script stored in database/file
- DOM-based XSS: Vulnerability exists in client-side code
XSS Examples and Prevention
\\ Vulnerable code - Reflected XSS
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Search results for: ${query}</h1>`);
\\ Attacker can use: /search?q=<script>alert('XSS')</script>
});
\\ Secure code - Proper escaping
const escapeHtml = (unsafe) => {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q);
res.send(`<h1>Search results for: ${query}</h1>`);
});
\\ Using template engine with auto-escaping (Recommended)
app.set('view engine', 'ejs');
app.get('/search', (req, res) => {
res.render('search', { query: req.query.q }); \\ EJS auto-escapes by default \\
});
Client-Side XSS Prevention
\\ Vulnerable DOM manipulation
function displayUserComment(comment) {
document.getElementById('comments').innerHTML += '<div>' + comment + '</div>';
}
\\ Secure DOM manipulation
function displayUserCommentSafe(comment) {
const div = document.createElement('div');
div.textContent = comment; \\textContent automatically escapes\\
document.getElementById('comments').appendChild(div);
}
\\ Input validation and sanitization
function sanitizeInput(input) {
return input
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
\\ Modern framework safe patterns
function createSafeElement(tag, content) {
const element = document.createElement(tag);
element.textContent = content; \\Safe from XSS\\
return element;
}
8. Insecure Deserialization
Insecure deserialization often leads to remote code execution. Even if deserialization flaws do not result in remote code execution, they can be used to perform attacks including replay attacks, injection attacks, and privilege escalation.
Vulnerable Deserialization Example
import pickle
import base64
// Vulnerable code - Never deserialize untrusted data!
def load_user_session(session_data):
try:
// Dangerous! Can execute arbitrary code
session = pickle.loads(base64.b64decode(session_data))
return session
except:
return None
// Attacker can create malicious pickle payload:
class MaliciousPayload:
def __reduce__(self):
import os
return (os.system, ('rm -rf /',)) // Dangerous payload
// Safe alternatives
import json
import hmac
import hashlib
class SecureSessionManager:
def __init__(self, secret_key):
self.secret_key = secret_key
def create_session(self, user_data):
// Use JSON for safe serialization
session_json = json.dumps(user_data)
// Create HMAC signature
signature = hmac.new(
self.secret_key.encode(),
session_json.encode(),
hashlib.sha256
).hexdigest()
return base64.b64encode(f"{session_json}:{signature}".encode())
def load_session(self, session_data):
try:
decoded = base64.b64decode(session_data).decode()
session_json, signature = decoded.rsplit(':', 1)
// Verify signature
expected_sig = hmac.new(
self.secret_key.encode(),
session_json.encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_sig):
return None
return json.loads(session_json)
except:
return None
9. Using Components with Known Vulnerabilities
Components such as libraries, frameworks, and other software modules run with the same privileges as the application. If a vulnerable component is exploited, such an attack can facilitate serious data loss or server takeover.
Component Security Best Practices
- Inventory Management: Maintain a complete inventory of all components
- Regular Updates: Keep all components up to date
- Vulnerability Scanning: Regularly scan for known vulnerabilities
- Security Monitoring: Monitor security advisories for your components
- Remove Unused Components: Remove unnecessary dependencies
Automated Vulnerability Scanning
// Python - Check for known vulnerabilities
pip install safety
safety check
// Alternative: Use pip-audit
pip install pip-audit
pip-audit
// Node.js - Check for vulnerabilities
npm audit
npm audit fix
// Generate detailed report
npm audit --json > audit-report.json
// Java - Use OWASP Dependency Check
mvn org.owasp:dependency-check-maven:check
// Docker - Scan container images
docker run --rm -v $(pwd):/app clair-scanner:latest
Dependency Management Example
// GitHub Actions - Automated dependency scanning
name: Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: 'trivy-results.sarif'
// requirements.txt with version pinning
Flask==2.3.2 /* Pin to specific secure version*/
Werkzeug==2.3.4
Jinja2==3.1.2
// Avoid using outdated versions like Flask==1.0.0
10. Insufficient Logging & Monitoring
Insufficient logging and monitoring, coupled with missing or ineffective integration with incident response, allows attackers to further attack systems, maintain persistence, pivot to more systems, and tamper, extract, or destroy data.
What to Log
- Authentication Events: Login attempts, password changes
- Authorization Failures: Access denied events
- High-Value Transactions: Financial transactions, data exports
- Security Events: Failed validation, suspicious patterns
- System Events: Application start/stop, configuration changes
Secure Logging Implementation
import logging
import json
from datetime import datetime
from flask import request, g
// Configure structured logging
logging.basicConfig(
level=logging.INFO,
format='%(message)s'
)
class SecurityLogger:
def __init__(self):
self.logger = logging.getLogger('security')
handler = logging.FileHandler('security.log')
handler.setFormatter(logging.Formatter('%(message)s'))
self.logger.addHandler(handler)
def log_security_event(self, event_type, user_id=None, ip_address=None,
details=None, severity='INFO'):
log_entry = {
'timestamp': datetime.utcnow().isoformat(),
'event_type': event_type,
'user_id': user_id,
'ip_address': ip_address or request.remote_addr,
'user_agent': request.headers.get('User-Agent'),
'severity': severity,
'details': details
}
self.logger.info(json.dumps(log_entry))
// Usage examples
security_logger = SecurityLogger()
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
user = authenticate(username, password)
if user:
security_logger.log_security_event(
'LOGIN_SUCCESS',
user_id=user.id,
details={'username': username}
)
return jsonify({'status': 'success'})
else:
security_logger.log_security_event(
'LOGIN_FAILURE',
details={'username': username, 'reason': 'invalid_credentials'},
severity='WARNING'
)
return jsonify({'error': 'Invalid credentials'}), 401
@app.route('/admin/users/<int:user_id>')
@admin_required
def get_user(user_id):
if not current_user.is_admin:
security_logger.log_security_event(
'UNAUTHORIZED_ACCESS_ATTEMPT',
user_id=current_user.id,
details={'attempted_resource': f'/admin/users/{user_id}'},
severity='HIGH'
)
abort(403)
// Log high-privilege access
security_logger.log_security_event(
'ADMIN_ACCESS',
user_id=current_user.id,
details={'accessed_user': user_id}
)
return jsonify(User.query.get(user_id).to_dict())
Log Analysis with ELK Stack
// docker-compose.yml for ELK Stack
version: '3.7'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.14.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
logstash:
image: docker.elastic.co/logstash/logstash:7.14.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
depends_on:
- elasticsearch
kibana:
image: docker.elastic.co/kibana/kibana:7.14.0
ports:
- "5601:5601"
depends_on:
- elasticsearch
// logstash.conf
input {
file {
path => "/var/log/security.log"
start_position => "beginning"
codec => "json"
}
}
filter {
if [severity] == "HIGH" or [severity] == "CRITICAL" {
mutate {
add_tag => ["alert"]
}
}
// Parse IP geolocation
geoip {
source => "ip_address"
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "security-logs-%{+YYYY.MM.dd}"
}
}
Real-time Monitoring Alerts
import smtplib
from email.mime.text import MIMEText
from collections import defaultdict
from datetime import datetime, timedelta
class SecurityMonitor:
def __init__(self):
self.failed_attempts = defaultdict(list)
self.alert_threshold = 5 *5 failed attempts
self.time_window = timedelta(minutes=10)
def check_brute_force(self, ip_address, event_type):
if event_type == 'LOGIN_FAILURE':
now = datetime.utcnow()
// Clean old attempts
self.failed_attempts[ip_address] = [
attempt for attempt in self.failed_attempts[ip_address]
if now - attempt < self.time_window
]
// Add current attempt
self.failed_attempts[ip_address].append(now)
// Check if threshold exceeded
if len(self.failed_attempts[ip_address]) >= self.alert_threshold:
self.send_alert(
f"Brute force attack detected from IP: {ip_address}",
f"Multiple failed login attempts detected"
)
return True
return False
def send_alert(self, subject, message):
// Send email alert
msg = MIMEText(message)
msg['Subject'] = f"[SECURITY ALERT] {subject}"
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'
// Also log the alert
security_logger.log_security_event(
'SECURITY_ALERT_SENT',
details={'subject': subject, 'message': message},
severity='CRITICAL'
)
// Integration with Flask app
monitor = SecurityMonitor()
@app.after_request
def security_monitoring(response):
if hasattr(g, 'security_event'):
event = g.security_event
monitor.check_brute_force(
request.remote_addr,
event.get('event_type')
)
return response
Summary and Next Steps
The OWASP Top 10 represents the most critical web application security risks. As a security professional, you should:
- Stay Updated: OWASP Top 10 is updated every 3-4 years
- Implement Defense in Depth: Use multiple layers of security
- Security Testing: Regular penetration testing and code reviews
- Developer Training: Educate development teams on secure coding
- Incident Response: Have a plan ready for when attacks happen
"Security is a process, not a product. It's about good practices, awareness, and continuous improvement." - Bruce Schneier
Comments & Discussion
Excellent comprehensive guide! The code examples are particularly helpful. I've bookmarked this for reference during our security code reviews. The logging section (#10) is often overlooked but so important.
Great work on covering all 10 vulnerabilities with practical examples. The Broken Access Control section really helped me understand IDOR attacks better. Would love to see a follow-up on OWASP API Security Top 10!