Krayin CRM 2.2.0 - Cross-User IDOR Across Lead, Contact, and Activity Controllers

Authenticated cross user idor vulnerability has been identified in Webkul's Krain CRM 2.2.0.

B
Bytium Operators
2 min read

In Krayin CRM 2.2.0, the DataGrid (list) queries scope results correctly to the requesting staff user via bouncer()->getAuthorizedUserIds(), but the sibling single-object and write endpoints re-fetch by URL id through findOrFail() with no ownership re-check. An authenticated staff user — including the lowest-privilege Sales Rep with view_permission = individual — sees only their own records in the UI, but can read, modify, delete, mass-modify, or download attachments belonging to any other user by incrementing the URL id.

Reproduction

curl -X PUT -H "X-XSRF-TOKEN: $XSRF" -H "X-Requested-With: XMLHttpRequest" -b "$JAR" \
     --data-urlencode "title=HIJACKED" \
     --data-urlencode "description=IDOR PoC" \
     "http://target/admin/leads/edit/1"

The DB row's user_id stays at 1 (the original owner), but the title and description are fully attacker-controlled. The original owner sees the modified content as if they had written it themselves.

Attachment exfiltration uses the same primitive:

curl -b "$JAR" -o "victim_contract.pdf" "http://target/admin/activities/download/<id>"

In production deployments, activity attachments commonly contain signed contracts, PII exports, and pasted credentials.

Impact

  • Confidentiality: full read of every lead, contact, organization, activity note, and activity file attachment in the database via id enumeration.
  • Integrity: silent overwrite of any other user's records with the original owner preserved in the audit trail. The author's identity is hidden from any audit log that derives the actor from the row owner.
  • Operational: the QuoteController::mail primitive sends a target tenant's quote PDF to a recipient address attached to the lead, opening a path for social-engineering against the legitimate prospect.

The threat model is "any authenticated staff user." The CRM's own access-control model — designed around view_permission = individual — does not hold up once the attacker stops clicking and starts typing URLs.

Fix

Replicate the ownership check from LeadController::view() into every write and download method. Example:

$lead = $this->leadRepository->findOrFail($id);
$userIds = bouncer()->getAuthorizedUserIds();
if ($userIds && ! in_array($lead->user_id, $userIds)) abort(403);

Better: extract the check into a Laravel Policy (LeadPolicy::manage, ActivityPolicy::download, etc.) or a trait, so new modules and new methods inherit the check by default rather than each controller re-implementing it.

Timeline

  • 2026-05-04 — Bug discovered during audit of Krayin CRM 2.2.0. Verified with live PoC on stock Docker install, non-admin Sales Rep account.
  • 2026-05-07 — Vendor notified by email with 7-day disclosure deadline.
  • 2026-05-16 — Coordinated-disclosure deadline. If no fix or vendor engagement by this point, advisory and CVE submission go public.

Credit

Discovered and reported by Jobyer Ahmed — Offensive Security Researcher.

References

Need help?

Talk with Bytium

Share your goals and we'll shape the right testing, detection, or compliance plan.

Talk to security