Mass-Assignment-Schutz: Bankkarte/Adresse/Ausweis (Pentest R155)
Die create/update-Services reichten rohen req.body an Prisma durch, wodurch
customerId (Owner) und id (PK) per Update mutierbar waren - staff-only, kein
Cross-Tenant-Bruch, aber echte Integritaetsschwaeche; mit dem neuen
cardNumber-Feld liegt zudem Finanz-PII auf dieser Flaeche.
Fix: explizite Feld-Whitelist im Service (create+update) fuer BankCard,
Address und IdentityDocument - nur benannte Spalten gehen an Prisma, kein
...data/req.body-Spread mehr. Controller-Helper pickBankCardFields haelt
zusaetzlich die Audit-Logs sauber (keine Phantom-Eintraege injizierter Keys).
Verifiziert: updateBankCard mit {customerId:99999, id:88888, bogusField, ...}
-> id+customerId unveraendert, nur cardNumber gesetzt, Fremdfelder ignoriert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,23 @@ import {
|
||||
} from '../utils/accessControl.js';
|
||||
import { ApiError } from '../utils/apiError.js';
|
||||
|
||||
// Mass-Assignment-Schutz für Bankkarten (Pentest R155): nur diese Felder dürfen
|
||||
// aus dem Request-Body übernommen werden – NICHT customerId/id/createdAt/… Nur
|
||||
// tatsächlich gesendete Keys landen im Ergebnis, damit Partial-Updates und das
|
||||
// Änderungs-Audit korrekt bleiben.
|
||||
const BANK_CARD_ALLOWED_FIELDS = [
|
||||
'accountHolder', 'iban', 'cardNumber', 'bic', 'bankName', 'expiryDate', 'description', 'isActive',
|
||||
] as const;
|
||||
|
||||
function pickBankCardFields(body: unknown): Record<string, unknown> {
|
||||
const src = (body && typeof body === 'object') ? (body as Record<string, unknown>) : {};
|
||||
const out: Record<string, unknown> = {};
|
||||
for (const key of BANK_CARD_ALLOWED_FIELDS) {
|
||||
if (key in src) out[key] = src[key];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Customer CRUD
|
||||
export async function getCustomers(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
@@ -401,7 +418,8 @@ export async function createBankCard(req: AuthRequest, res: Response): Promise<v
|
||||
try {
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
if (!(await canAccessCustomer(req, res, customerId))) return;
|
||||
const card = await customerService.createBankCard(customerId, req.body);
|
||||
const data = pickBankCardFields(req.body);
|
||||
const card = await customerService.createBankCard(customerId, data as any);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'BankCard',
|
||||
resourceId: card.id.toString(),
|
||||
@@ -421,7 +439,10 @@ export async function updateBankCard(req: AuthRequest, res: Response): Promise<v
|
||||
try {
|
||||
const cardId = parseInt(req.params.id);
|
||||
if (!(await canAccessBankCard(req, res, cardId))) return;
|
||||
const data = req.body;
|
||||
// Mass-Assignment-Schutz (Pentest R155): nur erlaubte Felder aus dem Body,
|
||||
// kein customerId/id/Fremdfeld. Das Audit unten läuft über diese saubere
|
||||
// Menge (keine Phantom-Einträge). Enforcement zusätzlich im Service.
|
||||
const data = pickBankCardFields(req.body);
|
||||
|
||||
// Vorherigen Stand laden für Audit
|
||||
const before = await prisma.bankCard.findUnique({ where: { id: cardId } });
|
||||
|
||||
Reference in New Issue
Block a user