Skip to content

Validate uploaded file attachments are images#128

Open
n3crosis wants to merge 4 commits into
amidesfahani:4.xfrom
n3crosis:fix/validate-uploaded-image-attachments
Open

Validate uploaded file attachments are images#128
n3crosis wants to merge 4 commits into
amidesfahani:4.xfrom
n3crosis:fix/validate-uploaded-image-attachments

Conversation

@n3crosis

Copy link
Copy Markdown

Summary

This adds validation before persisting TinyEditor uploaded attachments from Livewire temporary storage.

TinyEditor::setUp() currently walks <img> tags in the submitted HTML, resolves the data-id back to a temporary upload, and then calls saveUploadedFileAttachment() directly. That means a crafted request can reference a non-image temporary upload through an <img> tag and have it persisted as an editor attachment.

This patch adds two guards before saving the attachment:

  • require the uploaded file MIME type to start with image/
  • require getimagesize() to succeed on the uploaded file

Why this matters

Without these checks, the code path trusts that any temporary upload referenced by an <img> element is actually an image. In practice, that allows non-image files to move from temporary upload storage into the configured attachment storage if the request payload is manipulated.

Validation

  • php -l src/TinyEditor.php

This patch is based on a local security fix already in production use.

Copilot AI review requested due to automatic review settings May 24, 2026 10:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens TinyEditor’s Livewire temporary-upload persistence path by validating that files referenced by <img data-id="..."> are actually images before they are saved as file attachments.

Changes:

  • Added a MIME-type guard requiring uploaded attachments to start with image/.
  • Added a content-based guard using getimagesize() to ensure the file is a readable image before persisting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/TinyEditor.php Outdated
$attachment = $this->getUploadedFileAttachment($fileKey);

if ($attachment) {
if (! str_starts_with($attachment->getMimeType(), 'image/')) {
Comment thread src/TinyEditor.php Outdated
Comment on lines +126 to +129
if (! getimagesize($attachment->getRealPath())) {
continue;
}

@n3crosis

Copy link
Copy Markdown
Author

Quick reproduction note for verification:

  1. Create a temporary Livewire upload that is not a real image, for example a harmless test.php or text file.
  2. Submit TinyEditor content with an <img> tag whose data-id points at that temporary upload and whose src points at the matching livewire-tmp/... URL.
  3. On save, TinyEditor::setUp() resolves the data-id back to the temporary upload and calls saveUploadedFileAttachment() without validating that the file is actually an image.
  4. The non-image file is then persisted into the configured attachment storage through an image-only path.

Impact note: the missing validation definitely allows unintended non-image file persistence / exposure. A .php file becomes code execution only if the target attachment location is web-accessible and the server is configured to execute PHP from that path. Otherwise it is still a file upload validation bypass and arbitrary file persistence issue.

n3crosis added 2 commits May 24, 2026 19:02
…tion

- Normalize getMimeType() to '' before str_starts_with() to avoid
  TypeError when MIME type cannot be determined
- Guard getRealPath() result as a non-empty string before reading
  file contents, skipping attachments whose temp file is missing
- Replace getimagesize($path) with getimagesizefromstring($contents)
  to avoid PHP warnings on invalid/unreadable files and remove any
  dependency on a real filesystem path at validation time
Accidentally overwrote these in the previous commit; revert them back
to the values from bae4f48 (the base of this branch) while keeping
the getMimeType/getRealPath/getimagesizefromstring security fixes.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment thread src/TinyEditor.php
Comment on lines +130 to +137
// getRealPath() returns false for stream wrappers or missing
// temp files. Read the file contents instead and validate the
// image data with getimagesizefromstring(), which is also free
// of the PHP warnings that getimagesize() emits on bad input.
$realPath = $attachment->getRealPath();
if (! is_string($realPath) || $realPath === '') {
continue;
}
Comment thread src/TinyEditor.php Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants