{"id":"PYSEC-2026-2893","summary":"PPTAgent: Arbitrary File Write + Directory Creation via markdown_table_to_image","details":"### Summary\n\nThe `markdown_table_to_image` tool accepts a caller-controlled path parameter and passes it directly to `get_html_table_image`:\n\n```python\n# pptagent/mcp_server.py:127-143\ndef markdown_table_to_image(markdown_table: str, path: str, css: str) -\u003e str:\n    \"\"\"\n    Args:\n        path (str): The file path where the image will be saved\n    \"\"\"\n    html = markdown_to_html(markdown_table)\n    get_html_table_image(html, path, css)           # ← no path validation\n    return f\"Markdown table converted to image and saved to {path}\"\n\n# pptagent/utils.py:337-366\ndef get_html_table_image(html: str, output_path: str, css: str = None):\n    parent_dir, base_name = os.path.split(output_path)\n    if parent_dir and not os.path.exists(parent_dir):\n        os.makedirs(parent_dir)                     # ← creates arbitrary directories\n    hti = Html2Image(...)\n    hti.screenshot(\n        html_str=html,\n        css_str=css,\n        save_as=base_name,                          # ← writes image to any directory\n        size=(1000, 600),\n    )\n```\n\n`os.makedirs(parent_dir)` creates arbitrary directory trees, and `Html2Image.screenshot` writes the rendered image to `parent_dir/base_name`. Unlike `download_file` in the same project, there is no `is_relative_to(workspace)` guard. This behaviour can be fixed with the same pattern as the above.\n\n\n### Impact\n\nThe concrete attack scenarios include\n\n- SSH key replacement: `path = \"/home/user/.ssh/authorized_keys\"` — replaces the authorized_keys file with an image binary (breaks - SSH but could be an image crafted with a specific PNG/JPEG payload).\n- Web shell: `path = \"/var/www/html/uploads/shell.php\"` — writes the rendered PNG there; the file has the .php extension but PNG content; combined with Apache Options +MultiViews or file-include vulnerabilities could be dangerous.\n- Directory creation oracle: `path = \"/root/test/probe.png\"` — if the directory is created, confirms the target path exists; if it errors, reveals permissions information.","aliases":["CVE-2026-42078","GHSA-hrcw-xc63-g29m"],"modified":"2026-07-13T16:33:00.029721251Z","published":"2026-07-13T15:15:37.567003Z","references":[{"type":"WEB","url":"https://github.com/icip-cas/PPTAgent/security/advisories/GHSA-hrcw-xc63-g29m"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-42078"},{"type":"WEB","url":"https://github.com/icip-cas/PPTAgent/commit/418491a9a1c02d9d93194b5973bb58df35cf9d00"},{"type":"PACKAGE","url":"https://github.com/icip-cas/PPTAgent"},{"type":"PACKAGE","url":"https://pypi.org/project/pptagent"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-hrcw-xc63-g29m"}],"affected":[{"package":{"name":"pptagent","ecosystem":"PyPI","purl":"pkg:pypi/pptagent"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.1.36"}]}],"versions":["0.2.0","0.2.1","0.2.10","0.2.11","0.2.13","0.2.14","0.2.15","0.2.16","0.2.17","0.2.18","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.2.7","0.2.8","0.2.9","1.0.0","1.0.1","1.0.2","1.1.1","1.1.10","1.1.11","1.1.12","1.1.13","1.1.14","1.1.15","1.1.16","1.1.17","1.1.18","1.1.19","1.1.2","1.1.20","1.1.21","1.1.22","1.1.24","1.1.26","1.1.27","1.1.28","1.1.29","1.1.3","1.1.30","1.1.31","1.1.32","1.1.33","1.1.34","1.1.35","1.1.5","1.1.6","1.1.7","1.1.8","1.1.9"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/pptagent/PYSEC-2026-2893.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:L"}]}