GHSA-4VH2-39RQ-RQ8J

Vulnerability from github – Published: 2026-10-09 20:52 – Updated: 2026-10-09 20:52
VLAI
Summary
Vikunja: Unbounded image decode on avatar and project-background uploads enables decode/resize amplification
Details

Summary

The 50-megapixel decode guard exists only on the task-attachment preview path. Avatar and project-background uploads decode uploaded images with no pixel cap. Worse, the avatar resize fixes the output height at 1024 and derives the width from the aspect ratio, so a tiny extreme-aspect-ratio PNG expands to an enormous output image — an input-side pixel cap would not catch it.

Details

The only maxPixels check (50MP) is in TaskAttachment.GetPreview (pkg/models/task_attachment.go ~lines 332-338). No such check guards: - avatar upload: pkg/modules/avatar/upload/upload.go (~lines 82, 137, 141) - project background: pkg/modules/background/handler/background.go (~lines 174, 269, 289)

imaging.Resize(img, 0, 1024, imaging.Lanczos) (upload.go ~line 141) fixes height=1024 and derives width from the aspect ratio: a 20000x10 input yields a ~2,048,000 x 1024 output (~2.1 billion pixels), so a few-hundred-byte file drives huge CPU and memory.

PoC (verified at runtime against v2.5.0)

PUT /api/v1/user/settings/avatar/upload   avatar=8000x8000 PNG (64MP, 192KB) -> 200 (accepted; exceeds the 50MP attachment cap)
PUT /api/v1/user/settings/avatar/upload   avatar=20000x10 PNG (681 bytes)    -> 200 after ~19.6s of server processing

Contrast (guard present): uploading the 64MP PNG as a task attachment and requesting its preview returns in ~1ms without decoding — GetPreview rejects it via maxPixels and falls back to the raw file.

Impact

A small crafted upload drives disproportionate CPU and memory on the avatar and background paths. Repeated or concurrent requests can exhaust server resources. Amplification comes from both the missing input pixel cap and the height-fixed resize, so an input-side cap alone is insufficient.

Fix

Apply a pixel-dimension cap (as on the attachment path) to the avatar and background decode paths, and bound the resize output dimensions (cap width as well as height).

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.5.0"
      },
      "package": {
        "ecosystem": "Go",
        "name": "code.vikunja.io/api"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.6.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-91971"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-400"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-09T20:52:15Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\nThe 50-megapixel decode guard exists only on the task-attachment preview path. Avatar and project-background uploads decode uploaded images with no pixel cap. Worse, the avatar resize fixes the output height at 1024 and derives the width from the aspect ratio, so a tiny extreme-aspect-ratio PNG expands to an enormous output image \u2014 an input-side pixel cap would not catch it.\n\n### Details\nThe only `maxPixels` check (50MP) is in `TaskAttachment.GetPreview` (`pkg/models/task_attachment.go` ~lines 332-338). No such check guards:\n- avatar upload: `pkg/modules/avatar/upload/upload.go` (~lines 82, 137, 141)\n- project background: `pkg/modules/background/handler/background.go` (~lines 174, 269, 289)\n\n`imaging.Resize(img, 0, 1024, imaging.Lanczos)` (upload.go ~line 141) fixes height=1024 and derives width from the aspect ratio: a 20000x10 input yields a ~2,048,000 x 1024 output (~2.1 billion pixels), so a few-hundred-byte file drives huge CPU and memory.\n\n### PoC (verified at runtime against v2.5.0)\n```\nPUT /api/v1/user/settings/avatar/upload   avatar=8000x8000 PNG (64MP, 192KB) -\u003e 200 (accepted; exceeds the 50MP attachment cap)\nPUT /api/v1/user/settings/avatar/upload   avatar=20000x10 PNG (681 bytes)    -\u003e 200 after ~19.6s of server processing\n```\nContrast (guard present): uploading the 64MP PNG as a task attachment and requesting its preview returns in ~1ms without decoding \u2014 `GetPreview` rejects it via `maxPixels` and falls back to the raw file.\n\n### Impact\nA small crafted upload drives disproportionate CPU and memory on the avatar and background paths. Repeated or concurrent requests can exhaust server resources. Amplification comes from both the missing input pixel cap and the height-fixed resize, so an input-side cap alone is insufficient.\n\n### Fix\nApply a pixel-dimension cap (as on the attachment path) to the avatar and background decode paths, and bound the resize output dimensions (cap width as well as height).",
  "id": "GHSA-4vh2-39rq-rq8j",
  "modified": "2026-10-09T20:52:16Z",
  "published": "2026-10-09T20:52:15Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/go-vikunja/vikunja/security/advisories/GHSA-4vh2-39rq-rq8j"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-91971"
    },
    {
      "type": "WEB",
      "url": "https://github.com/go-vikunja/vikunja/pull/3688"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/go-vikunja/vikunja"
    },
    {
      "type": "WEB",
      "url": "https://github.com/go-vikunja/vikunja/releases/tag/v2.6.0"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/vikunja-before-2.6.0-denial-of-service-via-avatar-upload"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Vikunja: Unbounded image decode on avatar and project-background uploads enables decode/resize amplification"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

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…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…