---
title: "Вразливість у Livewire PowerGrid: пошук через searchableRaw() показує чужі рядки"
url: https://laravelukraine.com/blog/vrazlivist-u-livewire-powergrid-posuk-cerez-searchableraw-pokazuje-cuzi-riadki
date: 2026-11-12
---

# Вразливість у Livewire PowerGrid: пошук через searchableRaw() показує чужі рядки

У [Livewire PowerGrid](https://github.com/Power-Components/livewire-powergrid) знайшли вразливість [GHSA-wj8p-8c55-266p](https://github.com/Power-Components/livewire-powergrid/security/advisories/GHSA-wj8p-8c55-266p): колонка з `searchableRaw()` або `searchableJson()` додає свою умову пошуку так, що вона обходить усі обмеження, які таблиця задає в `datasource()`. Автентифікований користувач, якому доступне поле пошуку, бачить рядки, яких бачити не повинен, наприклад записи інших користувачів. Критичність середня (CVSS 4.3, CWE-863 - неправильна авторизація), повідомив про проблему pboese.

### Хто уражений і як оновитися

Уражені всі версії до 7.0.0 включно. Автор звіту перевірив 6.11.1 і 7.0.0, а старіші версії з `searchableRaw()` найімовірніше теж уражені. Під загрозою таблиця, у якої одночасно:

- обмеження рядків прописане в `datasource()` (поточний користувач, статус, вкладка, категорія);
- хоча б одна колонка оголошена через `searchableRaw()` або `searchableJson()`.

Виправлення вийшло 31 жовтня в [PowerGrid 7.0.1](https://github.com/Power-Components/livewire-powergrid/releases/tag/v7.0.1). Сам фікс живе в пакеті `power-components/turbine`, на якому побудовано PowerGrid 7: версія [0.14.1](https://github.com/Power-Components/turbine/releases/tag/v0.14.1) застосовує сирі `orWhere`-умови всередині групи пошуку, а PowerGrid 7.0.1 тепер вимагає `turbine ^0.14.1`. Перевірте, що стоїть у проєкті, й оновіться разом із залежностями:

```bash
  composer show power-components/livewire-powergrid power-components/turbine
  composer update power-components/livewire-powergrid --with-dependencies
```

Із гілкою 6.x гірше: остання її версія 6.11.1 уражена, а патча для неї немає. Перехід на 7.x - це окрема робота (PHP 8.3+, Laravel 12+, Livewire 4, Tailwind CSS 4), тож до нього варто прибрати `searchableRaw()` і `searchableJson()` з таблиць, які обмежують рядки в `datasource()`.

### Як ламається запит

Звичайні колонки з `searchable()` PowerGrid загортає в одну групу `where(function ...)`. А `searchableRaw()` зберігає свою умову з методом `orWhereRaw`, і окремий крок конвеєра застосовує її вже після групи пошуку, прямо до кореневого запиту. В advisory наведено SQL, який виходить у підсумку:

```sql
  where `orders`.`user_id` = ? and (`orders`.`number` LIKE ? or ...) or customers.name LIKE ?
```

`AND` у SQL зв'язує сильніше за `OR`, тож база читає це так:

```sql
  (`orders`.`user_id` = ? and (...)) or customers.name LIKE ?
```

Друга половина не знає нічого про `user_id`. Варто ввести в пошук "acme", і таблиця покаже замовлення всіх користувачів, чий клієнт зветься Acme. Експорт будує набір даних тими самими кроками, тож вивантажить ті самі чужі рядки у файл.

Зверніть увагу, що для `relationSearch()` у 7.x цю пастку вже обійшли: умова пошуку по зв'язку стоїть усередині групи, і коментар у коді описує саме цей ризик. `searchableRaw()` просто не отримав того самого лікування - до 7.0.1.

### Чому глобальні скопи вистояли

Якщо обмеження на "свої" рядки зроблено глобальним скопом (Global Scope) моделі, вразливість його не зачіпає. Eloquent застосовує глобальні скопи наприкінці, перед виконанням запиту, і коли серед уже наявних умов є `OR`, сам бере їх у дужки, а умову скопу додає через `AND` окремо:

```sql
  where ((`orders`.`number` LIKE ? or ...) or customers.name LIKE ?) and `orders`.`user_id` = ?
```

Умови, написані прямо в `datasource()`, такого захисту не мають: для Eloquent це звичайні `where` того самого рівня, що й рядок `orWhereRaw`, доданий пізніше. А саме в `datasource()` найчастіше й обмежують таблицю поточним користувачем.

### Та сама пастка у власному коді

PowerGrid тут лише приклад. `orWhere` без групування - одна з найпоширеніших помилок в Eloquent, і вона так само тихо ламає фільтр доступу в контролерах, API та звітах. Кілька звичок, які її прибирають:

- **Будь-який `orWhere` - тільки всередині групи.** Пошук по кількох полях загортайте в замикання або беріть `whereAny()`, який групує умови сам:

```php
  Order::query()
      ->where('user_id', $request->user()->id)
      ->where(function ($query) use ($term) {
          $query->where('number', 'like', "%{$term}%")
              ->orWhere('note', 'like', "%{$term}%");
      });

  // або
  Order::query()
      ->where('user_id', $request->user()->id)
      ->whereAny(['number', 'note'], 'like', "%{$term}%");
```

- **Дивіться на SQL.** `toSql()` або `toRawSql()` у тесті чи в `tinker` одразу покаже, де стоять дужки. Саме так перевіряє виправлення новий тест у Turbine: він очікує в запиті `"in_stock" = ? and (dishes.name like ?)`.

- **Тестуйте поведінку, а не лише SQL.** Найнадійніший тест на такі речі - два користувачі зі схожими даними і перевірка, що пошук одного не віддає записів іншого:

```php
  it('does not leak other users orders through search', function () {
      $alice = User::factory()->create();
      $bob = User::factory()->create();

      Order::factory()->for($bob)->create(['number' => 'ACME-2']);

      $this->actingAs($alice)
          ->get('/orders?search=ACME')
          ->assertOk()
          ->assertDontSee('ACME-2');
  });
```

Такий тест ловить проблему незалежно від того, де вона сидить: у вашому запиті, у пакеті таблиць чи в макросі, який хтось додав рік тому.

### Що зробити зараз

Якщо в проєкті є PowerGrid, пройдіться пошуком по `searchableRaw(` і `searchableJson(`. Немає збігів - вразливість вас не стосується, але оновитися до 7.0.1 все одно варто. Є збіги в таблицях, що обмежують рядки в `datasource()`, - оновлюйтеся на 7.x негайно або, на 6.x, приберіть ці колонки з пошуку до переходу. Для гілки 6.x це не перша проблема за рік: у липні в ній уже закривали [SQL-ін'єкцію через `sortDirection`](https://github.com/Power-Components/livewire-powergrid/security/advisories/GHSA-7fgc-3h6c-698r) (CVE-2026-65971, виправлено в 6.10.4).

А у власному коді - той самий пошук, тільки по `orWhere`: кожен, що стоїть не всередині `where(function ...)`, вартий другого погляду.
