What’s OWASP briefly
OWASP stands for Open Web Application Security Project, a non-profit organization that’s focused on web application security standards, tools, and methodologies. OWASP top 10 in its turn stands for Top-10 major and wide-spread security risks of web applications (doesn’t matter backend or frontend ones).
OWASP is a registered trademark of the OWASP foundation
Injections
An injection is an execution of a malicious statement forged for a specific system by a user, where the vulnerability is only possible because the service allows any information to be put in the system (database, OS, etc) without limitations.
Injection example
The classic example of SQL injection is the “Bobby DROP TABLE” meme, where a boy named
Robert’); DROP TABLE students;– destroys the school’s database merely by the power of its name.
INSERT INTO students (name) VALUES ('Robert');
-- turns into
INSERT INTO students (name) VALUES ('Robert'); DROP TABLE students;--');
What does this SQL injection mean?
The data sent through some school forms weren’t checked or parametrized and were executed by SQL database in a raw, pristine way. Robert’s name, consisting of special characters, ended a previous command and executed a new one `DROP TABLE ‘students’ destroying the entire database and, perhaps, Robert’s bad education results, too.
You can see that the command imitates a normal SQL syntax, so it’s executed unnoticed. It’s not necessarily a real Robert’s name or performed as a direct SQL expression, but could be an attack carried through an HTTP request. Let’s take a look at the cURL example:
curl -X PUT -d '{"name": "Robert\'); DROP TABLE students;--"}' https://school.com/user/Robert
// Notice that the quotation marks are escaped in the request's body
Injection attack vector
An injection may involve any system that works with external data and has access to internal data:
- SQL and NoSQL
- XML and JSON parsers
- Command-line/OS commands
- LDAP
- XPath
- SMTP headers
- Expression languages
- ORM queries (sometimes)
- Custom macros
Despite the attack may come from any user or service input, the main source of vulnerability is API requests. Users can modify HTTP requests and add special characters and commands resulting in data breaches, system failure, etc.
Injection tools
Must have injection tools:
- sqlmap is the most popular open-source penetration tool that detects and exploits possible SQL injection. When the target is cracked, the tool provides features to take over the database
- DSSS is a compact SQLi scanner that has all the required features
- NoSQLMap is abusing NoSQL databases such as MongoDB or CouchDB, allows exploring vulnerabilities and eventually copy the data
- bbqsql is an automated blind SQL injection framework
Tools that are no longer maintained but still might be used for education purposes:
- zeus-scanner is an advanced URL explorer that powers up sqlmap and provides other reconnaissance features
- whitewidow see ‘zeus-scanner’, but without ‘URL’ part
How to prevent Injection
- Parametrize your API: you must know exactly what you expect from your users
- Prefer ORMs to libraries with more freedom
- Don’t use dynamic queries
- If you have to, escape special characters
- Whitelist commands that are intended to be executed by OS, but dynamically selected by users
- Don’t blacklist commands, it never works as attacks become more and more sophisticated
For instance, a company has a service that allows an image to be scaled, cropped, filtered, etc
curl -X POST https://company.com/images/{imageId}/crop
curl -X POST https://company.com/images/{imageId}/scale
As you see, this API has at least 2 attack vectors:
imageId and
crop | scale
{imageId} may include harmful statements like
SELECT * FROM images WHERE image.id = {imageId}
If
imageId contains a special character, an attacker can execute any command, as in Booby’s example. In the case of IDs, you need to check that the ID consists of only numbers, characters, or special symbols you are safe with. You cannot whitelist all possible IDs, but you can check if the ID follows the structure you expect.
crop | scale has to be simply whitelisted, any attempt to call
curl -X POST https://company.com/images/{imageId}/;)
must end on API router side as in the Node.js example:
app.post('/images/:imageId/(crop|scale)', ( request, response ) => {
if (validate(request.params.imageId)) {
// ...
}
}
)
Broken Authentication and Session Management
Another common OWASP attack vector is everything related to incorrectly configured and/or implemented authentication. Despite it being called ‘broken’ it doesn’t really need to be one, the authentication is just not good enough. This kind of vulnerability implies that the system has an authentication process that involves the usage of login and password in any form.
At the moment when this article was published, most of the world’s passwords are still on the list
Broken Authentication example
An attacker has a list of users and a ‘most popular passwords’ dictionary. As a result, some users and passwords match, therefore, the attacker gets to secure part of the web application using stolen identities.
Broken Authentication attack vector
- An attacker knows that the login form requires the user’s login & password, then clearly shows that the user exists in the database, but the entered password is incorrect
- An attacker can use the login form without a two-factor authentication option
- An attacker knows that systems’ passwords are weak because the sign-up form doesn’t check the password’s complexity
- Admin credentials are the default ones for the systems involved (e.g., WordPress with a default password “password” and username “admin”), therefore, an attacker can use them
How to prevent authentication breaking
- Always use 2FA or at least provide a simple way to use one
- Change the default credentials of the libraries you use
- Use logs for failed login attempts and use automated software to analyze suspicious ones
- Never explain why a pair of login and password doesn’t work, ‘Incorrect login or password’ is good enough in 100% of cases
- Use highly entropy session tokens & don’t use them in URL’s pathname or query
- Limit life of session tokens, use authentication & refresh tokens
- Follow NIST password policy for passwords you allow users to create
- Check passwords using Have I been pwned or another popular password dictionary. “Have I been pwned” has just gone open source and has libraries for multiple languages
- Artificially delay login responses by a random number of seconds
Exposure of Sensitive Data
The simplest way to expose data is to provide it to an attacker straight away. It might be an open API, unprotected port, or data that’s not encrypted or encrypted with weak algorithms. This vulnerability occurs when applications don’t adequately protect sensitive information such as financial data, healthcare records, personal information, or authentication credentials.
Sensitive Data Exposure Examples
Unencrypted Database Storage:
-- Vulnerable: Storing passwords in plain text
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(100), -- Stored as plain text!
ssn VARCHAR(11) -- Social Security Number in plain text
);
INSERT INTO users VALUES (1, 'john_doe', 'mypassword123', '123-45-6789');
Weak Encryption Implementation:
// Vulnerable: Using deprecated MD5 for password hashing
const crypto = require('crypto');
const password = 'user_password';
const hash = crypto.createHash('md5').update(password).digest('hex');
Data Transmitted Over HTTP:
# Vulnerable: Sending sensitive data over unencrypted connection
curl -X POST http://bankapi.com/transfer \
-d '{"account": "12345", "amount": 10000, "to": "67890"}'
Sensitive Data Exposure Attack Vectors
- Man-in-the-Middle attacks on unencrypted HTTP connections
- Database breaches exposing unencrypted stored data
- Backup files containing sensitive information left on web servers
- Application logs containing passwords, API keys, or personal data
- Browser storage (localStorage, sessionStorage) with sensitive data
- URL parameters containing authentication tokens or personal information
- Error messages revealing database schemas or internal system information
How to Prevent Sensitive Data Exposure
Encrypt Data at Rest:
# Secure: Using bcrypt for password hashing
import bcrypt
password = "user_password".encode('utf-8')
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password, salt)
# Verify password
if bcrypt.checkpw(password, hashed):
print("Password matches!")
Use HTTPS Everywhere:
# Nginx configuration forcing HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
}
Proper Key Management:
- Use environment variables or secure vaults for API keys
- Rotate encryption keys regularly
- Never hardcode secrets in source code
- Implement proper access controls for sensitive data
XML External Entities (XXE)
XXE attacks occur when a weakly configured XML parser processes XML input containing a reference to an external entity. This vulnerability can lead to the disclosure of confidential data, denial of service, server-side request forgery, and other system impacts.
XXE Attack Example
Vulnerable XML Processing:
<!-- Malicious XML payload -->
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<foo>&xxe;</foo>
Vulnerable PHP Code:
// Vulnerable: XML parser with external entities enabled
$xml = file_get_contents('user_input.xml');
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD);
echo $dom->textContent; // This could output /etc/passwd content
XXE Attack Vectors
- File disclosure through
SSRF attacks using
protocols
- Port scanning of the internal network
- Denial of service through entity expansion attacks
- Remote code execution in some configurations
XXE Prevention Methods
Disable External Entities:
// Secure XML parsing in Java
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(inputStream);
Use Safe Parsing Libraries:
# Secure XML parsing in Python
from defusedxml import ElementTree as ET
# This automatically disables dangerous features
tree = ET.parse('input.xml')
root = tree.getroot()
Broken Access Control
Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification, or destruction of data, or performing business functions outside the user’s limits.
Broken Access Control Examples
Insecure Direct Object Reference:
// Vulnerable: No authorization check
app.get('/api/accounts/:accountId', (req, res) => {
const accountId = req.params.accountId;
const account = database.getAccount(accountId);
res.json(account); // Any user can access any account!
});
Missing Function Level Access Control:
// Vulnerable: Admin function accessible to all users
if ($_SESSION['logged_in']) {
// Missing role check!
if ($_GET['action'] == 'delete_user') {
deleteUser($_GET['user_id']);
}
}
Access Control Attack Vectors
- Vertical privilege escalation – accessing admin functions as regular user
- Horizontal privilege escalation – accessing other users’ data
- Bypassing access control through URL manipulation
- Force browsing to unauthorized pages
- CORS misconfiguration allowing unauthorized cross-origin requests
Access Control Prevention
Implement Proper Authorization:
// Secure: Check both authentication and authorization
app.get('/api/accounts/:accountId', authenticateUser, (req, res) => {
const accountId = req.params.accountId;
const userId = req.user.id;
// Check if user owns this account or is admin
if (!userOwnsAccount(userId, accountId) && !req.user.isAdmin) {
return res.status(403).json({ error: 'Access denied' });
}
const account = database.getAccount(accountId);
res.json(account);
});
Role-Based Access Control (RBAC):# Secure: Decorator-based access control
from functools import wraps
def require_role(required_role):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.has_role(required_role):
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
@app.route('/admin/users')
@require_role('admin')
def admin_users():
return render_template('admin_users.html')
Security Misconfiguration
Security misconfiguration is commonly a result of insecure default configurations, incomplete configurations, open cloud storage, misconfigured HTTP headers, and verbose error messages containing sensitive information.
Security Misconfiguration Examples
Default Credentials:
# Vulnerable: Using default database credentials
DB_HOST=localhost
DB_USER=admin
DB_PASSWORD=password # Default password!
DB_NAME=production
Exposed Debug Information:
# Vulnerable: Debug mode enabled in production
from flask import Flask
app = Flask(__name__)
app.config['DEBUG'] = True # Exposes sensitive information!
@app.route('/')
def home():
1/0 # This will show full stack trace to users
Missing Security Headers:
HTTP/1.1 200 OK
Content-Type: text/html
# Missing security headers:
# X-Frame-Options: DENY
# X-Content-Type-Options: nosniff
# X-XSS-Protection: 1; mode=block
# Strict-Transport-Security: max-age=31536000
Security Misconfiguration Prevention
Secure Configuration Management:
# Secure Docker configuration
version: '3.8'
services:
webapp:
image: myapp:latest
environment:
- NODE_ENV=production
- DEBUG=false
secrets:
- db_password
user: "1001:1001" # Non-root user
secrets:
db_password:
external: true
Implement Security Headers:// Secure: Adding security headers middleware
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
Cross-Site Scripting (XSS)
XSS flaws occur when an application includes untrusted data in a web page without proper validation or escaping, allowing attackers to execute malicious scripts in victims’ browsers.
XSS Attack Types and Examples
Stored XSS:
// Vulnerable: Directly inserting user content
app.post('/comments', (req, res) => {
const comment = req.body.comment;
database.saveComment(comment); // Malicious script stored in DB
});
// Later displayed without escaping:
comments.forEach(comment => {
html += `<div>${comment.text}</div>`; // XSS executed here!
});
Reflected XSS:
// Vulnerable: Reflecting user input without sanitization
$search = $_GET['search'];
echo "<p>You searched for: " . $search . "</p>";
// URL: site.com/search?search=<script>alert('XSS')</script>
DOM-based XSS:
// Vulnerable: Client-side DOM manipulation
const hash = window.location.hash;
document.getElementById('content').innerHTML = hash.substring(1);
// URL: site.com#<img src=x onerror=alert('XSS')>
XSS Prevention Methods
Output Encoding:
// Secure: Proper escaping of user content
const escapeHtml = (unsafe) => {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
// Usage
const safeComment = escapeHtml(userComment);
html += `<div>${safeComment}</div>`;
Content Security Policy (CSP):
Content-Security-Policy: default-src 'self';
script-src 'self' 'unsafe-inline';
object-src 'none';
base-uri 'self';
Input Validation:
# Secure: Validate and sanitize input
import bleach
allowed_tags = ['p', 'strong', 'em', 'br']
allowed_attributes = {}
def sanitize_html(content):
return bleach.clean(content, tags=allowed_tags, attributes=allowed_attributes)
clean_content = sanitize_html(user_input)
Insecure Deserialization
Insecure deserialization often leads to remote code execution. Even if deserialization flaws don’t result in remote code execution, they can lead to replay attacks, injection attacks, and privilege escalation.
Insecure Deserialization Example
Vulnerable Python Pickle:
import pickle
import base64
# Vulnerable: Deserializing untrusted data
user_data = request.get('user_data') # From cookie or POST data
decoded_data = base64.b64decode(user_data)
user_object = pickle.loads(decoded_data) # Dangerous!
Malicious Payload:
# Attacker creates malicious payload
import pickle
import os
class MaliciousPayload:
def __reduce__(self):
return (os.system, ('rm -rf /',))
# Serialize malicious object
malicious_data = pickle.dumps(MaliciousPayload())
encoded_payload = base64.b64encode(malicious_data)
Insecure Deserialization Prevention
Use Safe Serialization Formats:
# Secure: Use JSON instead of pickle
import json
# Safe serialization
user_data = {
'username': 'john_doe',
'preferences': {'theme': 'dark'}
}
safe_data = json.dumps(user_data)
# Safe deserialization
parsed_data = json.loads(safe_data)
Implement Integrity Checks:
# Secure: Sign serialized data
import hmac
import hashlib
import json
SECRET_KEY = 'your-secret-key'
def sign_data(data):
json_data = json.dumps(data)
signature = hmac.new(
SECRET_KEY.encode(),
json_data.encode(),
hashlib.sha256
).hexdigest()
return f"{json_data}.{signature}"
def verify_data(signed_data):
try:
data, signature = signed_data.rsplit('.', 1)
expected_sig = hmac.new(
SECRET_KEY.encode(),
data.encode(),
hashlib.sha256
).hexdigest()
if hmac.compare_digest(signature, expected_sig):
return json.loads(data)
return None
except ValueError:
return None
Using Components with Known Vulnerabilities
Many applications use libraries, frameworks, and modules with known vulnerabilities. Attackers can exploit these vulnerabilities to compromise the application.
Vulnerable Components Example
Outdated Dependencies:
{
"name": "my-app",
"dependencies": {
"express": "4.16.0", // Known vulnerabilities
"lodash": "4.17.4", // Known vulnerabilities
"mongoose": "4.13.0", // Known vulnerabilities
"jquery": "2.1.4" // Multiple XSS vulnerabilities
}
}
Using Vulnerable Component:
// Vulnerable: Using lodash template with user input
const _ = require('lodash');
app.post('/template', (req, res) => {
const template = req.body.template;
const compiled = _.template(template); // RCE vulnerability in old versions
const result = compiled({ name: 'World' });
res.send(result);
});
Component Vulnerability Prevention
Dependency Scanning:
# Use npm audit to check for vulnerabilities
npm audit
# Fix vulnerabilities automatically
npm audit fix
# Use tools like Snyk
npx snyk test
npx snyk wizard
Keep Dependencies Updated:
# Check for outdated packages
npm outdated
# Update to latest versions
npm update
# Use package-lock.json to ensure consistent versions
npm ci # Instead of npm install in production
Implement Dependency Management:
# GitHub Dependabot configuration
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
reviewers:
- "security-team"
assignees:
- "dev-team"
Insufficient Logging and Monitoring
Insufficient logging and monitoring, coupled with missing or ineffective integration with incident response, allow attackers to further attack systems, maintain persistence, pivot to more systems, and tamper, extract, or destroy data.
Insufficient Logging Examples
Missing Security Events:
// Vulnerable: No logging of security events
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (validateCredentials(username, password)) {
req.session.user = username;
res.json({ success: true });
} else {
res.status(401).json({ error: 'Invalid credentials' });
// No logging of failed login attempt!
}
});
Inadequate Log Information:
# Vulnerable: Generic logging without context
import logging
def transfer_money(from_account, to_account, amount):
try:
result = process_transfer(from_account, to_account, amount)
logging.info("Transfer completed") # Not enough information!
except Exception as e:
logging.error("Transfer failed") # No details about the failure!
Proper Logging and Monitoring Implementation
Security Event Logging:
// Secure: Comprehensive security logging
const winston = require('winston');
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'security.log' }),
new winston.transports.Console()
]
});
app.post('/login', (req, res) => {
const { username, password } = req.body;
const clientIP = req.ip;
const userAgent = req.get('User-Agent');
if (validateCredentials(username, password)) {
securityLogger.info('Login success', {
event: 'login_success',
username: username,
ip: clientIP,
userAgent: userAgent,
timestamp: new Date().toISOString()
});
req.session.user = username;
res.json({ success: true });
} else {
securityLogger.warn('Login failure', {
event: 'login_failure',
username: username,
ip: clientIP,
userAgent: userAgent,
timestamp: new Date().toISOString()
});
res.status(401).json({ error: 'Invalid credentials' });
}
});
Monitoring and Alerting:
# Secure: Monitoring with alerts
import logging
from datetime import datetime, timedelta
from collections import defaultdict
class SecurityMonitor:
def __init__(self):
self.failed_logins = defaultdict(list)
def log_failed_login(self, username, ip_address):
timestamp = datetime.now()
# Log the event
logging.warning(f"Failed login attempt", extra={
'event_type': 'failed_login',
'username': username,
'ip_address': ip_address,
'timestamp': timestamp.isoformat()
})
# Track for rate limiting
self.failed_logins[ip_address].append(timestamp)
# Clean old entries
cutoff = timestamp - timedelta(minutes=15)
self.failed_logins[ip_address] = [
t for t in self.failed_logins[ip_address] if t > cutoff
]
# Alert on suspicious activity
if len(self.failed_logins[ip_address]) > 5:
self.send_security_alert(f"Multiple failed logins from {ip_address}")
def send_security_alert(self, message):
# Integration with alerting system (email, Slack, PagerDuty, etc.)
logging.critical(f"SECURITY ALERT: {message}")
Essential Security Monitoring Checklist:
- Authentication events (login, logout, failed attempts)
- Authorization failures
- Input validation failures
- Application errors and exceptions
- Admin privilege usage
- Data access and modification
- Network anomalies
- File integrity changes