You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by as...@apache.org on 2026/04/21 19:13:50 UTC
(tooling-agents) branch main updated: ASVS L3 audit: 12.2.1 (tls_and_transport_security)
This is an automated email from the ASF dual-hosted git repository.
asf-tooling pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-agents.git
The following commit(s) were added to refs/heads/main by this push:
new a2c9491 ASVS L3 audit: 12.2.1 (tls_and_transport_security)
a2c9491 is described below
commit a2c94915b43761e4f86b9fd2f691c3a5a873591b
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Tue Apr 21 21:13:45 2026 +0200
ASVS L3 audit: 12.2.1 (tls_and_transport_security)
---
.../d0aa7e9/tls_and_transport_security/12.2.1.md | 291 +++++++++++++++++++++
1 file changed, 291 insertions(+)
diff --git a/repos/apache/ASVS/reports/steve/v3/d0aa7e9/tls_and_transport_security/12.2.1.md b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/tls_and_transport_security/12.2.1.md
new file mode 100644
index 0000000..856aa1b
--- /dev/null
+++ b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/tls_and_transport_security/12.2.1.md
@@ -0,0 +1,291 @@
+# ASVS 12.2.1 Security Audit Report
+
+## Executive Summary
+
+- **Repository:** apache/steve
+- **Audit Date:** Apr 21, 2026
+- **Auditor:** Tooling Agents
+- **ASVS Requirement:** 12.2.1 — Verify that TLS is used for all connectivity between a client and external facing, HTTP-based services, and does not fall back to insecure or unencrypted communications. (Level: 1, Section: HTTPS Communication with External Facing Services, Chapter: Secure Communication)
+- **Files analyzed:** 2 relevant out of 2 total, 0 skipped
+
+### Finding Counts
+
+| Severity | Count |
+|----------|-------|
+| Critical | 1 |
+| High | 0 |
+| Medium | 0 |
+| Low | 0 |
+| **Total** | **1** |
+
+### Summary
+
+Audit of the Quart-based voting application's TLS configuration reveals a **CRITICAL** vulnerability: the application is explicitly designed to operate over plain HTTP when TLS certificates are not configured, with no enforcement mechanism, no HTTP-to-HTTPS redirection, and configuration documentation actively instructing how to disable TLS. For a voting system handling authentication tokens and encrypted ballots, this directly violates the requirement that TLS "does not fall back to ins [...]
+
+---
+
+## Security Controls Inventory
+
+| Control | Defined | Location | Applied | Enforced | Coverage |
+|---------|---------|----------|---------|----------|----------|
+| TLS Certificate Loading | ✅ | `main.py:84-87` | Conditional | ❌ | Standalone mode only |
+| TLS Key Loading | ✅ | `main.py:84-87` | Conditional | ❌ | Standalone mode only |
+| HTTP→HTTPS Redirect | ❌ | N/A | N/A | N/A | 0% |
+| HSTS Header | ❌ | N/A | N/A | N/A | 0% |
+| TLS Version Enforcement | ❌ | N/A | N/A | N/A | 0% |
+| Cipher Suite Configuration | ❌ | N/A | N/A | N/A | 0% |
+| ASGI TLS Configuration | ❌ | N/A | N/A | N/A | 0% |
+
+---
+
+## Critical File Review
+
+### `v3/server/main.py`
+
+| Line(s) | Element | Security Relevance | Finding |
+|---------|---------|-------------------|---------|
+| 84 | `if app.cfg.server.certfile:` | TLS conditional toggle | **CRITICAL** — allows plain HTTP |
+| 85-86 | `kwargs['certfile'] / kwargs['keyfile']` | TLS cert loading | No SSL context, no version/cipher control |
+| 90 | `app.runx(...)` | Server start | Accepts both TLS and non-TLS kwargs |
+| 98-117 | `run_asgi()` | ASGI entry point | No TLS configuration or validation |
+
+### `v3/server/config.yaml.example`
+
+| Line(s) | Element | Security Relevance | Finding |
+|---------|---------|-------------------|---------|
+| 27-28 | Comments about plain HTTP | Documents insecure option | **CRITICAL** — guides users to disable TLS |
+| 29-30 | `certfile` / `keyfile` | TLS configuration | Present but optional |
+| 23 | `port: 58383` | Non-standard port | Suggests proxy deployment (comment on line 21) |
+
+---
+
+## Findings
+
+### Critical Severity
+
+#### ASVS-1221-CRT-001: Application Falls Back to Plain HTTP When TLS Not Configured
+
+| Field | Detail |
+|-------|--------|
+| **Severity** | **CRITICAL** |
+| **Gap Type** | **Type B — Control EXISTS but conditionally NOT APPLIED** |
+| **File** | `v3/server/main.py` |
+| **Function** | `run_standalone()` |
+| **Lines** | 84–90 |
+| **ASVS Requirement** | 12.2.1 — TLS for all external connectivity, no fallback to unencrypted |
+
+**Related Functions Checked:**
+- `run_standalone()` (lines 72-90)
+- `run_asgi()` (lines 98-117)
+- Configuration loading in `config.yaml.example` (lines 27-31)
+
+**Description:**
+
+The TLS control exists but is implemented as an optional, bypassable configuration toggle. Three specific issues compound into a single critical vulnerability:
+
+1. **Explicit plain HTTP fallback by design**: The `if app.cfg.server.certfile:` conditional on line 84 means when the certfile config value is empty, blank, or absent, the server launches over plain HTTP with zero warnings, zero errors, and zero compensating controls. The configuration comments actively document this as intended behavior ("leave these two fields blank for plain HTTP").
+
+2. **No enforcement at any layer**: There is no startup validation that rejects a missing TLS configuration, no HTTP listener that redirects to HTTPS, no HSTS header injection, and no warning log message when operating without TLS. The application silently degrades to an insecure transport.
+
+3. **ASGI mode has no TLS configuration at all**: The `run_asgi()` function (lines 98-117) creates the application without any TLS parameters, delegating all transport security to the external ASGI server or reverse proxy with no verification that such protection exists.
+
+**Vulnerable Code:**
+
+```python
+# main.py lines 84-90
+kwargs = {}
+if app.cfg.server.certfile:
+ kwargs['certfile'] = CERTS_DIR / app.cfg.server.certfile
+ kwargs['keyfile'] = CERTS_DIR / app.cfg.server.keyfile
+ extra_files.update((kwargs['certfile'], kwargs['keyfile']))
+
+# Spool up the app!
+app.runx(port=app.cfg.server.port, extra_files=extra_files, **kwargs)
+```
+
+**Corresponding Configuration Documentation (config.yaml.example lines 27-31):**
+
+```yaml
+# Specify the .pem files to serve using TLS, or leave these two
+# fields blank for plain HTTP. These files are relative to the
+# "certs" subdirectory of main.py (the app), or they can be absolute
+# paths to their installed location.
+certfile: localhost.apache.org+3.pem
+keyfile: localhost.apache.org+3-key.pem
+```
+
+**Data Flow:**
+
+```
+config.yaml (certfile: "") → app.cfg.server.certfile evaluates to falsy
+ → if-check at line 84 is SKIPPED
+ → kwargs remains empty dict {}
+ → app.runx() called WITHOUT certfile/keyfile
+ → Server starts on plain HTTP
+ → All authentication tokens, session cookies, encrypted votes transmitted in cleartext
+```
+
+**Proof of Concept:**
+
+```yaml
+# config.yaml - deploy with empty TLS fields
+server:
+ port: 58383
+ certfile:
+ keyfile:
+db: steve.db
+```
+
+```bash
+# Server starts on plain HTTP - no error, no warning
+$ ./main.py
+[... |INFO|__main__] ** Run-mode: Standalone
+# Listening on http://0.0.0.0:58383
+
+# Attacker on same network intercepts authentication tokens
+$ tcpdump -A -i eth0 port 58383 | grep -i cookie
+# Session tokens, voter credentials, vote payloads visible in cleartext
+```
+
+**Impact:**
+
+For this voting system, plain HTTP operation exposes:
+- **Authentication tokens**: ASF OAuth tokens and session cookies transmitted in cleartext, enabling session hijacking
+- **Vote contents**: Even though votes are encrypted at rest, they are transmitted from client to server in the HTTP request body — an attacker performing a network-level MITM can capture or modify vote submissions before they reach the encryption layer
+- **Election management operations**: Administrative actions (creating elections, opening/closing voting, tallying) performed over cleartext HTTP
+- **Complete loss of transport security guarantees**: The entire trust model of the voting system collapses if the network transport is unprotected
+
+**Remediation:**
+
+```python
+# main.py - Enforce TLS, never allow plain HTTP for external-facing service
+
+def run_standalone():
+ "Run as a standalone server."
+
+ # ... logging setup ...
+
+ app = create_app()
+ steve = sys.modules['steve']
+ extra_files = { steve.election.QUERIES }
+
+ # ENFORCE TLS - do not allow plain HTTP operation
+ if not app.cfg.server.certfile or not app.cfg.server.keyfile:
+ _LOGGER.critical(
+ 'TLS certificate and key MUST be configured. '
+ 'Plain HTTP is not permitted for this voting system. '
+ 'Set server.certfile and server.keyfile in config.yaml.'
+ )
+ sys.exit(1)
+
+ certfile = CERTS_DIR / app.cfg.server.certfile
+ keyfile = CERTS_DIR / app.cfg.server.keyfile
+
+ if not certfile.exists() or not keyfile.exists():
+ _LOGGER.critical(
+ 'TLS certificate or key file not found: %s, %s',
+ certfile, keyfile
+ )
+ sys.exit(1)
+
+ extra_files.update((certfile, keyfile))
+
+ # Configure SSL context with minimum TLS 1.2
+ import ssl
+ ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
+ ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile)
+
+ # Disable weak ciphers
+ ssl_context.set_ciphers(
+ 'ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20'
+ ':!aNULL:!MD5:!DSS:!RC4:!3DES'
+ )
+
+ app.runx(
+ port=app.cfg.server.port,
+ extra_files=extra_files,
+ ssl=ssl_context,
+ )
+```
+
+```yaml
+# config.yaml.example - Remove documentation of plain HTTP option
+server:
+ port: 58383
+ # TLS certificate and key files (REQUIRED). Relative to certs/
+ # subdirectory or absolute paths.
+ certfile: localhost.apache.org+3.pem
+ keyfile: localhost.apache.org+3-key.pem
+```
+
+Additionally, add HSTS headers via a `before_request` or `after_request` hook in the application:
+
+```python
+@app.after_request
+async def set_security_headers(response):
+ response.headers['Strict-Transport-Security'] = (
+ 'max-age=31536000; includeSubDomains'
+ )
+ return response
+```
+
+---
+
+## Positive Security Patterns
+
+| Pattern | Location | Assessment |
+|---------|----------|------------|
+| TLS configuration capability exists | `main.py:84-87`, `config.yaml.example:29-30` | Good — the mechanism is present |
+| Certificate paths use `pathlib` | `main.py:26-27, 85-86` | Good — proper path construction |
+| Example config ships with TLS enabled | `config.yaml.example:29-30` | Good — secure default values in example |
+| OAuth URLs use HTTPS | `main.py:35-38` | Good — hardcoded `https://` for OAuth endpoints |
+| Static file serving disabled | `main.py:40` | Good — `static_folder=None` reduces attack surface |
+| Proxy architecture acknowledged | `config.yaml.example:21` | Partial — recognizes proxy use but doesn't enforce TLS anywhere |
+
+---
+
+## Architecture Observations
+
+1. **Proxy deployment model**: The comment "Typical usage is that a proxy sits in front of this server" (config.yaml.example line 21) suggests TLS termination may occur at a reverse proxy. However, the application has **no mechanism to verify or enforce** that a TLS-terminating proxy is present. The application should either enforce its own TLS or validate proxy headers (e.g., `X-Forwarded-Proto`).
+
+2. **Two execution modes, inconsistent TLS handling**: Standalone mode (`run_standalone`) has conditional TLS support. ASGI mode (`run_asgi`) has none at all. Neither mode enforces encrypted transport.
+
+3. **Framework delegation**: The `app.runx()` call delegates TLS handling to the `asfquart` framework. Without visibility into that framework's SSL context creation, it's unknown whether secure TLS versions (1.2+) and cipher suites are enforced even when certificates are provided.
+
+---
+
+## Recommendations Summary
+
+### Immediate (P0)
+
+| Recommendation | Effort | Impact |
+|----------------|--------|--------|
+| Make TLS mandatory — fail startup if certfile/keyfile are missing or invalid | Low | Critical |
+| Remove config documentation suggesting plain HTTP is acceptable | Low | High |
+
+### Short-term (P1)
+
+| Recommendation | Effort | Impact |
+|----------------|--------|--------|
+| Create explicit `ssl.SSLContext` with `minimum_version=TLSv1_2` and restricted cipher suites instead of passing raw file paths | Medium | High |
+| Add HSTS response header (`Strict-Transport-Security`) to all responses | Low | High |
+
+### Long-term (P2)
+
+| Recommendation | Effort | Impact |
+|----------------|--------|--------|
+| For ASGI mode, document mandatory Hypercorn TLS configuration and add startup validation of `X-Forwarded-Proto` or equivalent | Medium | Medium |
+| Add an HTTP listener on port 80 (or configured port) that returns 301 redirects to HTTPS, to handle accidental plaintext connections | Medium | Medium |
+
+---
+
+## Appendix: Files Analyzed
+
+<details>
+<summary>Click to expand file list (2 files)</summary>
+
+1. `v3/server/config.yaml.example` (relevance: 10/10)
+2. `v3/server/main.py` (relevance: 10/10)
+
+</details>
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org