Affected Versions
version ≤ 3.2.5 (latest version at time of disclosure)
Repository: https://github.com/shopizer-ecommerce/shopizer
Impact
Multiple REST API endpoints, including PUT /api/v1/private/category/{id}, POST /api/v1/auth/products/{id}/reviews, PUT /api/v1/private/customer/{id}, POST /api/v1/private/content, and POST /api/v1/search, are vulnerable to Stored Cross-Site Scripting (XSS). An attacker can bypass the backend XSS filter by submitting malicious JavaScript payloads within the JSON request body. These payloads are persisted to the database and returned verbatim by the corresponding GET endpoints (e.g., GET /api/v1/category), executing arbitrary JavaScript in the victim's browser.
Shopizer implements a tiered role-based access control system with varying privilege levels (e.g., admin_content, admin_category). A low-privileged admin user can exploit this Stored XSS to attack a super administrator — for example, by injecting a payload that steals the super admin's session token or performs privileged operations on their behalf when they view the affected resource.
Steps to Reproduce
- Authenticate as a low-privileged admin user (e.g., one with
admin_category role).
- Send a
PUT request to /api/v1/private/category/{id} with a malicious XSS payload injected into a category field description:
- Send a
GET request to the public endpoint /api/v1/category and observe that the response body contains the raw, unsanitized JavaScript payload.
- When a super administrator (or any user) visits a frontend page that renders this category data, the injected script executes in their browser context, allowing the attacker to steal session cookies, perform CSRF, or escalate privileges.
Root Cause Analysis
The vulnerability originates from an incomplete XSS filtering mechanism in the XssHttpServletRequestWrapper class.
File: sm-shop/src/main/java/com/salesmanager/shop/filter/XssHttpServletRequestWrapper.java
The wrapper only overrides getHeader(), getParameter(), and getParameterValues() to apply HTML sanitization via AntiSamy. Critically, it does not override getInputStream() or getReader():
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
return SanitizeUtils.getSafeRequestParamString(value);
}
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
return SanitizeUtils.getSafeRequestParamString(value);
}
@Override
public String[] getParameterValues(String parameter) {
// ... sanitizes URL query parameters only
}
// ✗ getInputStream() is NOT overridden
// ✗ getReader() is NOT overridden
}
In Spring MVC, all @RequestBody-annotated parameters are deserialized by reading from HttpServletRequest.getInputStream(). Since this method is not overridden, the JSON request body completely bypasses XSS filtering.
Nearly all REST API controllers in Shopizer use @RequestBody to accept JSON input. For example, the PersistableCategory model used by the category endpoint has no server-side HTML sanitization or validation on its name or description fields — only basic @NotEmpty constraints exist:
public class PersistableCategoryDescription extends CategoryDescription {
@NotEmpty
private String name; // ✗ No XSS filtering or HTML escaping
private String description; // ✗ No XSS filtering or HTML escaping
}
This means any string field in any JSON-based API endpoint is a potential injection point.
Remediation
- Override
getInputStream() and getReader() in XssHttpServletRequestWrapper to read, sanitize, and re-wrap the JSON request body before it reaches the Spring deserialization layer. Parse the body as a JSON tree, recursively sanitize all string values, then return a new ServletInputStream backed by the sanitized content.
- Enable security response headers in the Spring Security configuration: add
Content-Security-Policy, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and X-XSS-Protection: 1; mode=block.
- Register the XSS filter for all URL patterns, including
/services/**.
Affected Versions
version ≤ 3.2.5 (latest version at time of disclosure)
Repository: https://github.com/shopizer-ecommerce/shopizer
Impact
Multiple REST API endpoints, including
PUT /api/v1/private/category/{id},POST /api/v1/auth/products/{id}/reviews,PUT /api/v1/private/customer/{id},POST /api/v1/private/content, andPOST /api/v1/search, are vulnerable to Stored Cross-Site Scripting (XSS). An attacker can bypass the backend XSS filter by submitting malicious JavaScript payloads within the JSON request body. These payloads are persisted to the database and returned verbatim by the correspondingGETendpoints (e.g.,GET /api/v1/category), executing arbitrary JavaScript in the victim's browser.Shopizer implements a tiered role-based access control system with varying privilege levels (e.g.,
admin_content,admin_category). A low-privileged admin user can exploit this Stored XSS to attack a super administrator — for example, by injecting a payload that steals the super admin's session token or performs privileged operations on their behalf when they view the affected resource.Steps to Reproduce
admin_categoryrole).PUTrequest to/api/v1/private/category/{id}with a malicious XSS payload injected into a category fielddescription:GETrequest to the public endpoint/api/v1/categoryand observe that the response body contains the raw, unsanitized JavaScript payload.Root Cause Analysis
The vulnerability originates from an incomplete XSS filtering mechanism in the
XssHttpServletRequestWrapperclass.File:
sm-shop/src/main/java/com/salesmanager/shop/filter/XssHttpServletRequestWrapper.javaThe wrapper only overrides
getHeader(),getParameter(), andgetParameterValues()to apply HTML sanitization via AntiSamy. Critically, it does not overridegetInputStream()orgetReader():In Spring MVC, all
@RequestBody-annotated parameters are deserialized by reading fromHttpServletRequest.getInputStream(). Since this method is not overridden, the JSON request body completely bypasses XSS filtering.Nearly all REST API controllers in Shopizer use
@RequestBodyto accept JSON input. For example, thePersistableCategorymodel used by the category endpoint has no server-side HTML sanitization or validation on itsnameordescriptionfields — only basic@NotEmptyconstraints exist:This means any string field in any JSON-based API endpoint is a potential injection point.
Remediation
getInputStream()andgetReader()inXssHttpServletRequestWrapperto read, sanitize, and re-wrap the JSON request body before it reaches the Spring deserialization layer. Parse the body as a JSON tree, recursively sanitize all string values, then return a newServletInputStreambacked by the sanitized content.Content-Security-Policy,X-Content-Type-Options: nosniff,X-Frame-Options: DENY, andX-XSS-Protection: 1; mode=block./services/**.