Login Rate Limiting against brute-force attacks

several bugfixes
Security Audit
This commit is contained in:
ChrispyBacon-dev 2025-09-26 20:17:37 +02:00
parent 43520222a1
commit b872096c77
8 changed files with 272 additions and 105 deletions

View file

@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Resolved multiple issues with the new OAuth feature, including `redirect_uri_mismatch` errors, a broken "Edit" button, and several backend bugs.
### Security
- **Login Rate Limiting:** Implemented rate limiting on the password login form (6 attempts per minute) to protect against brute-force attacks.
---
## [v3.0.0] - 2025-09-25

View file

@ -1,84 +0,0 @@
***Disclaimer:** This security audit was automatically generated by Google's Gemini 2.5 Pro model. While it aims to identify potential vulnerabilities and deviations from best practices by analyzing the codebase, it is not a substitute for a manual audit by a certified human security expert. All findings should be carefully reviewed and validated before any action is taken.*
---
# DockFlare Security Audit Report
**Date:** September 25, 2025
### **Overall Assessment:**
The DockFlare application is built with a strong awareness of security principles. The developers have correctly implemented many critical security controls, particularly around authentication, session management, and key generation. The overall security posture is good. However, several vulnerabilities and areas for improvement were identified during this audit.
---
## Detailed Audit Trail & Analysis
This section provides a detailed walkthrough of the audit process, mirroring the conversational analysis performed.
### 1. Initial Understanding & Dependency Analysis
* **Process:** The audit began by reviewing the `README.md` to understand the application's purpose and architecture. Subsequently, the Python dependencies in `dockflare/requirements.txt` and Node.js dependencies in `dockflare/package.json` were examined.
* **Analysis:**
* The Python dependencies (`Flask`, `cryptography`, `werkzeug`, `docker`, etc.) are relatively up-to-date for a project of this nature. No high-risk, outdated libraries were immediately apparent.
* The Node.js dependencies are all `devDependencies` related to frontend asset building (Tailwind CSS) and pose no direct runtime security risk to the deployed application.
* **Conclusion:** The project's dependencies do not present an immediate, obvious security risk.
### 2. Configuration and Secret Management
* **Process:** The investigation focused on `dockflare/app/config.py` and `dockflare/app/web/config_loader.py` to understand how application secrets are managed.
* **Analysis:**
* `config.py` correctly avoids hardcoding secrets. Sensitive variables like `CF_API_TOKEN` are initialized as `None` and loaded dynamically.
* `config_loader.py` reveals that secrets are stored in an encrypted file, `dockflare_config.dat`. The encryption uses `cryptography.fernet`, which is an excellent, secure choice (AES-128-GCM with HMAC-SHA256).
* The encryption key itself (`dockflare.key`) is stored as a separate file in the same `/app/data` directory.
* **Finding (Low Risk):** The security of all secrets is dependent on restricting read access to the `/app/data` volume. An attacker who gains filesystem access can read both the key and the encrypted data, defeating the encryption. This risk is mitigated by the use of a non-root container user, but it remains a single point of failure.
### 3. User Authentication & Session Management
* **Process:** The audit covered `dockflare/app/__init__.py`, `dockflare/app/web/auth_routes.py`, and `dockflare/app/web/setup_routes.py` to trace the full authentication lifecycle.
* **Analysis:**
* **Password Hashing:** `setup_routes.py` uses `werkzeug.security.generate_password_hash` to create the user's password hash during initial setup. `auth_routes.py` uses the corresponding `check_password_hash` to verify it. This is the correct and secure way to handle password storage.
* **Session Security:** `__init__.py` configures the Flask application's `secret_key` using `os.urandom(24)`. This is the best practice for session security, as it creates a new, unpredictable, and cryptographically strong key every time the application starts, making session cookie forgery impossible between restarts.
* **CSRF Protection:** `__init__.py` correctly initializes `Flask-WTF`'s `CSRFProtect`, applying sitewide protection against Cross-Site Request Forgery on forms.
* **Open Redirect:** `auth_routes.py` uses a custom `is_safe_url` function (defined in `web/utils.py`) to validate the `next` parameter for post-login redirects. The implementation correctly compares the hostname of the redirect target to the application's own hostname, effectively preventing open redirect attacks.
* **Conclusion:** The web UI's authentication and session management systems are robust and adhere to modern security standards.
### 4. API Security
* **Process:** The investigation focused on `dockflare/app/web/api_v2_routes.py` and the exemptions noted in `__init__.py`.
* **Analysis:**
* The V2 API blueprint is correctly exempted from the web UI's CSRF and session-based login systems.
* A `@before_request` hook, `_enforce_master_api_key`, protects all administrative API endpoints.
* This hook extracts a bearer token from the `Authorization` header and, crucially, uses `secrets.compare_digest` to validate it against the stored `MASTER_API_KEY`. Using a constant-time comparison function like this is essential to prevent timing attacks.
* A separate authentication flow (`_authenticate_agent_request`) exists for agent-specific endpoints, providing good separation of concerns.
* **Conclusion:** The API is secured with a well-implemented, token-based authentication mechanism that is separate from the UI's session login.
### 5. Docker Socket Interaction & Permissions
* **Process:** This involved analyzing the `docker-compose.yml` file's `docker-socket-proxy` configuration and the Docker API calls made in the codebase.
* **Analysis & Finding:**
* The application requires `exec` permissions on the Docker socket. Our investigation confirmed this is necessary for running `/usr/local/bin/cloudflared --version` inside the agent container to verify its status.
* The `docker-compose.yml` enables this with the `EXEC=1` environment variable. This setting allows the version check to work, but it's worth noting that it also grants permission to execute *any* command inside a container, not just the specific one required.
* An initial recommendation to use a more granular control like `EXEC_ALLOWED_CMDS` was explored.
* **Investigation Conclusion:** Further research confirmed that the `tecnativa/docker-socket-proxy` software **does not support this feature**. Access to the `/exec` API endpoint is all-or-nothing (either enabled with `EXEC=1` or disabled).
* **Consideration:** This presents a trade-off between functionality and security. To run the application as-is with the current proxy, `EXEC=1` must be enabled. The alternative for achieving a more granular, least-privilege setup would involve changing the proxy software to one that supports `exec` command whitelisting.
### 6. Web Frontend Security
* **Process:** The templates in `dockflare/app/templates/` were searched for misuse of the `|safe` filter, which disables Jinja2's default output escaping.
* **Analysis & Finding (Medium Risk):**
* The file `help.html` contains the line `{{ content|safe }}`.
* The `content` variable is populated from HTML that has been converted from local Markdown files in the `dockflare/app/templates/docs/` directory.
* This creates a potential Stored Cross-Site Scripting (XSS) vulnerability. If an attacker could modify those source `.md` files, they could inject malicious JavaScript that would be executed in an administrator's browser when they visit the help pages.
* The risk is medium, not high, because it requires the attacker to already have write-access to files within the project, but it is a weakness that could be chained with other exploits.
* **Recommendation:** Use a library like `bleach` to sanitize the HTML after conversion from Markdown and before it is rendered by the template.
---
### **Summary Table of Findings**
| Severity | Vulnerability | Recommendation |
| :--- | :--- | :--- |
| **High** | **Excessive Docker Socket Permissions** | A choice must be made: 1) Accept the risk and use `EXEC=1` for functionality, or 2) Migrate to a different socket proxy that supports command whitelisting to mitigate the risk. |
| **Medium** | **Stored Cross-Site Scripting (XSS)** | Use a library like `bleach` to sanitize the HTML generated from Markdown files before rendering in the `help.html` template. |
| **Low** | **Encryption Key Stored on Filesystem** | For higher security, supply the encryption key at runtime via Docker secrets or an external secrets manager instead of storing it as a file. |
| **Info** | **Insecure Temporary Credential Storage** | This is a minor issue with no direct recommendation, but be aware that credentials exist in a readable (though tamper-proof) form in the session cookie during setup. |

View file

@ -25,6 +25,9 @@ from flask import Flask
from flask_wtf.csrf import CSRFProtect
from flask_login import LoginManager
from authlib.integrations.flask_client import OAuth
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from .core.user import User
import docker
from docker.errors import APIError
@ -40,6 +43,12 @@ log_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', dat
oauth = None
limiter = Limiter(
key_func=get_remote_address,
default_limits=[],
storage_uri="memory://"
)
class QueueLogHandler(logging.Handler):
def __init__(self, log_queue_instance):
super().__init__()
@ -117,6 +126,8 @@ def create_app():
oauth = OAuth()
oauth.init_app(app_instance)
limiter.init_app(app_instance)
@login_manager.unauthorized_handler
def unauthorized():
from flask import request, jsonify, redirect, url_for

View file

@ -27,7 +27,7 @@
<th class="p-3">Display Name</th>
<th class="p-3">Version</th>
<th class="p-3">Status</th>
<th class="p-3">Last Seen</th>
<th class="p-3 text-center">Heart Beat</th>
<th class="p-3">Assigned Tunnel</th>
<th class="p-3">Migration</th>
<th class="p-3">Cloudflared Version</th>

View file

@ -46,6 +46,7 @@
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[51] p-2 shadow bg-base-100 rounded-box w-52">
<li><a href="{{ url_for('web.status_page') }}" class="{{ 'active' if request.endpoint == 'web.status_page' else '' }}">Dashboard</a></li>
<li><a href="{{ url_for('web.access_policies_page') }}" class="{{ 'active' if request.endpoint == 'web.access_policies_page' else '' }}">Access Policies</a></li>
<li><a href="{{ url_for('web.agents_page') }}" class="{{ 'active' if request.endpoint == 'web.agents_page' else '' }}">Agents</a></li>
<li><a href="{{ url_for('web.settings_page') }}" class="{{ 'active' if request.endpoint == 'web.settings_page' else '' }}">Settings</a></li>
<li><a href="{{ url_for('help.help_page') }}" class="{{ 'active' if request.endpoint.startswith('help.') else '' }}">Help</a></li>
</ul>

View file

@ -34,7 +34,7 @@ from flask import (
from flask_login import current_user, login_required, login_user, logout_user
from app.core.user import User
from app import config, docker_client, tunnel_state, cloudflared_agent_state, log_queue, state_update_queue, publish_state_event
from app import config, docker_client, tunnel_state, cloudflared_agent_state, log_queue, state_update_queue, publish_state_event, limiter
from app.core.cache import CACHE_ENABLED
from app.core.state_manager import managed_rules, access_groups, state_lock, save_state, load_state
from app.core.tunnel_manager import (
@ -808,11 +808,10 @@ def tunnel_dns_records(tunnel_id):
@bp.route('/ping')
def ping():
return jsonify({ "status": "ok", "timestamp": int(time.time()), "version": config.APP_VERSION,
return jsonify({ "status": "ok", "timestamp": int(time.time()),
"protocol": request.environ.get('wsgi.url_scheme', 'unknown')})
@bp.route('/version/check')
@login_required
def version_check():
"""
Check whether the running DockFlare image matches the remote tag (digest comparison).
@ -1765,22 +1764,6 @@ def delete_access_group(group_id):
flash(f"Success: Access Group '{display_name}' has been deleted.", "success")
return redirect(url_for('web.access_policies_page'))
@bp.route('/cloudflare-ping')
def cloudflare_ping_route():
try:
cf_headers = {k: v for k, v in request.headers.items() if k.lower().startswith('cf-')}
visitor_data = json.loads(request.headers.get('Cf-Visitor', '{}'))
return jsonify({
"status": "ok", "timestamp": int(time.time()),
"cloudflare": { "connecting_ip": request.headers.get('Cf-Connecting-Ip') or request.remote_addr,
"visitor": visitor_data, "ray": request.headers.get('Cf-Ray') },
"request": { "host": request.host, "path": request.path, "scheme": request.scheme },
"server": { "wsgi_url_scheme": request.environ.get('wsgi.url_scheme') }
})
except Exception as e_cfping:
logging.error(f"Error in /cloudflare-ping route: {e_cfping}", exc_info=True)
return jsonify({ "error": "An internal error occurred.", "status": "error", "timestamp": int(time.time()) }), 500
@bp.route('/backup/download')
def download_state_backup():
try:
@ -1834,6 +1817,7 @@ def restore_state_backup():
return redirect(url_for('web.settings_page'))
@bp.route('/login', methods=['GET', 'POST'])
@limiter.limit("6 per minute", methods=['POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('web.status_page'))

View file

@ -64,4 +64,7 @@ Pygments==2.19.2
# Redis Caching
redis==4.5.5
flask-caching==2.1.0
flask-caching==2.1.0
# Rate Limiting
Flask-Limiter==3.13

View file

@ -0,0 +1,249 @@
# DockFlare Security Assessment Report
**Assessment Date:** September 26, 2025
**Target Application:** DockFlare v3.0.1
**Live URL:** https://df.dataverse.icu
**Assessment Type:** White-box penetration test with source code access
## Executive Summary
DockFlare is a Flask-based web application that automates Cloudflare Tunnel ingress from Docker labels. The security assessment revealed a **medium-low risk** profile with generally good security practices implemented. The application demonstrates proper authentication controls, CSRF protection, and secure session management. However, some areas for improvement were identified, particularly around information disclosure and security headers.
## Application Architecture Analysis
### Web Routes Identified
- **Main Web Routes:** 24 endpoints including authentication, settings, tunnel management
- **API v2 Routes:** 26 REST endpoints for programmatic access
- **Setup Routes:** 6 endpoints for initial configuration
- **Help Routes:** 2 documentation endpoints
### Key Components
- Flask web framework with Blueprint architecture
- Flask-Login for session management
- Flask-WTF for CSRF protection
- OAuth/OIDC integration (Google provider configured)
- Rate limiting via Flask-Limiter
- Agent-based distributed architecture
## Security Findings
### 🟢 Strengths (Good Security Practices)
#### Authentication & Authorization
- ✅ **Proper session-based authentication** via Flask-Login
- ✅ **CSRF protection** implemented across all forms
- ✅ **OAuth/OIDC integration** with Google provider
- ✅ **API key authentication** for programmatic access
- ✅ **Proper user session validation** with timeout controls
- ✅ **Login rate limiting** to prevent brute force attacks
#### Security Headers
- ✅ **Content Security Policy (CSP)** properly configured:
```
default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://rsms.me https://cdn.jsdelivr.net;
img-src 'self' data: https://img.shields.io; font-src 'self' https://rsms.me;
connect-src 'self'; frame-src 'none'; upgrade-insecure-requests
```
- ✅ **Strict-Transport-Security (HSTS)** enforced with 1-year max-age and includeSubDomains
- ✅ **X-Content-Type-Options: nosniff** prevents MIME type confusion
- ✅ **X-Frame-Options: SAMEORIGIN** protects against clickjacking
- ✅ **X-XSS-Protection: 1; mode=block** (legacy browser protection)
- ✅ **Referrer-Policy: strict-origin-when-cross-origin** controls referrer leakage
#### Session Security
- ✅ **HTTPOnly cookies** prevent XSS cookie theft
- ✅ **SameSite=Lax** cookie attribute set
- ✅ **24-hour session lifetime** configured
- ✅ **Secure random session keys** using `os.urandom(24)`
#### Input Validation & Protection
- ✅ **Path traversal protection** - tested endpoints reject `../` sequences
- ✅ **HTTP method restrictions** - unsupported methods return 405 errors
- ✅ **CORS preflight handling** for API endpoints
### 🟡 Areas for Improvement (Medium Risk)
#### Information Disclosure
- ⚠️ **Version information exposed** via `/ping` endpoint:
```json
{"protocol":"http","status":"ok","timestamp":1758906302,"version":"v3.0.1"}
```
**Impact:** Enables version-specific attack targeting
**Recommendation:** Remove version from public endpoints or require authentication
- ⚠️ **Cloudflare internal information leaked** via `/cloudflare-ping`:
```json
{
"cloudflare": {
"connecting_ip": "31.165.127.118",
"ray": "98545984780b931a-ZRH"
},
"request": {"host": "df.dataverse.icu", "path": "/cloudflare-ping", "scheme": "http"},
"server": {"wsgi_url_scheme": "http"}
}
```
**Impact:** Reveals infrastructure details and client IPs
**Recommendation:** Require authentication for this endpoint
#### Security Header Enhancements
- ⚠️ **CSP allows 'unsafe-inline'** for scripts and styles
**Impact:** Reduces XSS protection effectiveness
**Recommendation:** Implement nonce-based CSP or move inline scripts to external files
- ⚠️ **Missing Permissions-Policy header**
**Impact:** Browser features not explicitly controlled
**Recommendation:** Add restrictive Permissions-Policy header
#### CORS Configuration
- ⚠️ **Overly permissive CORS** with `Access-Control-Allow-Origin: *`
**Impact:** Allows requests from any domain
**Recommendation:** Restrict to specific trusted origins
### 🟢 Verified Protections (No Issues Found)
#### Injection Attacks
- ✅ **No SQL injection vectors** identified (using ORM patterns)
- ✅ **No command injection** opportunities in tested endpoints
- ✅ **XSS protection** via CSP and proper output encoding
#### Authentication Bypass
- ✅ **No authentication bypass** vectors found
- ✅ **API endpoints properly protected** with bearer token authentication
- ✅ **Session management secure** - no session fixation or hijacking vectors
#### File System Access
- ✅ **No local file inclusion** vulnerabilities
- ✅ **No exposed sensitive files** (.env, backup files, etc.)
- ✅ **Static file serving secured** - no directory traversal
#### Infrastructure Security
- ✅ **HTTP TRACE method disabled** (returns 405)
- ✅ **Proper error handling** - no stack traces exposed
- ✅ **Host header injection protected** by Cloudflare
## Risk Assessment Matrix
| Vulnerability Type | Risk Level | Count | Status |
|-------------------|------------|--------|---------|
| Critical | 🔴 | 0 | ✅ None Found |
| High | 🟠 | 0 | ✅ None Found |
| Medium | 🟡 | 3 | ⚠️ Found |
| Low | 🟢 | Multiple | ✅ Acceptable |
| Info | | 2 | 📝 Noted |
## Detailed Technical Findings
### Finding 1: Version Information Disclosure
**Endpoint:** `/ping`
**Risk Level:** Medium
**CWE:** CWE-200 (Information Exposure)
The ping endpoint exposes the exact application version (v3.0.1) to unauthenticated users. This information can be used by attackers to research known vulnerabilities specific to this version.
**Proof of Concept:**
```bash
curl -s https://df.dataverse.icu/ping
# Returns: {"version":"v3.0.1",...}
```
### Finding 2: Infrastructure Information Leakage
**Endpoint:** `/cloudflare-ping`
**Risk Level:** Medium
**CWE:** CWE-200 (Information Exposure)
This endpoint reveals internal infrastructure details including client IP addresses and Cloudflare Ray IDs, which could aid in reconnaissance.
### Finding 3: Permissive CORS Policy
**Scope:** All endpoints
**Risk Level:** Medium
**CWE:** CWE-942 (Permissive Cross-domain Policy)
The application sets `Access-Control-Allow-Origin: *`, allowing requests from any domain, which could enable CSRF attacks against authenticated users.
## Code Security Analysis
### Authentication Implementation (`dockflare/app/__init__.py:146-157`)
The application implements a custom request loader that bypasses authentication for certain API endpoints:
```python
@login_manager.request_loader
def load_user_from_request(request):
if request.path.startswith('/api/v2/auth/'):
return None
elif request.endpoint and request.endpoint.startswith('api_v2.'):
from app.core.user import User
return User('api_user')
return None
```
This design properly segregates API authentication from web session authentication.
### API Authentication (`dockflare/app/web/api_v2_routes.py:81-100`)
API endpoints require master API key authentication with proper bearer token validation:
```python
expected_key = current_app.config.get('MASTER_API_KEY') or config.MASTER_API_KEY
if not expected_key:
return jsonify({"status": "error", "message": "master_api_key_not_configured"}), 503
```
## Compliance Assessment
### OWASP Top 10 2021 Compliance
- ✅ **A01 - Broken Access Control:** Proper authentication and authorization
- ✅ **A02 - Cryptographic Failures:** HTTPS enforced, secure session handling
- ✅ **A03 - Injection:** No injection vectors identified
- ⚠️ **A04 - Insecure Design:** Minor issues with information disclosure
- ✅ **A05 - Security Misconfiguration:** Generally well configured
- ✅ **A06 - Vulnerable Components:** Current Flask/Python versions
- ✅ **A07 - Identity and Authentication Failures:** Robust auth system
- ✅ **A08 - Software and Data Integrity Failures:** Proper integrity controls
- ⚠️ **A09 - Security Logging and Monitoring Failures:** Limited logging visibility
- ✅ **A10 - Server-Side Request Forgery:** No SSRF vectors identified
## Recommendations
### Immediate Actions (High Priority)
1. **Restrict version information disclosure**
- Remove version from `/ping` endpoint or require authentication
- Consider implementing a generic health check endpoint
2. **Secure infrastructure endpoints**
- Add authentication requirement to `/cloudflare-ping`
- Consider removing or restricting access to debug endpoints
3. **Strengthen CORS policy**
- Replace `Access-Control-Allow-Origin: *` with specific trusted domains
- Implement proper preflight request handling
### Medium-term Improvements
1. **Enhance Content Security Policy**
- Remove `'unsafe-inline'` directives
- Implement nonce-based CSP for dynamic content
2. **Add security monitoring**
- Implement security event logging
- Add intrusion detection for repeated failed authentication attempts
3. **Security headers enhancement**
- Add Permissions-Policy header
- Consider adding Expect-CT header for certificate transparency
### Long-term Security Hardening
1. **Implement Web Application Firewall (WAF)**
2. **Add comprehensive security monitoring and alerting**
3. **Regular security dependency updates and vulnerability scanning**
4. **Penetration testing on a regular schedule**
## Conclusion
DockFlare demonstrates a **strong security foundation** with proper implementation of core security controls including authentication, CSRF protection, and secure session management. The identified vulnerabilities are primarily related to **information disclosure** and **configuration hardening** rather than critical security flaws.
The application is **suitable for production deployment** with the implementation of the recommended immediate actions. The overall security posture is **above average** for a self-hosted application, particularly given the proper implementation of authentication and session security controls.
**Overall Security Rating: B+ (Good)**
---
*This assessment was conducted on September 26, 2025, against DockFlare v3.0.1 deployed at https://df.dataverse.icu. Results may vary with different versions or configurations.*