-
Mahmoud Aglan authored
Two live disclosures and one latent account takeover, plus the infrastructure defects that hid them. AuthOtpController::verify() accepted a constant '0000' whenever auth_otp_mode was 'demo' — the value every instance was seeded with — and then minted a Sanctum token for whichever active user matched the submitted phone number, staff included. It was not exploitable as written, because 2026_08_30_000004 had normalised users.phone to digits-only local form while normalizePhone() produced +20…, so the lookup missed. That is one plausible bug-fix away from being live, which is why the whole surface goes rather than the branch. Deleting /api/v1 also removes: broadcast/send pushing to every device in the academy with no permission check; ReceiptController's inverted ownership check, which made any non-participant invoice world-readable to any token; PaymentController::initiate with no ownership check at all; and DeviceController keying updateOrCreate on the FCM token alone, letting one user claim another's device. None of it is replaced — the member-facing surface is the session-authenticated web portal, so a second token-authenticated surface meant building and authorizing everything twice. bootstrap/app.php built a full diagnostic payload for any 500 and errors/500 rendered it to the browser, ungated by APP_DEBUG. The session it printed carries password_hash_web — the signed-in user's bcrypt hash — alongside the last ten queries, the request input and the headers. Now gated on debug, auth keys stripped by prefix even there, and the production page is self-contained with no CDN. Detail still reaches storage/logs, keyed by the error id shown to the user. ParentHome::$activeChildId was validated in mount() and selectChild() but used raw in render() at eight query sites. Livewire is ^4.3, where a public property is settable from the browser, so those checks were decoration: a guardian could walk participant ids and read any child's balance, attendance and evaluations. Locked, and re-validated in render() since the child list can change between requests. ParentExcuseForm wrote the attachment — typically a child's medical note — to the PUBLIC disk, then discarded the record and flashed success. The parent believed the absence was excused; nothing was stored, and the record kept feeding the consecutive-absence threshold that auto-suspends a participant. It now stores nothing and says so, until excuses are modelled properly. Infrastructure, because each one hid a failure rather than causing one: entrypoint continued booting after a failed migration, which serves a stale schema and silently blocks every later migration forever; the env whitelist had no PAYMOB_, so config:cache baked null credentials and the gateway failed closed with no error anywhere; nginx's static-asset regex answered =404 for /sw.js before PHP saw it; and Route::fallback returned 200 for every unrouted path, so a deleted endpoint served a website page instead of 404. Verified: 43/44 tests pass. The one failure is ExampleTest, which fails identically on unmodified main — confirmed by stashing. Two new tests pin both disclosures so they cannot return. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
883391c7
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| 403.blade.php | ||
| 404.blade.php | ||
| 500.blade.php |