| विवरण | Summary
An arbitrary file read vulnerability exists in the blender-mcp MCP Server. The root cause is the use of Python's open() function to read user-controlled file paths without any validation, path restriction, or file type checking. The input_image_url parameter provided by the MCP client is passed through the MCP Server (server.py) to the Blender addon (addon.py) via a TCP socket connection. When the value does not start with http:// or https://, it is treated as a local file path and read with open(image, "rb"). The file contents are then base64-encoded and sent to an external API endpoint (Tencent Cloud or a user-configured local API). Successful exploitation allows reading any file accessible by the Blender process and exfiltrating its contents to an attacker-controlled server.
The server accepts an input_image_url string from the MCP client and forwards it as the image parameter to the Blender addon without any validation. The addon's only check is whether the string starts with http:// — if not, it is passed directly to open() as a local file path.
Details
The MCP server is for the Blender 3D modeling application, allowing AI assistants to interact with Blender. The generate_hunyuan3d_model tool is intended to accept an image URL or local image path to generate a 3D model via the Hunyuan3D AI service. An MCP Client can be instructed to supply an arbitrary file path instead of a legitimate image, for example via prompt injection. Below are the vulnerable code locations and methods for testing this vulnerability using MCP Inspector. Similarly, attackers can achieve this attack through methods such as indirect prompt injection. The following code leads to arbitrary file read with data exfiltration.
Vulnerable code
The vulnerability spans two files — the MCP Server tool definition (source) and the Blender addon handler (sink). The input_image_url parameter flows through the entire chain with zero path validation. The vulnerability exists in both the OFFICIAL_API and LOCAL_API code paths within the addon.
Version: Latest
File: /src/blender_mcp/server.py
@mcp.tool()
def generate_hunyuan3d_model(
ctx: Context,
text_prompt: str = None,
input_image_url: str = None
) -> str:
"""
Generate 3D asset using Hunyuan3D by providing either text description, image reference,
or both for the desired asset, and import the asset into Blender.
The 3D asset has built-in materials.
Parameters:
- text_prompt: (Optional) A short description of the desired model in English/Chinese.
- input_image_url: (Optional) The local or remote url of the input image. Accepts None if only using text prompt.
Returns:
- When successful, returns a JSON with job_id (format: "job_xxx") indicating the task is in progress
- When the job completes, the status will change to "DONE" indicating the model has been imported
- Returns error message if the operation fails
"""
try:
blender = get_blender_connection()
result = blender.send_command("create_hunyuan_job", {
"text_prompt": text_prompt,
"image": input_image_url,
})
if "JobId" in result.get("Response", {}):
job_id = result["Response"]["JobId"]
formatted_job_id = f"job_{job_id}"
return json.dumps({
"job_id": formatted_job_id,
})
return json.dumps(result)
except Exception as e:
logger.error(f"Error generating Hunyuan3D task: {str(e)}")
return f"Error generating Hunyuan3D task: {str(e)}"
In the generate_hunyuan3d_model tool, the input_image_url parameter is provided directly by the MCP client. This parameter is forwarded to the Blender addon as image via send_command("create_hunyuan_job", {"image": input_image_url}) without any path validation or sanitization.
Version: Latest
File: /addon.py
def create_hunyuan_job_local_site(
self,
text_prompt: str = None,
image: str = None):
try:
base_url = bpy.context.scene.blendermcp_hunyuan3d_api_url.rstrip('/')
octree_resolution = bpy.context.scene.blendermcp_hunyuan3d_octree_resolution
num_inference_steps = bpy.context.scene.blendermcp_hunyuan3d_num_inference_steps
guidance_scale = bpy.context.scene.blendermcp_hunyuan3d_guidance_scale
texture = bpy.context.scene.blendermcp_hunyuan3d_texture
if not base_url:
return {"error": "API URL is not given"}
# Parameter verification
if not text_prompt and not image:
return {"error": "Prompt or Image is required"}
# Constructing request parameters
data = {
"octree_resolution": octree_resolution,
"num_inference_steps": num_inference_steps,
"guidance_scale": guidance_scale,
"texture": texture,
}
# Handling text prompts
if text_prompt:
data["text"] = text_prompt
# Handling image
if image:
if re.match(r'^https?://', image, re.IGNORECASE) is not None:
try:
resImg = requests.get(image)
resImg.raise_for_status()
image_base64 = base64.b64encode(resImg.content).decode("ascii")
data["image"] = image_base64
except Exception as e:
return {"error": f"Failed to download or encode image: {str(e)}"}
else:
try:
# Convert to Base64 format
with open(image, "rb") as f:
image_base64 = base64.b64encode(f.read()).decode("ascii")
data["image"] = image_base64
except Exception as e:
return {"error": f"Image encoding failed: {str(e)}"}
response = requests.post(
f"{base_url}/generate",
json = data,
)
Version: Latest
File: /addon.py
def create_hunyuan_job_main_site(
self,
text_prompt: str = None,
image: str = None
):
try:
secret_id = bpy.context.scene.blendermcp_hunyuan3d_secret_id
secret_key = bpy.context.scene.blendermcp_hunyuan3d_secret_key
if not secret_id or not secret_key:
return {"error": "SecretId or SecretKey is not given"}
# Parameter verification
if not text_prompt and not image:
return {"error": "Prompt or Image is required"}
if text_prompt and image:
return {"error": "Prompt and Image cannot be provided simultaneously"}
# Fixed parameter configuration
service = "hunyuan"
action = "SubmitHunyuanTo3DJob"
version = "2023-09-01"
region = "ap-guangzhou"
headParams={
"Action": action,
"Version": version,
"Region": region,
}
# Constructing request parameters
data = {
"Num": 1 # The current API limit is only 1
}
# Handling text prompts
if text_prompt:
if len(text_prompt) > 200:
return {"error": "Prompt exceeds 200 characters limit"}
data["Prompt"] = text_prompt
# Handling image
if image:
if re.match(r'^https?://', image, re.IGNORECASE) is not None:
data["ImageUrl"] = image
else:
try:
# Convert to Base64 format
with open(image, "rb") as f:
image_base64 = base64.b64encode(f.read()).decode("ascii")
data["ImageBase64"] = image_base64
except Exception as e:
return {"error": f"Image encoding failed: {str(e)}"}
# Get signed headers
headers, endpoint = self.get_tencent_cloud_sign_headers("POST", "/", headParams, data, service, region, secret_id, secret_key)
response = requests.post(
endpoint,
headers = headers,
data = json.dumps(data)
)
In both code paths, the image parameter is checked only against re.match(r'^https?://', image). If the value does not start with http:// or https://, it is passed directly to open(image, "rb") — allowing the reading of any file on the filesystem that the Blender process has access to. The file contents are base64-encoded and sent via HTTP POST to the configured API endpoint. In LOCAL_API mode, the destination is user-configured (base_url), which means the exfiltrated data goes to whatever server is configured in Blender's settings. In OFFICIAL_API mode, the data is sent to Tencent Cloud's API.
In the source code of the tool generate_hunyuan3d_model, we found a call to send_command("create_hunyuan_job", ...) which routes to the create_hunyuan_job handler in the addon. This means that this tool directly introduces the security risk.
If a user uses an unofficial API (such as certain malicious LOCAL_APIs) and the user is subjected to a prompt injection attack, this could lead to the leakage of certain sensitive data.
You can find more details at https://github.com/ahujasid/blender-mcp/issues/202 |
|---|