Mobilfunknetz: server-seitige Whitelist (Pentest-INFO)

mobileNetwork akzeptierte serverseitig jeden stripHtml-bereinigten
String – das Frontend-Dropdown beschränkte nur clientseitig. Jetzt
Whitelist TELEKOM|VODAFONE|TELEFONICA (normalizeMobileNetwork),
angewandt in Create- UND Update-Pfad (der Update-Spread reichte den
Wert vorher ungefiltert an Prisma durch). Unbekannte/leere Werte
werden zu null normalisiert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 16:41:19 +02:00
co-authored by Claude Opus 4.7
parent b4dfede494
commit e8d996d07e
2 changed files with 25 additions and 1 deletions
+17 -1
View File
@@ -322,6 +322,17 @@ interface ContractCreateData {
}; };
} }
// Erlaubte Mobilfunknetze. Server-seitige Whitelist, damit über einen
// handgebauten Request kein beliebiger String in mobileNetwork landet
// (das Frontend-Dropdown beschränkt schon, der Server vertraut ihm aber
// nicht). Unbekannte/leere Werte werden zu null normalisiert.
const ALLOWED_MOBILE_NETWORKS = new Set(['TELEKOM', 'VODAFONE', 'TELEFONICA']);
function normalizeMobileNetwork(value: unknown): string | null {
if (typeof value !== 'string') return null;
const v = value.trim().toUpperCase();
return ALLOWED_MOBILE_NETWORKS.has(v) ? v : null;
}
export async function createContract(data: ContractCreateData) { export async function createContract(data: ContractCreateData) {
const { const {
energyDetails, energyDetails,
@@ -387,7 +398,7 @@ export async function createContract(data: ContractCreateData) {
mobileDetails: { mobileDetails: {
create: { create: {
requiresMultisim: mobileDetails.requiresMultisim, requiresMultisim: mobileDetails.requiresMultisim,
mobileNetwork: mobileDetails.mobileNetwork ?? null, mobileNetwork: normalizeMobileNetwork(mobileDetails.mobileNetwork),
dataVolume: mobileDetails.dataVolume, dataVolume: mobileDetails.dataVolume,
includedMinutes: mobileDetails.includedMinutes, includedMinutes: mobileDetails.includedMinutes,
includedSMS: mobileDetails.includedSMS, includedSMS: mobileDetails.includedSMS,
@@ -623,6 +634,11 @@ export async function updateContract(
if (mobileDetails) { if (mobileDetails) {
const { simCards, ...mobileData } = mobileDetails; const { simCards, ...mobileData } = mobileDetails;
// Whitelist auf das Netz anwenden, bevor mobileData per Spread an
// Prisma geht (Update-Pfad reicht sonst beliebige Strings durch).
if ('mobileNetwork' in mobileData) {
mobileData.mobileNetwork = normalizeMobileNetwork(mobileData.mobileNetwork);
}
const existing = await prisma.mobileContractDetails.findUnique({ const existing = await prisma.mobileContractDetails.findUnique({
where: { contractId: id }, where: { contractId: id },
}); });
+8
View File
@@ -97,6 +97,14 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt ## ✅ Erledigt
- [x] **🔒 Mobilfunknetz: server-seitige Whitelist (Pentest-INFO)**
- `mobileNetwork` akzeptierte serverseitig jeden (stripHtml-bereinigten)
String das Frontend-Dropdown beschränkte nur clientseitig. Jetzt
Whitelist `TELEKOM|VODAFONE|TELEFONICA` in `contract.service.ts`
(`normalizeMobileNetwork`), angewandt in Create- UND Update-Pfad
(Update-Spread wurde vorher ungefiltert durchgereicht). Unbekannte/
leere Werte → null.
- [x] **🔒 Rate-Limiting: IPv6-Bypass-Härtung (ipKeyGenerator)** - [x] **🔒 Rate-Limiting: IPv6-Bypass-Härtung (ipKeyGenerator)**
- Die Rate-Limiter mit eigenem `keyGenerator` (`loginRateLimiter`, - Die Rate-Limiter mit eigenem `keyGenerator` (`loginRateLimiter`,
`staffPasswordReAuthLimiter`) keyten auf die **volle** `req.ip`. `staffPasswordReAuthLimiter`) keyten auf die **volle** `req.ip`.