(jp) =
最近、Laravel のすべてのクエリ ビルダー機能が「安全」であるとは限らないことを知りました。 これは、アプリケーションが SQL インジェクションの脆弱性にさらされる可能性があるため、ユーザー入力を直接渡すべきではないことを意味します。
ここ数日で、これらの危険な機能に関するコミュニティの知識がほとんどないことが明らかになりました。 多くの開発者は、私と同様に、Laravel クエリ ビルダが SQL インジェクション攻撃を完全に防いでいると想定しています。
このブログ投稿は、何が安全で何が安全でないかについての認識を高めることを目的としています。
# SQL インジェクションの脆弱性?
この脆弱性は Laravel 5.8.11 の時点で修正されていることに言及することから始めましょう。 技術的にはこれを「脆弱性」と呼ぶことができますが、Laravel 開発者は、この種の問題を防ぐ役割も果たしていることを知っておく必要があります。
問題を調べてみましょう。
Laravel には、クエリで選択する列を手動で指定する機能があります。 また、JSON データをクエリするための簡略表記も提供します。
<hljs type>Blog</hljs>::<hljs prop>query</hljs>()
-><hljs prop>addSelect</hljs>('<hljs green>title</hljs>-><hljs blue>en</hljs>');
<hljs keyword>SELECT</hljs> <hljs prop>json_extract</hljs>(`<hljs green>title</hljs>`, '$."<hljs blue>en</hljs>"') <hljs keyword>FROM</hljs> blogs;
手で書く代わりに json_extract
、簡略化された ->
Laravel が正しい SQL ステートメントに変換する構文。
ただし注意してください: Laravel はこの変換中にエスケープを行いません。 次の例を検討してください。
<hljs type>Blog</hljs>::<hljs prop>query</hljs>()
-><hljs prop>addSelect</hljs>('<hljs green>title</hljs>-><hljs blue>en</hljs><hljs red>'#</hljs>');
挿入することで '#
入力では、手動で閉じることができます json_extract
関数を呼び出し、クエリの残りを無視します。
<hljs keyword>SELECT</hljs> <hljs prop>json_extract</hljs>(`<hljs green>title</hljs>`, '$."<hljs blue>en</hljs><hljs red>'#</hljs><hljs textgrey>"') FROM blogs;</hljs>
このクエリは構文エラーのために失敗しますが、次のクエリはどうでしょうか?
<hljs keyword>SELECT</hljs> <hljs prop>json_extract</hljs>(
`<hljs green>title</hljs>`,
'$."<hljs blue>en</hljs><hljs red>"'))
FROM blogs RIGHT OUTER JOIN users ON users.id <> null
#</hljs>
<hljs textgrey>"') FROM blogs;</hljs>
に外部結合を追加しています users
テーブル。 基本的にその中のすべてのデータを選択します。
参考までに、これは悪意のあるコードの URL エンコード バージョンです。
%22%27%29%29+FROM+blogs+RIGHT+OUTER+JOIN+users+ON+users.id+%3C%3E+null%23
パブリック API からブログ投稿をクエリするために、アプリケーションに次のエンドポイントがあるとします。
Route::get('/posts', function (Request $request)
$fields = $request->get('fields', []);
$users = Blog::query()->addSelect($fields)->get();
return response()->json($users);
);
この API の利用者はいくつかのフィールドにしか関心がない可能性があるため、 fields
フィルター。 JSON api 仕様のスパース フィールドセットに似たもの。
エンドポイントは次のように使用できるようになりました。
/blog?fields[]=url&fields[]=title
代わりに、悪意のあるコードを挿入します。
/blog?fields[]=%22%27%29%29+FROM+blogs+RIGHT+OUTER+JOIN+users+ON+users.id+%3C%3E+null%23
クエリに追加されます。 クエリ結果を JSON として返すことで、users テーブルの完全な内容を確認できます。
Blog::query()->addSelect([
'%22%27%29%29+FROM+blogs+RIGHT+OUTER+JOIN+users+ON+users.id+%3C%3E+null%23'
])->get();
この攻撃を可能にするには、次の 2 つのことが必要です。
- アクセス可能な API エンドポイント。攻撃者が悪意のあるコードを
select
またaddSelect
. プロジェクトでこれを手動で行っていない可能性があります。 簡単な API エンドポイントと URL フィルタリングのためにこの機能を提供する一般的なパッケージがあります。 - エントリ ポイント テーブルには、JSON データを含む列が必要です。 そうでなければ、
json_extract
関数は失敗し、クエリが停止します。 ただし、エントリ ポイントから、すべてのデータにアクセスできます。
# 防止?
前述のように、この特定の脆弱性は Laravel 5.8.11 で修正されました。 最新の Laravel バージョンを常に最新の状態に保つことをお勧めします。
ただし、さらに重要なことは、開発者は、ホワイトリストなしで、ユーザー入力が列を直接指定することを決して許可してはならないということです。 前の例では、特定のフィールドのリクエストのみを許可することで、この攻撃を防ぐことができました。これにより、問題を完全に防ぐことができます。
次に、広く使用されているパッケージの 1 つです。 spatie/laravel-querybuilder
、 開く addSelect
意図的に。 これは、私たちのパッケージを使用している Web サイトが根本的な問題に対して脆弱であることを意味していました。 私たちはすぐにそれを修正し、Freek はそれについて詳しく書いています。 当社のパッケージを使用していて、最新の Laravel バージョンに更新できない場合は、すぐにパッケージを更新する必要があります。
最後に、Laravel ドキュメントも更新され、クエリ ビルダーを使用するときにユーザー入力を列に直接渡さないように開発者に警告するようになりました。