diff --git a/CHANGELOG.md b/CHANGELOG.md index 44bfe4d..50b219b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/SECURITY_AUDIT.md b/SECURITY_AUDIT.md deleted file mode 100644 index 12b8c50..0000000 --- a/SECURITY_AUDIT.md +++ /dev/null @@ -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. | diff --git a/dockflare/app/__init__.py b/dockflare/app/__init__.py index 42899a3..a4c1dd4 100644 --- a/dockflare/app/__init__.py +++ b/dockflare/app/__init__.py @@ -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 diff --git a/dockflare/app/templates/agents.html b/dockflare/app/templates/agents.html index fb911c3..420fd70 100644 --- a/dockflare/app/templates/agents.html +++ b/dockflare/app/templates/agents.html @@ -27,7 +27,7 @@ Display Name Version Status - Last Seen + Heart Beat Assigned Tunnel Migration Cloudflared Version diff --git a/dockflare/app/templates/base.html b/dockflare/app/templates/base.html index 947621c..57f47a1 100644 --- a/dockflare/app/templates/base.html +++ b/dockflare/app/templates/base.html @@ -46,6 +46,7 @@ diff --git a/dockflare/app/web/routes.py b/dockflare/app/web/routes.py index cfc4501..e137125 100644 --- a/dockflare/app/web/routes.py +++ b/dockflare/app/web/routes.py @@ -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')) diff --git a/dockflare/requirements.txt b/dockflare/requirements.txt index 157e7df..c9c28ea 100644 --- a/dockflare/requirements.txt +++ b/dockflare/requirements.txt @@ -64,4 +64,7 @@ Pygments==2.19.2 # Redis Caching redis==4.5.5 -flask-caching==2.1.0 \ No newline at end of file +flask-caching==2.1.0 + +# Rate Limiting +Flask-Limiter==3.13 \ No newline at end of file diff --git a/security_assessment_report.md b/security_assessment_report.md new file mode 100644 index 0000000..8cffc1e --- /dev/null +++ b/security_assessment_report.md @@ -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.* \ No newline at end of file