PT-2026-46889 · Packagist · Shopware/Core+1
Published
2026-06-04
·
Updated
2026-06-04
·
CVE-2026-48013
CVSS v3.1
4.1
Medium
| Vector | AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N |
Summary
The
/api/ action/media/external-link endpoint allows authenticated admin users to make server-side HTTP HEAD requests to arbitrary internal IP addresses. While the parallel uploadFromURL flow validates target IPs against private/reserved ranges via FileUrlValidator, the linkURL flow only performs a URL format check (regex for http:// or https:// prefix), allowing SSRF to internal network services and cloud metadata endpoints.Details
The vulnerability is an inconsistency between two URL-handling flows in
MediaUploadService.Vulnerable path (
external-link):MediaUploadV2Controller::externalLink() at src/Core/Content/Media/Api/MediaUploadV2Controller.php:66 takes a user-supplied url parameter and passes it to MediaUploadService::linkURL() at src/Core/Content/Media/Upload/MediaUploadService.php:134.linkURL() calls getContentSizeFromValidExternalUrl($url) at line 159, which only validates via validateExternalUrl():// src/Core/Content/Media/Upload/MediaUploadService.php:207-212
public static function validateExternalUrl(string $url): void
{
if (!preg match('/^https?://.+/', $url)) {
throw MediaException::invalidUrl($url);
}
}
Then makes a server-side HEAD request with no IP filtering:
// src/Core/Content/Media/Upload/MediaUploadService.php:292-300
private function getContentSizeFromValidExternalUrl(string $url): int
{
$this->validateExternalUrl($url);
$headers = $this->httpClient->request('HEAD', $url)->getHeaders();
if (!array key exists('content-length', $headers)) {
throw MediaException::fileNotFound($url);
}
return (int) $headers['content-length'][0];
}
Protected path (
upload by url):In contrast,
uploadFromURL uses FileFetcher::fetchFromURL() which calls FileUrlValidator::isValid():// src/Core/Content/Media/File/FileFetcher.php:64
if ($this->enableUrlValidation && !$this->fileUrlValidator->isValid($url)) {
throw MediaException::illegalUrl($url);
}
FileUrlValidator::isValid() resolves the hostname via gethostbyname() and validates the IP against private and reserved ranges using filter var() with FILTER FLAG NO PRIV RANGE | FILTER FLAG NO RES RANGE. This protection is entirely absent from the linkURL flow.Impact
An authenticated admin user can:
- Probe cloud metadata services — HEAD requests to
169.254.169.254reveal whether cloud metadata endpoints exist and leak content-length values - Scan internal networks — Differentiate open/closed/filtered ports on internal hosts (10.x, 172.16.x, 192.168.x) based on response timing and error types
- Leak internal service information — The
fileSizefield stored in the database reflects thecontent-lengthheader from internal services - Redirect-based escalation — Symfony HttpClient follows redirects by default (max redirects=20), allowing an attacker-controlled external server to redirect the HEAD request to arbitrary internal destinations
Impact is limited to information disclosure via HEAD requests. The admin authentication requirement (PR:H) reduces exploitability, but in multi-tenant or compromised-credential scenarios this allows network reconnaissance from the server's perspective.
Recommended Fix
Apply
FileUrlValidator to the linkURL flow, consistent with the uploadFromURL flow. In MediaUploadService:// src/Core/Content/Media/Upload/MediaUploadService.php
// Add constructor dependency:
private readonly FileUrlValidatorInterface $fileUrlValidator;
// In getContentSizeFromValidExternalUrl(), add IP validation:
private function getContentSizeFromValidExternalUrl(string $url): int
{
$this->validateExternalUrl($url);
if (!$this->fileUrlValidator->isValid($url)) {
throw MediaException::illegalUrl($url);
}
$headers = $this->httpClient->request('HEAD', $url)->getHeaders();
if (!array key exists('content-length', $headers)) {
throw MediaException::fileNotFound($url);
}
return (int) $headers['content-length'][0];
}
Additionally, consider setting
max redirects: 0 on the HttpClient request to prevent redirect-based SSRF bypasses.Fix
SSRF
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Shopware/Core
Shopware/Platform