WHITEPLUS TechBlog

株式会社ホワイトプラスのエンジニアによる開発ブログです。

LaravelのSQLインジェクション対策の実装方法を調べてみた

はじめに

こんにちは。 ホワイトプラスのコアシステム開発Gエンジニアのyamauchiです。

現在のシステム開発ではフレームワークやライブラリを用いた開発が一般的であり、ありがたいことに元からセキュリティ対策が備わっているものが多くあります。 より便利になった環境でエンジニアとしてのキャリアをスタートした自分は良くも悪くもセキュリティを意識する機会が減ってきています。

最近、情報処理推進機構(IPA)が発行した「安全なウェブサイトの作り方」を読む機会があったため、 今回は、代表的なセキュリティ実装であるSQLインジェクション対策を改めて学習し、ガイドラインに記載されている対策毎に、普段触っているLaravelの実装例をまとめてみました。

安全なウェブサイトの作り方とは

「安全なウェブサイトの作り方」は、ウェブサイトのセキュリティリスクに対処するための包括的なガイドラインです。 この中ではSQLインジェクション、クロスサイトスクリプティング(XSS)などの一般的なウェブセキュリティの脅威に対する防御策が詳細に説明されています。

SQLインジェクション

SQLインジェクションのおさらい

問題のあるSQL文の組み立て方によりデータベースが不正利用される攻撃をSQLインジェクションと言います。

例えば、以下のようなユーザー名とパスワードを入力することでログインができるWEBサービスがあるとします。

<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

public function login(Request $request) {
    // ユーザーからの入力を取得
    $name = $request->input('name');
    $password = $request->input('password');

    // 生のSQLクエリ
    $sql = "SELECT * FROM user WHERE name =" . $name . " AND password = " . $password;
    $users = DB::select(DB::raw($sql));

    // 以下、ログイン処理
}
?>

仮に"'admin' --"という文字列をユーザー名として入力すると以下のようなクエリが組み立てられます(--以降の青文字はコメントアウトされる)

SELECT * FROM user WHERE name = 'admin' -- AND password = '';

なんとパスワードの条件が無効化され、管理者パスワードを入力せずとも管理者としてログインできてしまいました。

1. SQL文の組み立ては全てプレースホルダで実装する

プレースホルダとは、パラメータを変数としてクエリ内に配置し、予めクエリを構文解析にかけ、クエリを実行するタイミングで変数にパラメータをバインドする機構のことです。 構文解析を終えてからパラメータを変数にバインドすることでパラメータをSQLの構文として扱えなくなり、攻撃を防ぐことができます。

プレースホルダには動的プレースホルダと静的プレースホルダの2つの種類があり、クエリを組み立てる場所に違いがあります。

  • 静的プレースホルダ:DBでSQLのクエリの組み立てを行う
  • 動的プレースホルダ:アプリケーションでSQLのクエリの組み立てを行う

SQLの構文解析を行うタイミングで、パラメータのバインドを行うため後からクエリが変更されることがないため、静的プレースホルダの方がより安全だと言われています。

先ほどの例に交えてプレースホルダを使用した場合、実行するクエリは以下のようになります(赤文字が丸々nameのパラメータとして認識される)

SELECT * FROM user WHERE name = 'admin -- AND password = ''';

Laravelの対策

LaravelのSQLインジェクション対策の実装例をいくつか紹介します。

Eloquent ORM

<?php
$users = User::where('name', $name)->where('password', $password)->get();
?>

Query Builder

<?php
$users = DB::table('users')->where('name', $name)->where('password', $password)->get();
?>

DB Facade

<?php
$users = DB::select('SELECT * FROM users WHERE name = :name', ['name' => $name]);
?>

上記はLaravelのコードの一例ですが、もちろんピュアPHPで直接PDOを用いてプレースホルダを実装することも可能です。

エミュレーションの設定

また弊社ではDBエンジンにMySQLを採用しているため、PDOの接続設定にあるPDO::ATTR_EMULATE_PREPARESを変更することで、trueは動的プレースホルダ、falseは静的プレースホルダを適用することができます。

2. SQL 文の組み立てを文字列連結により行う場合は、エスケープ処理等を行うデータ

ベースエンジンの API を用いて、SQL 文のリテラルを正しく構成する

SQLのクエリを文字列連結で組み立てる場合、初めの例でも示した通り'などの記号文字を「文字列リテラル」を囲う'と判定されてしまい問題が発生しました。 この問題をプレースホルダとは別のエスケープ処理を行うことで解決することができます。

例えばnameにMcDonald'sさんを指定した場合、人名の'が文字列リテラルを囲んでいる'として扱われ、s'がはみ出ることでSyntax Errorを引き起こします。

SELECT * FROM users WHERE name = 'McDonald's'

DBエンジンにとって意味のある記号を以下の方法でエスケープすることでnameに'を指定することができます。

SELECT * FROM users WHERE name = 'McDonald''s'
-- MySQLの場合、`'`もしくは`\`を対象の文字列の前におくことでエスケープすることができます。

エスケープ処理はDBエンジンやそれぞれの設定によって方法が変わってくるため、実装する前にサーバー側で使用している言語とDBエンジンの組み合わせを確かめてみる必要がありそうです。 また特にこだわりがなければプレースホルダを用いてクエリを実行する方が無難だと思いました。

最後に

記事を作成する上で「問題が起きないようにとりあえずフレームワークの機能に則ってクエリを発行していた」状態から、「メリデメの比較や目的に応じて実装の選択を考えられる」状態に変化できたことが大きな収穫でした。 今回はSQLインジェクションにフォーカスして記事を作成しましたが、他にも様々なセキュリティ実装があるため引き続き知識をつけていきたいと思います。

ホワイトプラスでは、ビジョンバリューに共感していただけるエンジニアを募集しています!
ネットクリーニングの「リネット」など「生活領域×テクノロジー」で事業を展開している会社です。
どんな会社か気になった方はオウンドメディア「ホワプラSTYLE」をぜひご覧ください。
オンラインでカジュアル面談もできますので、ぜひお気軽にお問い合わせください。

採用情報 | 株式会社ホワイトプラス

参考資料