Skip to content

Fix memory DoS and extension bypass in attribute file uploads - #789

Merged
KrzysztofPajak merged 3 commits into
developfrom
fix/attribute-upload-memory-dos
Aug 16, 2026
Merged

Fix memory DoS and extension bypass in attribute file uploads#789
KrzysztofPajak merged 3 commits into
developfrom
fix/attribute-upload-memory-dos

Conversation

@KrzysztofPajak

@KrzysztofPajak KrzysztofPajak commented Aug 16, 2026

Copy link
Copy Markdown
Member

Resolves #issueNumber
Type: bugfix

Issue

ContactController.UploadFileContactAttribute, ProductController.UploadFileProductAttribute, and ShoppingCartController.UploadFileCheckoutAttribute (all reachable from the storefront, two without authentication required) buffered the entire uploaded file into a byte[] via IFormFile.GetDownloadBits() before checking ValidationFileMaximumSize. Application.MaxRequestBodySize in appsettings.json was left null, so only Kestrel's default 30 MB request-body limit applied. An unauthenticated user could repeatedly upload files up to that limit, each fully allocated in memory before being rejected - a cheap memory-exhaustion DoS.

Separately, when an attribute had no ValidationFileAllowedExtensions configured, the extension check was skipped entirely, so any file extension was accepted and stored.

To reproduce (pre-fix): configure a contact/checkout/product attribute of type File Upload with no allowed-extensions list, then POST a large file (e.g. 20 MB) to UploadFileContactAttribute as an anonymous user - the full body is read into memory regardless of any configured size limit, and any extension is accepted.

Solution

  • Set Application.MaxRequestBodySize to 10 MB in Grand.Web/App_Data/appsettings.json. This setting already exists and is wired to both Kestrel.Limits.MaxRequestBodySize and FormOptions.MultipartBodyLengthLimit (ConfigurationExtensions.ConfigureApplicationSettings) - ASP.NET Core now rejects an oversized request for every storefront endpoint before the body is parsed, without any new code. Admin-configurable, as before.
  • Kept the per-attribute ValidationFileMaximumSize check, comparing against IFormFile.Length (size reported by the multipart headers) before GetDownloadBits() is called, so a request within the 10 MB ceiling but over a smaller attribute-configured limit is still rejected without being buffered.
  • Replaced the raw ValidationFileAllowedExtensions.Split(...) check with the existing FileExtensions.GetAllowedMediaFileTypes(...) helper (already used by PictureController), which falls back to a safe image-extension allow-list when the attribute has none configured, instead of allowing everything.

Breaking changes

Any Grand.Web storefront request body over 10 MB (not just these three endpoints) is now rejected by Kestrel; previously up to 30 MB was allowed. Adjust Application.MaxRequestBodySize in appsettings.json if a store needs a higher storefront-wide limit. Attributes with no configured allowed-extensions list now restrict uploads to .gif/.jpg/.jpeg/.png/.bmp/.webp instead of accepting any extension - a deliberate secure-by-default change to previously unrestricted behavior.

Testing

  1. Create a Checkout Attribute (or Contact/Product Attribute) of control type "File upload", leave "Allowed file extensions" empty, save.
  2. As an anonymous storefront user, attempt to upload a .exe file to the corresponding upload endpoint - it is now rejected (ValidationFileAllowed message) instead of being accepted.
  3. Upload a file larger than 10 MB to any Grand.Web endpoint - rejected by Kestrel before reaching the action.
  4. Set "Maximum file size" on the attribute to a value below 10 MB, then upload a file between that value and 10 MB - rejected with the friendly maximum-size JSON message before the request body is fully buffered.
  5. Upload a valid image within both the attribute's configured limit and the 10 MB ceiling - succeeds as before.

Contact/Product/Checkout attribute upload endpoints buffered the entire
file into memory before checking the size limit, and treated an empty
ValidationFileAllowedExtensions as "any extension allowed". An
unauthenticated user could exhaust server memory with repeated large
uploads, or store arbitrary file content.

Check IFormFile.Length against a hard cap (min of the attribute's
configured limit and a new 10 MB ceiling) before reading the body, and
fall back to the existing safe default extension allow-list (already
used by PictureController) when the attribute has none configured.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 16, 2026 11:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

KrzysztofPajak and others added 2 commits August 16, 2026 13:43
ASP.NET Core already provides a declarative way to cap request body size
per action; use it instead of hand-rolled Math.Min/byte-comparison logic
in the controller. The framework now rejects an oversized request before
the multipart body is parsed at all, which is a stronger guarantee than
checking IFormFile.Length in application code. The per-attribute
ValidationFileMaximumSize check (via file.Length, before reading the
body) is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tribute

Grand.Web already exposes AppConfig.MaxRequestBodySize, wired to both
Kestrel's Limits.MaxRequestBodySize and FormOptions.MultipartBodyLengthLimit
(ConfigurationExtensions.ConfigureApplicationSettings). It was left null,
falling back to Kestrel's 30MB default. Set it to 10MB in Grand.Web's
appsettings.json instead of introducing a custom [RequestSizeLimit]
attribute/constant per action - reuses the mechanism the app already has
for this, applies to every storefront endpoint, and stays admin-configurable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@KrzysztofPajak
KrzysztofPajak merged commit a153496 into develop Aug 16, 2026
4 of 5 checks passed
@KrzysztofPajak
KrzysztofPajak deleted the fix/attribute-upload-memory-dos branch August 16, 2026 11:55
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