もう怖くない!SQLインジェクションの仕組みと防ぎ方を徹底解説

スポンサーリンク
この記事は約10分で読めます。

「SQLインジェクション」──ちょっと聞き慣れない言葉かもしれませんが、実は私たちの身近に潜んでいる、とても危険なセキュリティリスクです。

Webサイトで会員登録をしたり、ログインしたり、検索したり。
こうした何気ない操作の裏側では、データベースというものが使われており、そこに送られる命令文がSQLです。

でももし、そのSQL文に“悪意ある命令”を紛れ込ませることができたら…?
想像してみてください。
パスワードがバレたり、個人情報が漏れたり、ひどいときにはサイト全体が乗っ取られてしまうなんてこともあるのです。

このような攻撃を「SQLインジェクション(SQL Injection)」といいます。
専門用語に見えますが、原理はとてもシンプル。そして対策も「ちゃんと知っていれば」難しいものではありません。

この記事では、

  • SQLインジェクションとは何か
  • どうやって攻撃されるのか
  • どんな被害があるのか
  • どうすれば防げるのか

といったことを、初心者の方にもわかりやすく、そして正確に解説していきます。
「今さら聞けない…」と思っている方でも大丈夫です。安心して読み進めてくださいね。

SQLインジェクションとは?

Web開発者向け HTML/CSS/JavaScriptに強いエディタ3選

「SQLインジェクションって、実際どんなもの?」
一言でいうと、Webアプリケーションにおける“隙”を突いて、データベースに不正な命令を注入する攻撃です。

攻撃者の狙い

たとえば、こんなSQL文があったとします。

SELECT * FROM users WHERE username = 'taro' AND password = '1234';

これは、「ユーザー名がtaro、かつパスワードが1234の人を探してね」という意味ですね。

ところが、このSQL文をユーザーの入力によって直接作っているとしたらどうでしょう?

もし攻撃者が以下のような入力をしたら…

ユーザー名:' OR '1'='1
パスワード:(何でもOK)

生成されるSQL文はこんなふうになります。

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '';

'1'='1' は常に真になる条件なので、本来ならログインできないはずの人が、ログインできてしまう
これが、SQLインジェクションの基本的な仕組みです。

「インジェクション」って何?

「インジェクション」は英語で「注入」という意味です。
本来あるべきSQL文の“隙間”に、意図しない命令を“注入”することから、こう呼ばれているんですね。

SQLインジェクションは、一見すると「そんな古典的な攻撃、もう誰も使ってないでしょ?」と思われがちですが、2020年代になっても現役で使われている攻撃手法です。
実際、IPA(情報処理推進機構)の「10大脅威」にも毎年ランクインするほどの危険性を持っています。

実際の攻撃例を紹介

「理屈はわかったけど、実際どんなふうにやられるの?」
そんな方のために、ごくシンプルなログイン機能を例に、SQLインジェクションがどう発生するかを見ていきましょう。

例:ログイン処理の脆弱なコード(PHP)

以下は、PHPで書かれた非常に基本的なログイン処理です。

<?php
// ユーザーからの入力を取得
$username = $_POST['username'];
$password = $_POST['password'];

// SQLクエリを生成(※危険な書き方)
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

// 実行
$result = mysqli_query($conn, $sql);
?>

見たところ、単純なクエリですね。ですが、このコードは非常に危険です。

攻撃者が入力する内容

攻撃者が、以下のように入力したとします。

  • ユーザー名:' OR '1'='1
  • パスワード:何でもOK(例:aaa

すると、SQL文はこうなります。

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'aaa';

実行されるとどうなる?

  • '1'='1' は常に真(TRUE)なので、WHERE条件が常に成立してしまいます。
  • 結果として、本来なら認証されないはずの人でもログインできてしまうのです。
  • しかも、条件次第では最初の1人目のユーザー(つまり管理者アカウント)にログインされる可能性すら…。

さらに深刻な攻撃

攻撃者が次のような入力をしたら?

  • ユーザー名:admin'; DROP TABLE users;--
  • パスワード:何でも

生成されるSQLは、

SELECT * FROM users WHERE username = 'admin'; DROP TABLE users;--' AND password = 'xxx';

この場合、2つ目の命令 DROP TABLE users; によって、ユーザー情報のテーブルが完全に削除されてしまうという最悪の事態も起こりえます。

※多くのDBでは1回のクエリで複数文の実行は禁止されている場合もありますが、設定次第では実行可能です。

攻撃が成立する条件とは?

以下のような条件がそろっている場合、攻撃される恐れがあります。

  • ユーザー入力をSQL文に直接埋め込んでいる
  • プレースホルダ(パラメータ化)を使っていない
  • データベースのエラーメッセージがそのまま表示される(ヒントを与える)
  • 管理者アカウントに特別な制限がかかっていない

なぜSQLインジェクションが発生するのか?

SQLインジェクションの怖さは理解できたと思いますが、そもそもなぜこんな攻撃が可能になるのでしょうか?
それには、いくつかの「よくある落とし穴」があります。

ユーザー入力をそのままSQLに組み込んでいる

これが最大の原因です。

以下のように、ユーザーの入力値をダイレクトにSQL文に差し込んでしまうと、

$sql = "SELECT * FROM users WHERE name = '$input'";

攻撃者は input任意のSQL文を紛れ込ませることができてしまうのです。

「バリデーションしてるから大丈夫」と思い込む

よくあるのが、「入力チェックしてるから平気」という過信です。

たとえば、以下のような「英数字しか許さない」バリデーションをしていたとしても、

if (!preg_match('/^[a-zA-Z0-9]+$/', $input)) {
    die("Invalid input");
}

このようなチェックは補助的な対策でしかなく、本質的な防御にはなりません。
万一このチェックをすり抜けた場合、直結しているSQLは依然として無防備です。

動的SQLを使いすぎている

開発の都合上、どうしても動的にSQL文を組み立てたいケースはあります。
たとえば、検索条件が複数あるようなときですね。

SELECT * FROM items WHERE 1=1
AND (category = '$cat')
AND (price <= $price);

このような構造は柔軟ではある一方で、不正な値が入り込む余地が増えるため、
パラメータ化されていなければ、SQLインジェクションのリスクも跳ね上がります。

エラーメッセージの表示が丁寧すぎる

たとえば、SQLの構文ミスで次のようなエラーが画面に表示されたら?

You have an error in your SQL syntax near '...'

攻撃者は「このサイトはMySQLだな」「この列名はusersだな」といった重要なヒントを得てしまいます。

開発中は便利でも、本番環境では詳細なエラーメッセージは非表示にすることが推奨されます。

対策方法とベストプラクティス

「じゃあ、どうすればSQLインジェクションを防げるの?」
──安心してください。攻撃の原理がシンプルだからこそ、効果的な対策も明確です。

開発者が最低限押さえておくべき防止策とベストプラクティスをご紹介します。

プリペアドステートメントの使用

これが最も確実で基本的な対策です。
プリペアドステートメント(またはパラメータ化クエリ)を使うことで、SQLとデータを明確に分離できます。

PHPの例

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute([
  ':username' => $username,
  ':password' => $password
]);

このように書けば、たとえユーザーが ' OR '1'='1 のような値を入力しても、
それは単なる「文字列」として扱われ、SQL文としては意味を持たなくなります。

他の言語(Java、C#、Pythonなど)でも、プリペアドステートメントは標準機能として提供されています。

ORM(Object-Relational Mapping)を活用する

LaravelのEloquent、DjangoのORM、C#のEntity Frameworkなど、
最近のWebフレームワークには、SQLを直接書かなくてもデータ操作できる仕組み(ORM)が用意されています。

これらのORMは基本的に内部でプリペアドステートメントを使っており、SQLインジェクションへの耐性が高いです。

ただし、生SQLを書く機能もあるため油断は禁物
生のDB::raw()ExecuteSql()を使うときは特に注意しましょう。

入力値のバリデーションとサニタイズ

「バリデーション(形式チェック)」や「サニタイズ(無害化)」は、補助的な防御として有効です。

  • 数字だけのID → is_numeric() でチェック
  • 文字数や正規表現による制限
  • 特殊文字の無害化(HTML表示時など)

ただし、これだけではSQLインジェクションは防げません。
本質的な対策はプリペアドステートメントです。

DBユーザーの権限を最小限にする

アプリケーションが使うデータベースユーザーには、必要最低限の権限しか与えないのが鉄則です。

たとえば、ログイン機能なら SELECT 権限だけでも充分かもしれません。
間違っても DROPGRANT などの強力な権限を与えないようにしましょう。

仮にSQLインジェクションを受けたとしても、被害を最小限に抑えるための“セーフティネット”になります。

エラーメッセージの制御

本番環境では、データベースやSQLに関する詳細なエラーメッセージは絶対に表示しないこと。

代わりに「予期せぬエラーが発生しました」といった汎用的なメッセージに切り替えましょう。

開発時には便利でも、攻撃者にとっては“宝の地図”になってしまいます。

まとめ

SQLインジェクションは、非常に古くから知られている攻撃手法でありながら、今なお多くのWebサービスがその脅威にさらされています。
その本質は、「ユーザー入力を信用しすぎてしまったこと」による脆弱なSQL文の実行です。

この記事では、以下のポイントを中心に解説してきました。

  • SQLインジェクションとは何か?
    → SQL文に悪意あるコードを混ぜて、データベースを不正操作する攻撃。
  • どうやって攻撃されるのか?
    → ユーザー入力を直接SQLに埋め込んだ場合、簡単に回避・改ざんされてしまう。
  • どんな被害があるのか?
    → 情報漏洩、管理者権限の乗っ取り、最悪の場合はデータベース破壊も。
  • どう防げばよいか?
    → プリペアドステートメントを使い、SQLとデータを明確に分離することが最重要。

「自分のコードは大丈夫」と思っていても、一度立ち止まって確認してみることが大切です。

Web開発においてセキュリティは、「できたらいい」ではなく「やって当たり前」の時代です。
小さな油断が、ユーザーの信頼や会社の信用を一瞬で崩すこともあります。

この記事が、あなたのコードをより安全に、そして自信を持てるものにする手助けとなれば幸いです。

コメント

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