Repository navigation
fix: restrict document upload file types to prevent XSS (#7959) - #7967
wakqasahmed wants to merge 2 commits into
Conversation
Whitelist safe file extensions/MIME types for the contact document upload endpoint (used by both the web and API upload paths). The document upload service previously accepted any file type with no validation, allowing an attacker to upload an HTML file that gets stored on the public filesystem disk and served verbatim by the webserver, executing attacker JavaScript in Monica's origin when a victim opens the link (session/XSRF token theft).
30990f3 to
b72c423
Compare
| 'account_id' => 'required|integer|exists:accounts,id', | ||
| 'contact_id' => 'required|integer', | ||
| 'document' => 'required|file', | ||
| 'document' => 'required|file|mimes:pdf,doc,docx,odt,xls,xlsx,ods,ppt,pptx,odp,txt,csv,rtf,jpg,jpeg,png,gif,heic', |
There was a problem hiding this comment.
💡 Quality: Allowlist narrows previously-unrestricted uploads (behavior change)
Previously any file type was accepted; the allowlist now silently rejects formats users may legitimately store (e.g. zip, mp3, mp4, mov, epub, key/numbers/pages). This is the correct security tradeoff, but it is a behavior change for existing users. Consider documenting the accepted types in the upload UI/error surface, or confirm with maintainers that this narrowed set is intended.
Was this helpful? React with 👍 / 👎
docx/xlsx/pptx/odt/ods/odp are ZIP containers. On libmagic versions without full OOXML/ODF signature support they get sniffed as a generic zip rather than their specific format, so Laravel's mimes: check (which validates against the sniffed type) rejects otherwise legitimate uploads. Per Gitar's review on monicahq#7967.
|
Added |
Code Review 👍 Approved with suggestions 1 resolved / 2 findingsRestricts document upload file types to an allowlist of common document and image formats, closing an XSS vulnerability where attackers could upload and serve HTML files from the public filesystem. The fix addresses both web and API upload paths. Consider documenting the accepted types in the upload UI or confirming with maintainers that this narrowed set is the intended scope, as existing users storing other formats (e.g. zip, mp3, mp4, mov, epub) will now see silent rejections. 💡 Quality: Allowlist narrows previously-unrestricted uploads (behavior change)📄 app/Services/Contact/Document/UploadDocument.php:23 Previously any file type was accepted; the allowlist now silently rejects formats users may legitimately store (e.g. zip, mp3, mp4, mov, epub, key/numbers/pages). This is the correct security tradeoff, but it is a behavior change for existing users. Consider documenting the accepted types in the upload UI/error surface, or confirm with maintainers that this narrowed set is intended. ✅ 1 resolved✅ Edge Case: mimes rule may reject legit docx/xlsx/odt (zip-based) uploads
🤖 Prompt for agentsReview coverageRules No rules evaluated OptionsAuto-apply is off → Gitar will not commit updates to this branch. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
|
Good catch on the OOXML/ODF zip-detection edge case — the narrowed allowlist is intentional (this closes a stored-XSS path via arbitrary file upload), but you're right that some environments' fileinfo/libmagic could misdetect docx/xlsx/odt as plain zip. Rather than adding |
|
Hi @gitar-bot — this has been sitting green and mergeable for a bit now — happy to make any changes if something's blocking it, otherwise just flagging in case it slipped by. Thanks! |
Gitar can change code and merge on your behalf, so it only acts on requests from people who can push. This needs Write access to this repository. |
Fixes #7959
Vulnerability
App\Services\Contact\Document\UploadDocumentvalidated only that thedocumentfield wasrequired|file, with no restriction on file type/extension/MIME. Uploaded documents are stored on thepublicfilesystem disk (the default configured disk), which is symlinked intopublic/storageand served directly by the webserver, outside of any Laravel auth/routing.Because there was no allowlist, an attacker with any valid Monica account could upload an
.htmlfile. The webserver serves it withContent-Type: text/htmlfrom a publicly reachable, unauthenticated URL under Monica's own origin. A victim who opens that link (e.g. framed as a "business report") gets attacker JavaScript executed in Monica's origin, with access to the victim's session cookie and XSRF token — enabling contact-data exfiltration and full account takeover.Both the web upload path (
Contacts\DocumentsController) and the API upload path (Api\Contact\ApiDocumentController) call the sameUploadDocumentservice, so both were affected and both are now fixed by this single change.Fix
Added a
mimes:allowlist to thedocumentvalidation rule inUploadDocument::rules(), restricting uploads to common, non-executable document/image types (pdf, doc, docx, odt, xls, xlsx, ods, ppt, pptx, odp, txt, csv, rtf, jpg, jpeg, png, gif, heic). Laravel'smimesrule validates the file's real, sniffed extension (not the client-supplied name/MIME), so a renamed.htmlpayload is rejected regardless of its filename.This directly closes the reported vector: HTML/SVG/script-bearing files can no longer be accepted by the upload endpoint at all, so the webserver can never be tricked into serving attacker-controlled markup with a script-executing content type from this feature.
I scoped the fix to the validation allowlist (the issue's own recommendation #1) rather than also restructuring file serving to go through an authenticated controller route (recommendation #2) or adding webserver-level headers on
/storage/documents/(recommendation #3) — both of those are broader architectural/infra changes outside a single, reviewable PR, and the allowlist alone removes the ability to store any content type a browser would execute.Tests
Added:
tests/Unit/Services/Contact/Document/UploadDocumentTest.php::it_fails_if_the_file_type_is_not_allowed— asserts a.htmlupload throwsValidationException.tests/Api/Contact/ApiDocumentControllerTest.php::document_store_gets_an_error_if_the_file_type_is_not_allowed— asserts the API returns a validation error for a.htmlupload.Existing tests use
UploadedFile::fake()->image('...pdf'), which is real generated image content (accepted by the newpng/jpgallowlist entries), so legitimate uploads are unaffected.Disclosure: PHP is not available in the environment these changes were authored in, so these tests could not be executed locally. They were written to match this repo's existing Pest/PHPUnit conventions in the same test files and should be run in CI/a working dev environment before merge.