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/22 14:40:26 UTC

(tooling-agents) branch main updated: ASVS L3 audit: 6.6.2 (ch06_general)

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 84419b9  ASVS L3 audit: 6.6.2 (ch06_general)
84419b9 is described below

commit 84419b911381ca91093042bb2342dad8f8c7ccc6
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Wed Apr 22 16:40:21 2026 +0200

    ASVS L3 audit: 6.6.2 (ch06_general)
---
 .../reports/steve/v3/d0aa7e9/ch06_general/6.6.2.md | 263 +++++++++++++++++++++
 1 file changed, 263 insertions(+)

diff --git a/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch06_general/6.6.2.md b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch06_general/6.6.2.md
new file mode 100644
index 0000000..04f733a
--- /dev/null
+++ b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch06_general/6.6.2.md
@@ -0,0 +1,263 @@
+# ASVS 6.6.2 Security Audit Report
+
+## Executive Summary
+
+- **Repository:** apache/steve
+- **Audit Date:** Apr 22, 2026
+- **Auditor:** Tooling Agents
+- **ASVS Requirement:** 6.6.2 — Verify that out-of-band authentication requests, codes, or tokens are bound to the original authentication request for which they were generated and are not usable for a previous or subsequent one.
+- **ASVS Level:** 2
+- **Section:** Out-of-Band authentication mechanisms
+- **Files Analyzed:** 4 relevant out of 49 total, 12 skipped
+
+### Finding Summary
+
+| Severity | Count | IDs |
+|----------|-------|-----|
+| Critical | 0 | — |
+| High | 0 | — |
+| Medium | 1 | ASVS-662-SEV-001 |
+| Low | 0 | — |
+| **Total** | **1** | |
+
+---
+
+## Security Controls Inventory
+
+| Control | Defined In | Description | Coverage |
+|---------|-----------|-------------|----------|
+| Vote Token Generation | `crypto.py:gen_vote_token()` L48 | Deterministic Argon2 hash of (opened_key + pid + iid + salt) | Used in `add_vote()`, `tally_issue()`, `has_voted_upon()` |
+| Opened Key Generation | `crypto.py:gen_opened_key()` L37 | Argon2 hash of election data for tamper detection | Used in `election.py:open()`, `is_tampered()` |
+| Salt Generation | `crypto.py:gen_salt()` L33 | `secrets.token_bytes(16)` per voter-issue pair | Used in `add_salts()`, `open()` |
+| Email Notification | `mail-voters.py:main()` L30 | Sends emails to eligible voters | **No authentication token generation** |
+| Election State Machine | `election.py:_compute_state()` L358 | Enforces editable→open→closed lifecycle | Applied via `is_editable()`, `is_open()`, `is_closed()` |
+| **OOB Authentication Token Binding** | **NOT FOUND** | **No mechanism to generate request-bound OOB tokens** | **MISSING** |
+
+---
+
+## Critical File Review
+
+### `v3/server/bin/mail-voters.py`
+
+| Line(s) | Operation | OOB Token Binding | Status |
+|---------|-----------|-------------------|--------|
+| 50-55 | Prepare template data | Static identifiers only (pid, eid) | ❌ No token generation |
+| 60-66 | Send email to voter | No unique token per send | ❌ Not request-bound |
+| 45 | Load voters | Returns persistent attributes | ❌ No session context |
+
+### `v3/steve/crypto.py`
+
+| Function | Purpose | OOB Relevance | Status |
+|----------|---------|---------------|--------|
+| `gen_salt()` | Random salt generation | Not used in email flow | N/A |
+| `gen_vote_token()` | Deterministic vote encryption token | Not an OOB auth token | N/A |
+| `create_id()` | Random hex ID | Not used for OOB auth | N/A |
+
+### `v3/steve/election.py`
+
+| Method | Purpose | OOB Relevance | Status |
+|--------|---------|---------------|--------|
+| `get_voters_for_email()` | Returns voter list (pid, name, email) | Provides data for email OOB channel | ❌ No token binding |
+| `add_vote()` | Records vote | Validates election state, not OOB token | N/A |
+| `get_metadata()` | Returns election metadata | Passed to email template as static data | ❌ No request context |
+
+---
+
+## Findings
+
+### Medium Severity
+
+#### ASVS-662-SEV-001: Email-based voter notification lacks request-bound authentication token generation
+
+**Severity:** MEDIUM  
+**Gap Type:** Type A — Entry point with NO control
+
+**Location:**
+- **File:** `v3/server/bin/mail-voters.py`
+- **Function:** `main()`
+- **Lines:** 45–68
+
+**Related Functions:**
+- `v3/steve/election.py:get_voters_for_email()` (lines 455–460)
+- `v3/steve/election.py:get_metadata()`
+
+**Vulnerable Code:**
+```python
+# mail-voters.py lines 50-68
+for voter in voters:
+    # Prepare data dictionary for template rendering
+    data = {
+        'voter': voter,  # edict with pid, name, email
+        'election': metadata,  # edict with eid, title, owner_pid, etc.
+    }
+
+    # Render the template to a StringIO stream
+    output = io.StringIO()
+    template.generate(output, data)
+    rendered_body = output.getvalue()
+
+    # Send the email
+    asfpy.messaging.mail(
+        sender='Apache Election Platform <vo...@apache.org>',
+        recipient=voter.email,
+        subject=f'Vote in Election: {metadata.title}',
+        message=rendered_body,
+    )
+```
+
+**Data Flow:**
+1. `get_voters_for_email()` returns **static** voter attributes: `pid`, `name`, `email`
+2. `get_metadata()` returns **static** election attributes: `eid`, `title`, `owner_pid`, etc.
+3. Template receives only these static identifiers — **no unique, time-limited, or request-bound token is generated**
+4. Email is sent over an insecure OOB channel (email) with no cryptographic binding to any authentication session
+
+**Description:**
+
+The email notification system provides template data consisting entirely of static, reusable identifiers. There is no mechanism anywhere in the email sending flow to:
+
+- Generate a unique, time-limited authentication token per voter per notification
+- Bind any token to an originating authentication request/session
+- Store or validate a nonce for later verification
+- Create a cryptographically signed URL that expires
+
+The `get_voters_for_email()` method returns only persistent attributes:
+```python
+# election.py lines 455-460
+def get_voters_for_email(self):
+    self.q_voting_persons.perform(self.eid)
+    return [
+        edict(pid=row.pid, name=row.name, email=row.email)
+        for row in self.q_voting_persons.fetchall()
+    ]
+```
+
+If the operator-provided EZT template constructs any voting link (e.g., `https://vote.example.org/election/[election.eid]?voter=[voter.pid]`), that link would be:
+- Identical across multiple invocations of `mail-voters.py` for the same election
+- Replayable indefinitely for the election's lifetime
+- Not bound to any specific authentication request or session
+
+This violates ASVS 6.6.2 which requires OOB tokens to be "bound to the original authentication request for which they were generated and are not usable for a previous or subsequent one." The ASVS section description also explicitly notes: "Unsafe out-of-band authentication mechanisms such as e-mail [...] are not permitted."
+
+**Proof of Concept:**
+
+An attacker who intercepts or obtains a voter notification email can extract the static voter identifiers. Because no request-bound token exists:
+
+1. Attacker intercepts email to `voter_a@example.org` for election `abc1234567`
+2. Email contains link: `https://vote.example.org/election/abc1234567` with voter PID
+3. The same link/identifiers remain valid for any future session since nothing binds them to a specific authentication request
+4. If re-sent (e.g., running `mail-voters.py` again), identical credentials are generated — no previous tokens are invalidated
+
+**Impact:**
+- OOB authentication identifiers sent via email are not bound to specific authentication requests
+- Email-delivered credentials remain perpetually valid for the election duration
+- No mechanism exists to detect or prevent replay of intercepted email content
+- For an L2 voting system, this undermines the integrity of the voter authentication channel
+
+**Remediation:**
+
+Generate a cryptographically random, time-limited, single-use token per voter per email send, store it server-side bound to the authentication context, and validate it on use:
+
+```python
+import secrets
+import time
+
+def generate_voter_auth_token(election, voter_pid):
+    """Generate a request-bound, time-limited OOB token."""
+    token = secrets.token_urlsafe(32)
+    expiry = int(time.time()) + 3600  # 1 hour validity
+    
+    # Store token bound to this specific authentication request
+    election.c_store_oob_token.perform(
+        token,
+        voter_pid,
+        election.eid,
+        expiry,
+        False  # not yet used
+    )
+    return token
+
+def validate_voter_auth_token(election, token, voter_pid):
+    """Validate and consume an OOB token."""
+    row = election.q_get_oob_token.first_row(token)
+    if row is None:
+        return False
+    if row.pid != voter_pid or row.eid != election.eid:
+        return False
+    if int(time.time()) > row.expiry:
+        return False
+    if row.used:
+        return False  # Already consumed
+    # Mark as used (single-use)
+    election.c_mark_oob_token_used.perform(token)
+    return True
+
+# In mail-voters.py:
+for voter in voters:
+    token = generate_voter_auth_token(election, voter.pid)
+    data = {
+        'voter': voter,
+        'election': metadata,
+        'auth_token': token,  # Unique, time-limited, request-bound
+    }
+    # ... render and send
+```
+
+---
+
+## Positive Security Patterns
+
+| Pattern | Location | Assessment |
+|---------|----------|------------|
+| Per-voter-per-issue cryptographic salt | `election.py:add_salts()` using `crypto.gen_salt()` | ✅ Strong isolation between voter-issue pairs |
+| Argon2 for key derivation | `crypto.py:_hash()` with 64MB memory cost | ✅ Resistant to brute-force |
+| HKDF for vote encryption key | `crypto.py:_b64_vote_key()` with SHA-256 | ✅ Proper key stretching |
+| Cryptographic shuffle of tallied votes | `crypto.py:shuffle()` using `secrets.randbelow()` | ✅ Prevents ordering attacks |
+| Election state machine enforcement | `election.py:_all_metadata(required_state)` | ✅ Prevents out-of-order operations |
+| Tamper detection via opened_key | `election.py:is_tampered()` | ✅ Detects post-open modifications |
+| Non-predictable IDs | `crypto.py:create_id()` using `secrets.token_hex(5)` | ✅ Prevents enumeration |
+| Transaction isolation for sensitive operations | `election.py:delete()`, `add_salts()` | ✅ Atomic multi-step operations |
+
+---
+
+## Architecture Observations
+
+1. **Authentication Architecture Gap:** The provided codebase contains a voter email notification system (`mail-voters.py`) but lacks any OOB authentication token infrastructure (generation, storage, validation, expiration). Authentication appears to be delegated to LDAP, but the email channel has no cryptographic binding to authentication sessions.
+
+2. **Email as OOB Channel:** The ASVS section explicitly warns that "unsafe out-of-band authentication mechanisms such as e-mail [...] are not permitted." The system uses email to communicate with voters, and if this serves any authentication purpose, it operates on a channel ASVS designates as unsafe.
+
+3. **Template-Controlled Security Boundary:** The email content is determined by an operator-provided EZT template (`--template` argument). Security-critical decisions about what identifiers appear in emails are delegated to template authors with no guardrails or token injection mechanism provided by the application.
+
+4. **Strong Vote Integrity, Weak Authentication Channel:** The vote encryption pipeline (Argon2 → HKDF → Fernet) is well-constructed with per-voter salts and tamper detection. However, the authentication channel (email notification) lacks comparable cryptographic rigor.
+
+---
+
+## Recommendations Summary
+
+### Immediate Actions (0-30 days)
+1. **Implement request-bound, time-limited OOB tokens** for voter email notifications (ASVS 6.6.2) — **Medium Effort**
+2. **Add database table for OOB token storage** with expiry and single-use enforcement (ASVS 6.6.2) — **Medium Effort**
+
+### Short-term Actions (1-3 months)
+3. **Provide application control over authentication token injection** into emails, removing this responsibility from templates (ASVS 6.6.2) — **Low Effort**
+4. **Implement HMAC-signed URLs** with server-validated expiry timestamps if email must continue to be used (ASVS 6.6.2) — **Medium Effort**
+
+### Long-term Actions (3-12 months)
+5. **Migrate from email-based voter notification** to push notifications or TOTP for voter authentication, per ASVS section guidance (ASVS 6.6.2) — **High Effort**
+
+---
+
+## Appendix: Files Analyzed
+
+<details>
+<summary>Click to expand file list (4 files)</summary>
+
+1. `v3/server/bin/mail-voters.py` (relevance: 5/5)
+2. `v3/steve/crypto.py` (relevance: 5/5)
+3. `v3/steve/election.py` (relevance: 3/5)
+4. `v3/docs/schema.md` (relevance: 2/5)
+
+</details>
+
+---
+
+**End of Report**
\ 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