Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Tools/GitHubGitHub/biitts/poc-cve-2026-65971
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingPapers & ResearchLearning & Education
GitHubbiitts/poc-cve-2026-65971

POC-CVE-2026-65971

Proof-of-concept and technical write-up for CVE-2026-65971 — SQL injection via the sortDirection Livewire property in power-components/livewire-powergrid (< 6.10.4)

View Repository
192 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-65971 — SQL Injection in Livewire PowerGrid via sortDirection

Proof-of-concept and full technical write-up for CVE-2026-65971 / GHSA-7fgc-3h6c-698r, an SQL injection in power-components/livewire-powergrid reachable through the public Livewire property sortDirection.

CVECVE-2026-65971
GHSAGHSA-7fgc-3h6c-698r
Packagepower-components/livewire-powergrid (Composer / Packagist)
Affected>= 6.0.0, < 6.10.4
Patched6.10.4
WeaknessCWE-89 — Improper Neutralization of Special Elements used in an SQL Command
Severity7.6 High — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
Reported byCaio Fabrício (@BiiTts)
DisclosureCoordinated, via GitHub private security advisory
├── poc/exploit_powergrid_sqli.py     working exploit — confirm + blind extraction
├── lab/                              build the vulnerable app to reproduce it yourself
├── evidence/EVIDENCE.txt             raw lab notes from confirmation
├── patch/security-fix-v6.10.4.diff   the security-relevant portion of the official fix
└── detection/                        Sigma rules + Nuclei template for defenders

🧠 Summary

PowerGrid is a datatable component for Laravel + Livewire (~2k stars, widely used in Laravel admin panels). Its sort state lives in two public Livewire properties:

public string $sortField = 'id';
public string $sortDirection = 'asc';

In Livewire, a public property is part of the component's wire format — any client that can reach the component can set it through POST /livewire/update. That is by design; the security boundary is what the server does with the value.

PowerGrid's naturalSort() feature builds a raw ORDER BY expression containing the literal placeholder {sortDirection}, and a pipeline substitutes that placeholder with the raw, unvalidated property value before handing the string to orderByRaw(). The direction keyword therefore lands verbatim inside SQL.

Laravel's own orderBy() rejects anything that is not asc/desc, and that validation is what makes the ordinary sort path safe. The bug is that a second, unvalidated path to the same clause exists — and an attacker can reach it while skipping the validating one entirely (see The bypass).

Result: arbitrary SQL in the ORDER BY clause, exploitable as a blind boolean/time-based oracle to read any data the database user can read.


🔥 Impact

Anyone who can reach a PowerGrid table that uses naturalSort can read arbitrary data from the database — other tables, password hashes, session tokens, API keys, cross-tenant records — using a time-based / boolean oracle.

  • Confidentiality: High. Full read of anything the DB user can SELECT.
  • Integrity: Low. Stacked queries are blocked by PDO MySQL's default configuration, so ; UPDATE ... does not execute. Write impact is limited to what a subquery can trigger.
  • Availability: Low. The same primitive gives an attacker SLEEP() and heavy subqueries — trivially abusable to pin database threads.
  • Typical placement is the aggravating factor. PowerGrid tables sit in admin panels and multi-tenant back-offices — exactly where the interesting data is. A low-privileged tenant user reaching one such table can exfiltrate the whole database.

Privileges required is PR:L because a datatable normally sits behind application authentication. If the affected table renders on an unauthenticated page, recompute with PR:N → 8.2 High.


🧩 Root cause — the complete taint chain

Three files, three stages. All references are to the vulnerable tag v6.10.3.

Stage 1 — Source: an attacker-controlled public property

src/Concerns/Sorting.php

public string $sortField = 'id';        // line 11
public string $sortDirection = 'asc';   // line 13

Neither property has a whitelist, a validation rule, or a normalizing setter. sortDirection is only ever assigned or flipped:

public function reverseSort(): string    // line 37
{
    return $this->sortDirection === 'asc' ? 'desc' : 'asc';
}

updatedSortDirection() (line 103) exists — the natural place for validation — but in v6.10.3 it only handles lazy-loading bookkeeping. It never inspects the value.

Because Livewire hydrates public properties straight from the request, sortDirection is fully attacker-controlled, as an arbitrary string, at this point.

Stage 2 — The raw clause: naturalSort() plants a placeholder

src/Providers/Macros.php, lines 102–116 — the naturalSort column macro:

Column::macro('naturalSort', function (bool $when = false, ?string $tableName = null): Column {
    $this->enableSort();

    if ($when) {
        $this->rawQueries[] = [
            'method'   => 'orderByRaw',                          // <-- raw sink
            'sql'      => Sql::sortStringAsNumber($this->dataField),
            'bindings' => [],
        ];
    }

    return $this;
});

Sql::sortStringAsNumber() resolves to a per-driver expression built by getSortSqlByDriver() in src/DataSource/Support/Sql.php (lines 60–100). Every driver variant ends with the same literal placeholder:

$default = "$sortField+0 {sortDirection}";                                                          // line 76
'8.0.4'  => "CAST(NULLIF(REGEXP_REPLACE($sortField, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER) {sortDirection}",  // MySQL, line 81
'0'      => "CAST($sortField AS INTEGER) {sortDirection}",                                          // SQLite, line 84
'0'      => "CAST(NULLIF(REGEXP_REPLACE($sortField, '\D', '', 'g'), '') AS INTEGER) {sortDirection}", // PgSQL, line 87
'0'      => "CAST(SUBSTRING(...) AS INT) {sortDirection}",                                          // SQL Server, line 90

The vulnerability is driver-independent — every branch interpolates {sortDirection}.

Stage 3 — Sink: the placeholder is resolved with the raw property value

src/DataSource/Processors/Database/Pipelines/ColumnRawQueries.php

private function resolvePlaceholders(?string $sql): ?string   // line 56
{
    if (is_null($sql)) {
        return null;
    }

    return preg_replace_callback('/\{(\w+)\}/', function ($matches) {
        $property = trim($matches[1]);

        return data_get($this->component, $property, '');   // line 65 — raw property, no escaping
    }, $sql);
}

and the execution, line 52:

$query->{$method}($resolvedSql, $resolvedBindings);   // $method === 'orderByRaw'

data_get($this->component, 'sortDirection') returns the attacker's string, preg_replace_callback splices it into the SQL text, and orderByRaw() — which by contract does not escape its argument — passes it to the database.

Note the bitter irony one line below: resolveBindings() (line 69) exists, and naturalSort declares 'bindings' => []. The mechanism for safe parameterization is right there. It cannot be used for a direction keyword — ORDER BY x ? is not valid SQL, a direction can never be a bound parameter — which is precisely why a direction keyword must be allowlisted instead.

The chain in one line

POST /livewire/update  ──▶  public string $sortDirection   (Sorting.php:13, no validation)
                       ──▶  data_get($component, 'sortDirection')   (ColumnRawQueries.php:65)
                       ──▶  "CAST(...) {sortDirection}"  →  "CAST(...) asc, (SELECT SLEEP(3))"
                       ──▶  orderByRaw($sql)   (ColumnRawQueries.php:52)
                       ──▶  MySQL/MariaDB/PgSQL/SQLite/MSSQL

Download Tool