| 제목 | mickasmt next-saas-stripe-starter 1.0.0 Authorization Bypass |
|---|
| 설명 | https://github.com/mickasmt/next-saas-stripe-starter
IDOR on Stripe Customer Portal — Access Any User's Billing
File: `actions/open-customer-portal.ts` (lines 16–35)
Called from: components/forms/customer-portal-button.tsx` → `components/pricing/billing-info.tsx`
CWE: CWE-639 (Authorization Bypass Through User-Controlled Key)
OWASP A01:2021 – Broken Access Control
The vulnerability: The `openCustomerPortal` action receives `userStripeId` directly from the client and passes it straight to the Stripe API without verifying that the Stripe customer ID belongs to the authenticated user:
```typescript
export async function openCustomerPortal(userStripeId: string): Promise<responseAction> {
// ...
const session = await auth();
if (!session?.user || !session?.user.email) {
throw new Error("Unauthorized");
}
if (userStripeId) {
const stripeSession = await stripe.billingPortal.sessions.create({
customer: userStripeId, // attacker-controlled, never verified
return_url: billingUrl,
});
redirectUrl = stripeSession.url as string;
}
// ...
}
```
The `CustomerPortalButton` component receives `userStripeId` as a prop and binds it to the action: `openCustomerPortal.bind(null, userStripeId)`. An attacker can intercept or modify this call to pass any Stripe customer ID.
Attack scenario:
1. Attacker obtains or guesses another user's Stripe customer ID (e.g., `cus_XXXXX` — these are sequential/predictable).
2. Calls the `openCustomerPortal` server action with the victim's Stripe customer ID.
3. Receives a valid Stripe billing portal URL for the victim's account.
4. Can view, modify, or cancel the victim's subscription, view payment methods, and access billing history.
Fix: Look up the authenticated user's Stripe customer ID from the database instead of trusting client input:
```typescript
export async function openCustomerPortal(): Promise<responseAction> {
const session = await auth();
if (!session?.user?.id) throw new Error("Unauthorized");
const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: { stripeCustomerId: true },
});
if (!user?.stripeCustomerId) throw new Error("No subscription found");
const stripeSession = await stripe.billingPortal.sessions.create({
customer: user.stripeCustomerId,
return_url: billingUrl,
});
redirect(stripeSession.url);
} |
|---|
| 사용자 | Ghufran Khan (UID 95493) |
|---|
| 제출 | 2026. 03. 07. PM 06:06 (3 개월 ago) |
|---|
| 모더레이션 | 2026. 03. 21. PM 05:49 (14 days later) |
|---|
| 상태 | 수락 |
|---|
| VulDB 항목 | 352376 [mickasmt next-saas-stripe-starter 1.0.0 Stripe API open-customer-portal.ts openCustomerPortal 권한 상승] |
|---|
| 포인트들 | 17 |
|---|