PYSEC-2026-2893

Vulnerability from pysec - Published: 2026-07-13 15:15 - Updated: 2026-07-13 16:05
VLAI
Details

Summary

The markdown_table_to_image tool accepts a caller-controlled path parameter and passes it directly to get_html_table_image:

# pptagent/mcp_server.py:127-143
def markdown_table_to_image(markdown_table: str, path: str, css: str) -> str:
    """
    Args:
        path (str): The file path where the image will be saved
    """
    html = markdown_to_html(markdown_table)
    get_html_table_image(html, path, css)           # ← no path validation
    return f"Markdown table converted to image and saved to {path}"

# pptagent/utils.py:337-366
def get_html_table_image(html: str, output_path: str, css: str = None):
    parent_dir, base_name = os.path.split(output_path)
    if parent_dir and not os.path.exists(parent_dir):
        os.makedirs(parent_dir)                     # ← creates arbitrary directories
    hti = Html2Image(...)
    hti.screenshot(
        html_str=html,
        css_str=css,
        save_as=base_name,                          # ← writes image to any directory
        size=(1000, 600),
    )

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.

Impact

The concrete attack scenarios include

  • 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).
  • 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.
  • Directory creation oracle: path = "/root/test/probe.png" — if the directory is created, confirms the target path exists; if it errors, reveals permissions information.
Impacted products
Name purl
pptagent pkg:pypi/pptagent

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "pptagent",
        "purl": "pkg:pypi/pptagent"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.1.36"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "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"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-42078",
    "GHSA-hrcw-xc63-g29m"
  ],
  "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)           # \u2190 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)                     # \u2190 creates arbitrary directories\n    hti = Html2Image(...)\n    hti.screenshot(\n        html_str=html,\n        css_str=css,\n        save_as=base_name,                          # \u2190 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\"` \u2014 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\"` \u2014 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\"` \u2014 if the directory is created, confirms the target path exists; if it errors, reveals permissions information.",
  "id": "PYSEC-2026-2893",
  "modified": "2026-07-13T16:05:37.449491Z",
  "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"
    }
  ],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "PPTAgent: Arbitrary File Write + Directory Creation via markdown_table_to_image"
}



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…