Brute Force Attack in Flask with Cockroachdb
Brute Force Attack in Flask with Cockroachdb — how this specific combination creates or exposes the vulnerability
A brute force attack against a Flask application using CockroachDB typically involves repeated authentication requests to guess user credentials. Because Flask does not enforce built-in account lockout or rate limiting by default, an attacker can send many password guesses per second. CockroachDB, while resilient to outages and designed for distributed consistency, does not prevent application-layer authentication logic from being weak. If Flask routes accept unlimited login attempts and the application does not enforce escalating delays or lockouts, the database becomes an implicit target for high-volume credential guessing.
The exposure is often amplified by common patterns such as querying users by username without constant-time comparison, enabling timing-based side channels. For example, a naive query like User.query.filter_by(username=username).first() may return slightly faster when the username does not exist, allowing an attacker to enumerate valid accounts even before attempting passwords. Because CockroachDB returns rows across distributed nodes with strong consistency, response times may vary under load, which can unintentionally signal the presence or absence of a user if the application does not normalize timing.
Additionally, if session tokens or password reset tokens are predictable or leaked via logs, an attacker can pivot to token brute force. Without protections such as per-user salting, secure password hashing, and token entropy validation, the CockroachDB backend may store secrets in a way that compounds the risk. Flaws in rate limiting, combined with weak token handling, make the API endpoint a practical target for automated brute force and credential stuffing.
Cockroachdb-Specific Remediation in Flask — concrete code fixes
Remediation focuses on authentication hardening, constant-time checks, and robust session/token handling. Below are concrete Flask code examples using CockroachDB with SQLAlchemy that implement key protections.
1. Constant-time user existence check
Prevent timing leaks by ensuring the operation takes the same time regardless of whether the user exists. Use a fixed-duration dummy hash comparison when the user is not found.
import time
import secrets
from flask import Flask, request, jsonify
from sqlalchemy import create_engine, text
import bcrypt
app = Flask(__name__)
DATABASE_URL = 'cockroachdb://root@localhost:26257/defaultdb?sslmode=require'
engine = create_engine(DATABASE_URL)
def get_user_hash(username: str):
with engine.connect() as conn:
result = conn.execute(
text('SELECT password_hash FROM users WHERE username = :username'),
{'username': username}
).fetchone()
return result.password_hash if result else None
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username', '')
password = request.json.get('password', '').encode('utf-8')
stored = get_user_hash(username)
dummy_hash = bcrypt.hashpw(b'x' * 32, bcrypt.gensalt())
# Always run a hash comparison to normalize timing
if stored:
bcrypt.checkpw(password, stored.encode('utf-8'))
else:
bcrypt.checkpw(password, dummy_hash)
# Implement rate limiting and lockout outside this snippet
return jsonify({'ok': True})
2. Parameterized queries and prepared statements
Always use parameterized SQL to prevent injection and ensure stable execution plans on CockroachDB.
with engine.connect() as conn:
result = conn.execute(
text('SELECT id, password_hash FROM users WHERE username = :username'),
{'username': username}
)
user = result.fetchone()
3. Account lockout and exponential backoff
Track failed attempts per username and enforce increasing delays. Store counters in CockroachDB with upserts to avoid race conditions.
with engine.connect() as conn:
conn.execute(text('''
INSERT INTO login_attempts (username, attempts, last_attempt_at)
VALUES (:username, 1, NOW())
ON CONFLICT (username) DO UPDATE SET
attempts = login_attempts.attempts + 1,
last_attempt_at = NOW()
RETURNING attempts
'''), {'username': username})
attempts = conn.scalar()
if attempts and attempts >= 5:
delay = min(2 ** (attempts - 5), 60)
time.sleep(delay)
4. Secure password storage and token handling
Use strong adaptive hashing and protect tokens with high entropy. Avoid reversible encryption for credentials.
password_hash = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# Store password_hash in CockroachDB as TEXT
import secrets
token = secrets.token_urlsafe(32)
# Store token hash in CockroachDB, never store plaintext tokens
5. Rate limiting at the application or gateway layer
Use Flask-compatible middleware or an external rate limiter; do not rely solely on CockroachDB for request throttling.
# Example using Flask-Limiter with any backend storage
from flask_limiter import Limiter
limiter = Limiter(app=app, key_func=lambda: request.json.get('username', request.remote_addr))
@app.route('/login', methods=['POST'])
@limiter.limit('5/minute')
def login_limited():
...
6. Input validation and safe consumption patterns
Validate and sanitize inputs before constructing queries, and prefer strict ORM mappings to reduce injection surface.
from marshmallow import Schema, fields, ValidationError
class LoginSchema(Schema):
username = fields.Str(required=True)
password = fields.Str(required=True)
try:
data = LoginSchema().load(request.json)
except ValidationError:
return jsonify({'error': 'invalid input'}), 400