-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Vulnerability: Stored XSS via Product Description
Location: Product page rendering (/app/app/javascript/components/Product/index.tsx), originating from product update logic (/app/app/controllers/links_controller.rb and /app/app/services/save_public_files_service.rb).
Description:
When a seller updates a product, the description content is processed by SavePublicFilesService. This service uses Nokogiri::HTML.fragment to parse the description and primarily focuses on validating and cleaning <public-file-embed> tags.
# /app/app/services/save_public_files_service.rb
def process
ActiveRecord::Base.transaction do
# ... (logic for handling public-file-embed)
doc = Nokogiri::HTML.fragment(content)
# ...
clean_invalid_file_embeds(doc, persisted_files)
doc.to_html # Returns potentially unsanitized HTML
end
endThe service does not appear to perform general HTML sanitization on the description content (content). It returns the processed HTML (doc.to_html), which is then saved to the product.description field in LinksController#update:
# /app/app/controllers/links_controller.rb
@product.description = SavePublicFilesService.new(..., content: @product.description).process
@product.save!On the product page (/app/app/javascript/components/Product/index.tsx), during the initial render (pageLoaded is false), the component uses dangerouslySetInnerHTML to render product.description_html:
// /app/app/javascript/components/Product/index.tsx
{
pageLoaded ? (
<EditorContent className="rich-text" editor={descriptionEditor} />
) : (
<div className="rich-text" dangerouslySetInnerHTML={{ __html: product.description_html ?? "" }} />
)
}If product.description_html is derived directly from the potentially unsanitized product.description saved by the backend (without an intermediate sanitization step during HTML generation), then any malicious HTML/JavaScript saved in the description by the seller will be rendered and executed in the browser of users visiting the product page.
Source: Product description field controlled by the seller.
Sink: dangerouslySetInnerHTML in /app/app/javascript/components/Product/index.tsx.
Sanitization: Incomplete; SavePublicFilesService doesn't sanitize general HTML. Vulnerability depends on whether description_html is generated safely before being passed to the frontend, but the direct sink usage suggests a potential gap.
Reproduction / Exploitation:
- As a seller, create or edit a product.
- In the description field, insert an XSS payload, e.g.,
<img src=x onerror=alert('XSS_in_Product_Description')>. - Save the product.
- Visit the public page for that product as a different user (or logged out).
- If the vulnerability exists, the JavaScript payload should execute when the description is rendered.
Remediation:
- Backend Sanitization: Ensure that the
product.descriptionfield is sanitized before saving using a robust HTML sanitizer (likerails-html-sanitizer) within theLinksController#updateaction orSavePublicFilesService. Remove any dangerous tags (<script>,<iframe>, etc.) and attributes (onerror,onload, etc.). - Frontend Sanitization (Less Ideal): If backend sanitization is not feasible, ensure that the
product.description_htmlprop passed to the frontend component is always generated through a safe process (e.g., Markdown rendering with sanitization enabled) before being sent in the initial page data. Avoid directly reflecting the raw saveddescriptioncontent. - Avoid
dangerouslySetInnerHTML: If possible, refactor the initial render to use the TiptapEditorContentlike thepageLoadedstate, or render the description as plain text if HTML is not strictly required before the editor loads.