Vulnerability Details
CWE-79: Stored Cross-Site Scripting (XSS) via RSS/Atom/JSON Feed
The photo description field is stored without HTML sanitization and rendered using {!! $item->summary !!} (Blade unescaped output) in the RSS, Atom, and JSON feed templates. The /feed endpoint is publicly accessible without authentication, allowing any RSS reader to execute attacker-controlled JavaScript.
Data Flow
- Source:
EditPhotoRequest.php — DescriptionRule allows any string up to 1000 chars with no HTML sanitization
- Storage:
PhotoController.php:154 — $photo->description = $request->description() (stored as-is)
- RSS Generation:
Actions/RSS/Generate.php:57 — 'summary' => $data->description ?? ''
- Sink (unescaped):
resources/views/vendor/feed/rss.blade.php:24: {!! $item->summary !!}
resources/views/vendor/feed/atom.blade.php:36: {!! $item->summary !!}
resources/views/vendor/feed/json.blade.php:14-15: {!! str_replace('"', '\\"', $item->summary) !!}
Vulnerable Code
resources/views/vendor/feed/rss.blade.php:24:
<description><![CDATA[...{!! $item->summary !!}]]></description>
The {!! Blade syntax explicitly outputs unescaped HTML. Combined with CDATA, any HTML/JavaScript in the description is rendered by RSS readers.
Steps to Reproduce
- Log in as any authenticated user
- Upload a photo to a public album
- Edit the photo description via
PATCH /api/v2/Photo:
{
"photo_id": "TARGET_PHOTO_ID",
"description": "<img src=x onerror=alert(document.domain)>",
"title": "test", "tags": [], "license": "none",
"upload_date": "2026-03-23", "taken_at": null, "from_id": null
}
- Visit
/feed (no authentication required) — the XSS payload is in the RSS <description> field
Verified Output (Docker PoC)
Tested on Lychee v7.5.2 (lycheeorg/lychee:v7.5.2):
<description><![CDATA[<img src="..."/><br/><br/><img src=x onerror=alert(document.domain)>]]></description>
Impact
- Attacker: Any authenticated user who can upload/edit photos
- Victim: Anyone consuming the public RSS feed (RSS readers, aggregators, browsers)
- Impact: Session theft, credential harvesting, phishing via RSS readers that render HTML content
- The
/feed endpoint requires no authentication, so the XSS payload is served to all feed consumers
CVSS v3.1: 5.4 Medium (AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N)
Proposed Fix
Escape the summary in the feed templates:
// Before (vulnerable)
{!! $item->summary !!}
// After (safe)
{{ $item->summary }}
Or sanitize HTML in DescriptionRule / Generate.php before output.
Vulnerability Details
CWE-79: Stored Cross-Site Scripting (XSS) via RSS/Atom/JSON Feed
The photo
descriptionfield is stored without HTML sanitization and rendered using{!! $item->summary !!}(Blade unescaped output) in the RSS, Atom, and JSON feed templates. The/feedendpoint is publicly accessible without authentication, allowing any RSS reader to execute attacker-controlled JavaScript.Data Flow
EditPhotoRequest.php—DescriptionRuleallows any string up to 1000 chars with no HTML sanitizationPhotoController.php:154—$photo->description = $request->description()(stored as-is)Actions/RSS/Generate.php:57—'summary' => $data->description ?? ''resources/views/vendor/feed/rss.blade.php:24:{!! $item->summary !!}resources/views/vendor/feed/atom.blade.php:36:{!! $item->summary !!}resources/views/vendor/feed/json.blade.php:14-15:{!! str_replace('"', '\\"', $item->summary) !!}Vulnerable Code
resources/views/vendor/feed/rss.blade.php:24:The
{!!Blade syntax explicitly outputs unescaped HTML. Combined with CDATA, any HTML/JavaScript in the description is rendered by RSS readers.Steps to Reproduce
PATCH /api/v2/Photo:{ "photo_id": "TARGET_PHOTO_ID", "description": "<img src=x onerror=alert(document.domain)>", "title": "test", "tags": [], "license": "none", "upload_date": "2026-03-23", "taken_at": null, "from_id": null }/feed(no authentication required) — the XSS payload is in the RSS<description>fieldVerified Output (Docker PoC)
Tested on Lychee v7.5.2 (
lycheeorg/lychee:v7.5.2):Impact
/feedendpoint requires no authentication, so the XSS payload is served to all feed consumersCVSS v3.1: 5.4 Medium (AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N)
Proposed Fix
Escape the summary in the feed templates:
Or sanitize HTML in
DescriptionRule/Generate.phpbefore output.