自分だけのクイズ作成 - Quipha公開中

【Laravel】敢えて試すSQLインジェクション【仕組みを知るには実装】

Laravel
スポンサーリンク

はじめに

最近話題になりましたSQLインジェクション
いわゆる脆弱性ですが、とあるWebシステムで見つかったそうです。(すっとぼけ)

ワクチン予約システムで話題の「SQLインジェクション」って何? 試すと法律違反? 専門家に聞く
「SQLインジェクションができる」と話題になった大規模接種センターの新型コロナワクチン接種予約システム。Twitterには実際に試すと犯罪になるという声もあったが、そもそもSQLインジェクションとは何で、実行すると本当に犯罪になるのか。専門家に聞いた。

「SQLインジェクションなんて初歩的なミスを・・」とか、「名前は聞いたことあるけど・・」とか、「最近のフレームワークを使っていれば安心でしょ?」など、色々な意見があるかと思います。

今回は、敢えてSQLインジェクションの脆弱性を含んだシステムを作成してみたいと思います。
また、人気のあるLaravelというフレームワークで試します。

Laravelを使用したことがない人でも構築できるように、イチから解説しています。
対策や仕組みを知るには、実際に試してみるのが手っ取り早いですよね!?

本記事ではLaravelを使用して実際に試してみますが、SQLインジェクションはどの言語・フレームワークでも概念は同じです。
しっかりと仕組みを押さえておきましょう。

お断り

試す場合は、プライベートな環境(ローカル等)で行いましょう。
また、脆弱性のあるコードを本記事では扱いますが、使用しないように気をつけてください。

環境

  • Laravel Framework 8.42.0
  • PHP v7.4.19

Quipha

個人開発ですがQuiphaというサービスを開発しました。
Webアプリケーションであり、Laravelで作成しました。もちろん脆弱性の対応を意識して開発しています。

良かったら、会員登録して動作を試してみて下さい。

フレームワークについて

Webシステムなどはじめ、開発においては通常はフレームワークを使用します。

フレームワークとはその名の通り土台であり、予め機能が用意されていたり、ルールが決まっているなど、不具合を埋め込みにくくするメリットもあります。

その一環として、脆弱性に対する対応も考慮されているケースもあります。
今回紹介するLaravelも、普通に実装すれば、SQLインジェクションを埋め込む可能性は低いと思います。

逆に「フレームワークを使ったら脆弱性のあるコードは入り込まないよね?」というのは間違いですので、気をつけましょう。

フレームワークを使用する場合、きちんとフレームワークのマニュアルを読み、特性を知ることが大事です。
学習コストが必要ですが、結果的には工数削減に繋がります。

Laravelで実装(問題のないコード)

それでは早速、実装を行いましょう。
いきなり脆弱性のあるコードを埋め込むのではなく、まずは問題のない実装を行います。

プロジェクトの作成

以下のコマンドで、Laravelのプロジェクトを作成します。
プロジェクト名は「sql-injection」にしました。(任意です)

composer create-project laravel/laravel sql-injection --prefer-dist

コマンドで、npmパッケージのインストールとビルドを行います。

npm install
npm run dev

MySQLのデータベースを作成し、環境ファイル(.env)の設定を行います。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

以下のコマンドで開発サーバを起動します。

php artisan serve

以下のURLにアクセスし、初期画面が表示されることを確認します。

http://localhost:8000

作成するサイト

  • 簡易的なログイン画面を作成。
  • ログインIDとパスワードを入力しログインを行う。
  • IDとパスワードが一致するレコードが存在する場合は、ログイン成功とする。
スポンサーリンク

テーブルの作成

テーブルを作成していきましょう。マイグレーションファイルを作成します。
初期状態でusersというテーブルが作成されますので、今回は別途アカウントテーブルを作成することにしました。

以下のコマンドを実行し、マイグレーションファイルを生成します。

php artisan make:migration create_accounts_table

マイグレーションファイルが作成されます。(ファイル名は現在時刻)

database/migrations/2021_05_19_140741_create_accounts_table.php

マイグレーションファイルを編集します。
今回は、ログインIDとパスワードの項目を追加します。(テーブル定義は細かく掘り下げません)

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAccountsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('accounts', function (Blueprint $table) {
            $table->id();
            $table->string('login_id');
            $table->string('password');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('accounts');
    }
}

マイグレーションを実行し、テーブルを作成します。

php artisan migrate

この時点でMySQLのデータベースを確認し、テーブルが作成されていることを確認してください。

ダミーデータの作成

Laravelのシーダーという機能で、ダミーデータを作成します。
以下のコマンドを実行し、シーダーを作成します。

php artisan make:seeder AccountsSeeder

シーダークラスが作成されます。

database/seeders/AccountsSeeder.php

ログインIDとパスワードのダミーデータを挿入する処理を追加します。

ログイン情報
  • ログインID : login-user
  • パスワード : password
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class AccountsSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('accounts')->insert([
            'login_id' => 'login-user',
            'password' => 'password',
        ]);
    }
}

シーダーを実行します。

php artisan db:seed --class=AccountsSeeder

アカウントテーブルにデータが挿入されたことを確認しましょう。

モデルの作成

先ほど作成したテーブルに対するモデルクラスを作成します。

コマンドを実行します。

php artisan make:model Account

クラスファイルが作成されますが、特に変更の必要はありません。

画面の作成

コマンドを実行し、コントローラーを作成します。

php artisan make:controller LoginController

web.phpを修正します。
ログイン入力画面と、結果画面のルーティングを追記します。

use App\Http\Controllers\LoginController;

Route::get('/login', [LoginController::class, 'index'])->name('login-index');
Route::post('/login', [LoginController::class, 'result'])->name('login-result');

続いて、画面テンプレートを作成します。(ログイン画面)
以下のファイルを作成します。

resources/views/login.blade.php

テンプレートファイルにログインフォームを記述します。

<html>
<body>
    <form method="POST" action="{{ route('login-result') }}">
        @csrf
        <input type="text" name="id" placeholder="ID"><br>
        <input type="text" name="password" placeholder="PASSWORD"><br>
        <input type="submit" value="ログイン">
    </form>
</body>
</html>

@csrfは、クロスサイトリクエストフォージェリ(CSRF)の対応です。(脆弱性の一つ)
また、actionのURLはroute経由で取得すると、web.phpの定義と連動できますので保守性が良くなるでしょう。

次に、以下のファイルを修正します。

App\Http\Controllers\LoginController.php

ログイン画面の表示と、ログイン情報の確認結果を表示する処理を実装します。
ここでは、テーブルからデータを取得するのに、LaravelのEloquentというORマッパーを使用します。

O/Rマッピングとは、オブジェクト指向プログラミング言語におけるオブジェクトとリレーショナルデータベース(RDB)の間でデータ形式の相互変換を行うこと。そのための機能やソフトウェアを「O/Rマッパー」(O/R mapper)という。

https://e-words.jp/w/O-R%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0.html

フォームで入力されたIDとパスワードが、アカウントテーブルに一致するデータが存在するかどうかチェックしています。(exists)

このコードは安全です。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Models\Account;

class LoginController extends Controller
{

    /**
     * ログインフォーム
     */
    public function index()
    {
        return view('login');
    }

    /**
     * ログイン結果を表示
     *
     * @param  Request  $request
     */
    public function result(Request $request)
    {
        $login_id = $request->input('login_id');
        $password = $request->input('password');

        // ログイン情報の確認
        $exists = Account::where('login_id', $login_id)
            ->where('password', $password)
            ->exists();

        if ($exists) {
            return "Login OK!";
        } else {
            return "Login NG!";
        }
    }
}

それでは画面を開いてみましょう。

http://localhost:8000/login

ログインフォームが表示れました。

IDに「login-user」、パスワードに「password」と入力しログインボタンをクリックすると、Login OK!」と表示され、ログイン成功です。
それ以外のログイン情報の場合は、「Login NG!」と表示され、ログイン失敗となります。

補足

データベースの設定次第では、大文字・小文字の区別はされない場合がありますが、今回は本質ではありませんので触れません。

引き続き次のページでは、SQLインジェクションを含んだコードを実装します。

コメント

タイトルとURLをコピーしました