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 23:00:31 UTC
(tooling-agents) branch main updated: ASVS L3 audit: 10.4.11 (authentication_and_session)
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 24f8f42 ASVS L3 audit: 10.4.11 (authentication_and_session)
24f8f42 is described below
commit 24f8f42756327a9549841e69d2874c1fb3cddc1d
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Wed Apr 22 01:00:27 2026 +0200
ASVS L3 audit: 10.4.11 (authentication_and_session)
---
.../d0aa7e9/authentication_and_session/10.4.11.md | 388 +++++++++++++++++++++
1 file changed, 388 insertions(+)
diff --git a/repos/apache/ASVS/reports/steve/v3/d0aa7e9/authentication_and_session/10.4.11.md b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/authentication_and_session/10.4.11.md
new file mode 100644
index 0000000..3d22b33
--- /dev/null
+++ b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/authentication_and_session/10.4.11.md
@@ -0,0 +1,388 @@
+# ASVS 10.4.11 Security Audit Report
+
+## Executive Summary
+
+- **Repository:** apache/steve
+- **Audit Date:** Apr 21, 2026
+- **Auditor:** Tooling Agents
+- **ASVS Requirement:** 10.4.11 — Verify that the authorization server configuration only assigns the required scopes to the OAuth client (Level 2, OAuth Authorization Server)
+- **Files analyzed:** 3 relevant out of 3 total, 0 skipped
+
+### Finding Counts
+
+| Severity | Count |
+|----------|-------|
+| Critical | 0 |
+| High | 2 |
+| Medium | 1 |
+| Low | 0 |
+| **Total** | **3** |
+
+### Summary
+
+The audit reveals that the OAuth client configuration does not request explicit scopes, and the application's internal authorization model is insufficiently granular—any authenticated committer can perform administrative operations on any election, and the vote submission endpoint lacks eligibility verification present in the corresponding display page.
+
+---
+
+## Security Controls Inventory
+
+| Control | Defined | Applied | Coverage |
+|---------|---------|---------|----------|
+| OAuth Scope Request | **NOT DEFINED** | N/A | 0% — No `scope` param in auth URL |
+| `R.committer` auth gate | `pages.py` (multiple) | All voter/management endpoints | Applied but **too coarse** |
+| `R.pmc_member` auth gate | `pages.py:459` | `do_create_endpoint` only | 1 endpoint |
+| Election ownership check | Commented as `### check authz` | **NEVER CALLED** | 0% — Type B gap |
+| Voter eligibility (mayvote) | `pages.py:262` (GET page) | vote_on_page only | **NOT applied to POST /do-vote** — Type B gap |
+| Session read | `asfquart.session.read()` | `basic_info()` | All handlers via basic_info |
+
+---
+
+## Critical File Review
+
+### `v3/server/main.py` — OAuth Configuration
+
+| Line | Code | Risk | Status |
+|------|------|------|--------|
+| 38-41 | OAuth URL without scope | MEDIUM | No scope parameter |
+| 42 | Token callback URL | LOW | Uses HTTPS |
+
+### `v3/server/pages.py` — Authorization Controls
+
+| Route | Auth Gate | Ownership Check | Scope Appropriate? |
+|-------|-----------|-----------------|-------------------|
+| `POST /do-create-election` | `R.pmc_member` | N/A (creates new) | ✅ Higher privilege |
+| `GET /do-open/<eid>` | `R.committer` | ❌ Missing | ❌ Too broad |
+| `GET /do-close/<eid>` | `R.committer` | ❌ Missing | ❌ Too broad |
+| `POST /do-add-issue/<eid>` | `R.committer` | ❌ Missing | ❌ Too broad |
+| `POST /do-edit-issue/<eid>/<iid>` | `R.committer` | ❌ Missing | ❌ Too broad |
+| `POST /do-delete-issue/<eid>/<iid>` | `R.committer` | ❌ Missing | ❌ Too broad |
+| `POST /do-vote/<eid>` | `R.committer` | ❌ Missing | ❌ No mayvote check |
+| `GET /vote-on/<eid>` | `R.committer` | ✅ mayvote check | ✅ |
+| `GET /docs/<iid>/<docname>` | `@require` | ✅ mayvote check | ✅ |
+
+---
+
+## Findings
+
+### High Severity
+
+#### ASVS-10411-HIGH-001 — Missing Election Ownership Authorization on All Management Endpoints
+
+**Severity:** HIGH
+**Gap Type:** Type B (control planned but never implemented — false confidence)
+
+**Location:**
+- **File:** `v3/server/pages.py`
+- **Functions:** Multiple endpoints
+- **Lines:** 97, 193, 217, 227, 487, 508, 527, 554, 581
+
+**Affected Endpoints:**
+
+| Endpoint | Route | Line |
+|----------|-------|------|
+| `do_open_endpoint` | `GET /do-open/<eid>` | ~487 |
+| `do_close_endpoint` | `GET /do-close/<eid>` | ~508 |
+| `do_add_issue_endpoint` | `POST /do-add-issue/<eid>` | ~527 |
+| `do_edit_issue_endpoint` | `POST /do-edit-issue/<eid>/<iid>` | ~554 |
+| `do_delete_issue_endpoint` | `POST /do-delete-issue/<eid>/<iid>` | ~581 |
+| `do_set_open_at_endpoint` | `POST /do-set-open_at/<eid>` | ~97 (via `_set_election_date`) |
+| `do_set_close_at_endpoint` | `POST /do-set-close_at/<eid>` | ~97 (via `_set_election_date`) |
+| `load_election` decorator | N/A | ~193 |
+| `load_election_issue` decorator | N/A | ~217, ~227 |
+
+**Related Functions Checked:**
+- `load_election()` — decorator at line ~193
+- `load_election_issue()` — decorator at lines ~217, ~227
+- `do_open_endpoint()` — line ~487
+- `do_close_endpoint()` — line ~508
+- `do_add_issue_endpoint()` — line ~527
+- `do_edit_issue_endpoint()` — line ~554
+- `do_delete_issue_endpoint()` — line ~581
+- `_set_election_date()` — line ~97
+
+**Description:**
+
+All election management endpoints contain a `### check authz` comment indicating planned authorization checks, but these checks are never implemented. Any authenticated ASF committer can open, close, or modify any election in the system, including adding, editing, or deleting ballot issues, regardless of election ownership.
+
+**Vulnerable Code:**
+
+```python
+@APP.get('/do-open/<eid>')
+@asfquart.auth.require({R.committer}) ### need general solution
+@load_election
+async def do_open_endpoint(election):
+ result = await basic_info()
+
+ ### check authz # <-- NEVER IMPLEMENTED
+
+ # Open the Election.
+ election.open(pdb) # <-- Any committer can open any election
+```
+
+**Data Flow:**
+
+```
+Authenticated committer
+ → GET /do-open/<any_eid>
+ → R.committer passes
+ → load_election loads election by EID (no ownership check)
+ → election.open() executes
+ → election owned by another user is opened/closed/modified
+```
+
+**Proof of Concept:**
+
+```http
+# Attacker is any authenticated ASF committer, not the election owner
+GET /do-open/election-uuid-owned-by-someone-else HTTP/1.1
+Cookie: [valid_session_cookie]
+```
+
+**Impact:**
+
+Any authenticated ASF committer can open, close, or modify any election in the system, including adding, editing, or deleting ballot issues. This completely undermines election integrity since the OAuth-assigned scope (committer role) is far broader than the required scope (election owner). This is a direct violation of ASVS 10.4.11's principle that only required scopes should be assigned to operations.
+
+**Remediation:**
+
+```python
+def load_election(func):
+ @functools.wraps(func)
+ async def loader(eid):
+ try:
+ e = steve.election.Election(DB_FNAME, eid)
+ except steve.election.ElectionNotFound:
+ # ... existing 404 handling
+ pass
+
+ # AUTHORIZATION CHECK: verify current user is the election owner
+ result = await basic_info()
+ metadata = e.get_metadata()
+ if metadata.owner_pid != result.uid:
+ quart.abort(403, 'Not authorized to manage this election')
+
+ return await func(e)
+ return loader
+```
+
+---
+
+#### ASVS-10411-HIGH-002 — Vote Submission Endpoint Missing Voter Eligibility Check
+
+**Severity:** HIGH
+**Gap Type:** Type B (control exists in GET handler but not applied to POST handler)
+
+**Location:**
+- **File:** `v3/server/pages.py`
+- **Function:** `do_vote_endpoint()`
+- **Lines:** ~411-456
+
+**Related Functions Checked:**
+- `vote_on_page()` — line ~262 (contains proper eligibility check)
+- `do_vote_endpoint()` — lines ~411-456 (missing eligibility check)
+
+**Description:**
+
+The voter eligibility check (mayvote verification) exists in the `vote_on_page` GET handler but is not applied to the `do_vote_endpoint` POST handler. Any authenticated ASF committer can submit votes on any open election, regardless of whether they are listed as an eligible voter.
+
+**Control EXISTS in `vote_on_page` (GET /vote-on/<eid>), lines ~262-267:**
+
+```python
+# Check if user can vote in this election (has mayvote entries)
+election.q_find_issues.perform(result.uid, election.eid)
+if not election.q_find_issues.fetchall():
+ result = await basic_info()
+ result.title = 'Access Denied'
+ result.eid = election.eid
+ raise_404(T_BAD_EID, result)
+```
+
+**Control NOT APPLIED in `do_vote_endpoint` (POST /do-vote/<eid>), lines ~411-456:**
+
+```python
+@APP.post('/do-vote/<eid>')
+@asfquart.auth.require({R.committer})
+@load_election
+async def do_vote_endpoint(election):
+ result = await basic_info()
+
+ ### check authz # <-- NEVER IMPLEMENTED — no mayvote check
+
+ form = edict(await quart.request.form)
+ # ... proceeds directly to:
+ election.add_vote(result.uid, iid, votestring) # Accepts vote from ineligible voter
+```
+
+**Data Flow:**
+
+```
+Ineligible committer
+ → POST /do-vote/<eid> with crafted form data
+ → R.committer passes
+ → no mayvote check
+ → election.add_vote() stores vote
+ → ineligible user has voted
+```
+
+**Proof of Concept:**
+
+```http
+POST /do-vote/target-election-eid HTTP/1.1
+Cookie: [valid_session_cookie_for_non_eligible_committer]
+Content-Type: application/x-www-form-urlencoded
+
+vote-issue1=yes&vote-issue2=no
+```
+
+**Impact:**
+
+Any authenticated ASF committer can submit votes on any open election, regardless of whether they are listed as an eligible voter (in the mayvote table). The eligibility check exists only on the display page (GET), which an attacker bypasses entirely by POSTing directly. This is a scope enforcement failure: the committer scope should not grant voting rights on elections where the user is not a registered voter.
+
+**Remediation:**
+
+```python
+@APP.post('/do-vote/<eid>')
+@asfquart.auth.require({R.committer})
+@load_election
+async def do_vote_endpoint(election):
+ result = await basic_info()
+
+ # Verify voter eligibility before accepting votes
+ election.q_find_issues.perform(result.uid, election.eid)
+ if not election.q_find_issues.fetchall():
+ quart.abort(403, 'Not authorized to vote in this election')
+
+ # ... rest of vote processing
+```
+
+---
+
+### Medium Severity
+
+#### ASVS-10411-MEDIUM-001 — OAuth Authorization Request Omits Scope Parameter
+
+**Severity:** MEDIUM
+**Gap Type:** Type A (no control)
+
+**Location:**
+- **File:** `v3/server/main.py`
+- **Function:** `create_app()`
+- **Lines:** 38–42
+
+**Related Functions Checked:**
+- `create_app()` — lines 38-42 (OAuth configuration)
+
+**Description:**
+
+The OAuth authorization URL template only includes `state` and `redirect_uri` parameters. No `scope` parameter is specified. Per RFC 6749 §3.3, when the client omits the scope parameter, the authorization server either uses a pre-configured default or fails. Without an explicit `scope` value, the application relies entirely on server-side defaults at `oauth.apache.org`, violating the principle of least privilege mandated by ASVS 10.4.11.
+
+**Vulnerable Code:**
+
+```python
+asfquart.generics.OAUTH_URL_INIT = (
+ 'https://oauth.apache.org/auth?state=%s&redirect_uri=%s'
+)
+```
+
+**Data Flow:**
+
+```
+create_app()
+ → sets OAuth init URL
+ → user redirected to AS
+ → no scope parameter
+ → AS grants default/full scopes
+ → application receives token with potentially excessive permissions
+```
+
+**Analysis:**
+
+The application requires two distinct authorization levels (committer operations and PMC-member operations), yet makes no attempt to request differentiated scopes from the OAuth AS. This means the issued token may carry broader permissions than any individual operation requires.
+
+**Impact:**
+
+The OAuth client may receive access tokens with scopes exceeding what's needed for the user's intended operation. If the AS grants broad default scopes, any compromised token grants full application access rather than limited access.
+
+**Remediation:**
+
+```python
+# Explicitly request only needed scopes
+asfquart.generics.OAUTH_URL_INIT = (
+ 'https://oauth.apache.org/auth?state=%s&redirect_uri=%s&scope=openid%20profile%20email'
+)
+```
+
+Additionally, validate returned scopes in the token response to confirm the AS only granted what was requested.
+
+---
+
+## Positive Security Patterns
+
+1. **LDAPS for directory access** (`asf-load-ldap.py`): Uses `ldaps://` protocol for LDAP communication.
+2. **OAuth uses HTTPS endpoints**: Both init and callback URLs use `https://`.
+3. **Two-tier role distinction**: `R.pmc_member` for election creation vs `R.committer` for operations shows intent for scope separation.
+4. **Voter eligibility check on display page**: `vote_on_page` correctly checks mayvote before showing the ballot.
+5. **Document serving authorization**: `serve_doc` checks mayvote before serving files.
+6. **Audit logging**: All state-changing operations log the user ID and action.
+
+---
+
+## Architecture Observations
+
+1. **OAuth is used purely for authentication**, not for fine-grained authorization. The application derives roles from LDAP group membership rather than OAuth scopes. This means ASVS 10.4.11's scope minimization principle must be enforced at both the OAuth level (request minimal scopes) and the application level (enforce ownership and eligibility).
+
+2. **The authorization model has a structural gap**: Authentication (OAuth) and coarse authorization (committer/pmc_member) are implemented, but fine-grained authorization (election ownership, voter eligibility) is systematically missing from state-changing endpoints despite being documented as needed (`### check authz`).
+
+3. **GET methods used for state-changing operations**: `/do-open/<eid>` and `/do-close/<eid>` use GET, which compounds the CSRF risk noted in TODO.md.
+
+---
+
+## Recommendations Summary
+
+### Immediate (Critical Priority)
+
+| Priority | Finding | Remediation |
+|----------|---------|-------------|
+| **1** | ASVS-10411-HIGH-001 | Implement ownership verification in `load_election` decorator for all management operations |
+| **2** | ASVS-10411-HIGH-002 | Add mayvote check to `do_vote_endpoint` before accepting votes |
+
+### Short-term (High Priority)
+
+| Priority | Finding | Remediation |
+|----------|---------|-------------|
+| **3** | ASVS-10411-MEDIUM-001 | Add explicit `scope` parameter to OAuth authorization URL; validate returned scopes |
+| **4** | Architecture | Convert `do-open` and `do-close` from GET to POST to enable proper CSRF protection |
+
+### Long-term (Medium Priority)
+
+| Priority | Recommendation | Rationale |
+|----------|----------------|-----------|
+| **5** | Implement CSRF token generation and validation | Complete the placeholder noted in `basic_info()` |
+| **6** | Standardize authorization pattern | Create reusable decorators for ownership and eligibility checks |
+| **7** | Scope-based authorization | Map OAuth scopes to application permissions rather than relying solely on LDAP groups |
+
+---
+
+## Appendix: Files Analyzed
+
+<details>
+<summary>Click to expand file list (3 files)</summary>
+
+1. **`v3/server/bin/asf-load-ldap.py`** (relevance: 10)
+ - LDAP integration for user directory
+ - Uses LDAPS protocol
+
+2. **`v3/server/main.py`** (relevance: 10)
+ - OAuth configuration
+ - Application initialization
+ - **Finding:** ASVS-10411-MEDIUM-001
+
+3. **`v3/server/pages.py`** (relevance: 10)
+ - All HTTP endpoint handlers
+ - Authorization decorators
+ - **Findings:** ASVS-10411-HIGH-001, ASVS-10411-HIGH-002
+
+</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