ぽかぽかコード日和

とっても暑い夏の日にプログラミングはじめました☀️

【Security】SQL インジェクション+Laravel実装

概要

Webアプリケーションのユーザーの入力領域に
攻撃者が不正なSQL文を注入し、データベースに意図しない命令を実行させる攻撃手法。

  • ログインフォームや検索機能などを持つWebサイトは特に注意する。
発生しうる脅威
  • 重要情報の漏洩
    データベースに格納されている重要な情報(パスワードやカード情報など)が盗まれる。
    企業の信頼を大きく損なうことにもつながる。

  • サイトの改ざん、削除
    偽の情報を表示したり、情報を消去することができる。

  • データベースの全消去・破壊
    DELETE、DROP文などを使い、データベースごと削除される

  • システムの乗っ取りや、攻撃の踏み台にされる
    管理者権限を奪われ、サーバーを他の攻撃に使われる。

--脆弱性のあるコード
SELECT * FROM users WHERE id='$id' AND pass='$pass';
↓
--正常な入力 
SELECT * FROM users WHERE id='John' AND pass='123pass';

--攻撃者の入力 
 SELECT * FROM users WHERE id='' or 1=1; -- 'AND pass='123pass';

☀︎攻撃者が$idに「' or 1=1; --」と入力されると、

  • $idは空文字または1は1に等しいという条件になり常に真となる。
  • --以降はコメント扱いになるためパスワードの条件は無効になる。

→そのため本来は通らないユーザー認証が成功してしまう。

原因

ユーザーからの入力値をそのままSQL文に埋め込むこと

対策

根本的解決

1.プレースホルダーとプリペアドステートメントで実装する
  • プレースホルダーとは、

    • SQL文の変数の部分を「?」や「:name」などの記号で置き換えたもの。
    • これによりSQL構文と値が別々にデータベースに送信され、
      悪意のある値であってもSQLとして解釈されず安全に実行される。
  • 静的プレースホルダ

    • プリペアドステートメントは、
      プレースホルダを含むSQL文を先に準備し、後からデータベース側でバインドして実行する仕組み。
    • 同じSQL構文で何度も実行できるため、効率的に使える。
PDOでのプレースホルダー使用例
//プレースホルダー付きSQL文を準備
$stmt = $PDO->prepare("SELECT * FROM users WHERE id = :id  AND  pass = :pass"); 

//値をバインド
$stmt->bindValue(':id', $id); 
$stmt->bindValue(':pass', $pass);

//実行
$stmt->execute();
2.エスケープ処理

データベースに値を渡す前に、特殊文字を安全な形に変換する。
「’」→「’’」、「¥」→「¥¥」など
PHPでは $mysqli->real_escape_string($input) を使うことができる。

3.hiddenパラメータで直接指定しない。(禁忌)

ユーザーからの入力を直接SQLに組み込むのは避ける

保険的対策

4.エラーメッセージ表示しない

攻撃者に内部構造(テーブル名など)の情報を与えないようにする。
エラーメッセージは表示せず、ログファイルに記録する。

5.データベースの権限を最小限に設定する

DBユーザーには必要最小限の操作だけ許可する。
SELECT操作で十分であれは、INSERTやDELETE文の権限までは付与しない。

Laravelでの実装例

1.Eloquent ORMを使う

  • Laravelには、「Eloquent ORM」というPDOのプリペアドステートメントを利用した機能が搭載されている。

  • モデルクラスとしてデータベースのテーブルと連携し、SQL文を直接書かずにデータベースを操作できる。

  • さらに詳細なクエリや条件を指定したいときは、「クエリビルダー」を使う。

  • SQLを使うときは、プリペアドステートメントを利用したり、入力値のバリデーションやエスケープ処理を徹底する。

実装の流れ

あらかじめモデルでデータベーステーブルと連携しておく
Post.phpファイルに

//Postクラスの作成
class Post extends Model
{
 //テーブル名を指定
    protected $table = 'posts';
}

コントローラーでPostクラスを使う
PostController.phpファイルに

class PostController extends Controller
{
    public function index() {
        //postsテーブルのデータを全件取得
        $posts=Post::all();
        return view('posts.index', compact('posts'));
        }
}

ビューで$postsを使い表示させる
posts/index.blade.phpファイルに

@foreach($posts as $post)
  <div>
    <h2>{{ $post->title }}</h2>
    <p>{{ $post->body }}</p>
  </div>
@endforeach

参考にしたサイト