SQLインジェクションとは?Webアプリケーションの致命的な脆弱性を理解して対策する

データベース・SQL

「SQLインジェクション」という言葉を聞いたことがありますか?

これは、Webアプリケーションの最も危険な脆弱性の一つで、個人情報の流出や不正なデータ改ざんなど、深刻な被害をもたらす可能性があります。

実際、大企業のWebサイトでも、SQLインジェクションによる情報漏洩事件が後を絶ちません。数百万件の顧客情報が流出したケースもあるんです。

でも安心してください。SQLインジェクションは、正しい知識と適切な対策を行えば、確実に防ぐことができます。

この記事では、SQLインジェクションとは何か、どのような被害があるのか、そしてどう対策すれば良いのかを、初心者の方にも分かりやすく解説していきます。

スポンサーリンク

SQLインジェクションとは?基本を理解しよう

まずは、SQLインジェクションがどういうものなのか見ていきましょう。

SQLインジェクションの定義

SQLインジェクション(SQL Injection)とは、Webアプリケーションの入力フォームなどに、悪意のあるSQL文を挿入(注入)することで、データベースを不正に操作する攻撃手法です。

インジェクション(Injection)は「注入」という意味ですね。

どのような仕組みか

通常のWebアプリケーションでは、ユーザーが入力したデータを使ってSQL文を作成し、データベースにアクセスします。

しかし、入力データをそのまま信頼してSQL文に組み込むと、攻撃者が意図しないSQL文を実行させることができてしまうんです。

簡単な例で理解する

正常な動作

ユーザーがログインフォームに以下を入力したとします。

ユーザー名:tanaka
パスワード:password123

アプリケーションは、こんなSQL文を実行します。

SELECT * FROM users 
WHERE username = 'tanaka' AND password = 'password123';

データベースに該当するユーザーがいればログイン成功です。

SQLインジェクション攻撃

攻撃者が、ユーザー名欄に以下を入力したとします。

ユーザー名:admin' --
パスワード:(空欄)

すると、実行されるSQL文はこうなります。

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

--はSQLのコメント記号なので、それ以降の部分は無視されます。

つまり、パスワードチェックが無効化され、adminアカウントでログインできてしまうんです。

SQLインジェクションの被害

SQLインジェクションが成功すると、どんな被害が起きるのでしょうか。

情報の漏洩

データベース内のすべてのデータを盗み出されます。

被害例

  • 顧客の個人情報(氏名、住所、電話番号)
  • クレジットカード情報
  • メールアドレスとパスワード
  • 企業の機密情報

データの改ざん・削除

データベースの内容を書き換えたり、削除したりされます。

被害例

  • 商品の価格を0円に変更
  • 注文情報の改ざん
  • ユーザーアカウントの削除
  • すべてのデータを削除するDROP TABLE文の実行

不正なログイン

管理者アカウントに不正にログインされます。

被害例

  • 管理画面への侵入
  • 権限の不正取得
  • 他のユーザーになりすまし

サーバーの乗っ取り

データベースサーバーを経由して、Webサーバー全体が乗っ取られる可能性もあります。

被害例

  • システムファイルの読み取り
  • 新しいファイルの作成
  • 他のシステムへの攻撃の踏み台に利用

脆弱性のあるコード例

実際にどんなコードが危険なのか、具体例で見ていきましょう。

危険なコード例(PHP)

脆弱なログイン処理

<?php
// ユーザー入力をそのままSQL文に埋め込んでいる(危険!)
$username = $_POST['username'];
$password = $_POST['password'];

$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);

if (mysqli_num_rows($result) > 0) {
    echo "ログイン成功";
} else {
    echo "ログイン失敗";
}
?>

このコードは、ユーザー入力を何も検証せずにSQL文に埋め込んでいます。

何が問題か

攻撃者がusernameadmin' --と入力すると、以下のSQL文が実行されます。

SELECT * FROM users WHERE username = 'admin' --' AND password = ''

パスワードチェックが無効化され、不正にログインできてしまいます。

危険なコード例(Java)

脆弱な検索機能

// ユーザー入力をそのままSQL文に連結(危険!)
String keyword = request.getParameter("keyword");
String sql = "SELECT * FROM products WHERE name LIKE '%" + keyword + "%'";

Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);

攻撃例

keywordに以下を入力されると、すべての商品情報が漏洩します。

keyword: %' OR '1'='1

実行されるSQL文:

SELECT * FROM products WHERE name LIKE '%%' OR '1'='1%'

'1'='1'は常に真なので、すべてのレコードが返されます。

危険なコード例(Python)

脆弱なデータ取得

# 文字列フォーマットを使ってSQL文を作成(危険!)
user_id = request.args.get('id')
sql = f"SELECT * FROM users WHERE id = {user_id}"

cursor.execute(sql)
result = cursor.fetchall()

攻撃例

idに以下を入力されると、すべてのユーザー情報が取得されます。

id: 1 OR 1=1

正しい対策方法

それでは、SQLインジェクションを防ぐための対策を見ていきましょう。

対策1:プリペアドステートメント(最重要)

プリペアドステートメント(Prepared Statement)は、SQL文とデータを分離する仕組みです。

これが最も確実で推奨される対策方法です。

安全なコード例(PHP – PDO)

<?php
// プリペアドステートメントを使用(安全)
$username = $_POST['username'];
$password = $_POST['password'];

// SQL文にプレースホルダー(?)を使用
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");

// 値をバインド(安全に代入)
$stmt->execute([$username, $password]);

$user = $stmt->fetch();
if ($user) {
    echo "ログイン成功";
} else {
    echo "ログイン失敗";
}
?>

安全なコード例(Java)

// プリペアドステートメントを使用(安全)
String keyword = request.getParameter("keyword");

String sql = "SELECT * FROM products WHERE name LIKE ?";
PreparedStatement pstmt = conn.prepareStatement(sql);

// プレースホルダーに値を設定
pstmt.setString(1, "%" + keyword + "%");

ResultSet rs = pstmt.executeQuery();

安全なコード例(Python)

# パラメータ化クエリを使用(安全)
user_id = request.args.get('id')

sql = "SELECT * FROM users WHERE id = %s"
cursor.execute(sql, (user_id,))

result = cursor.fetchall()

なぜ安全なのか

プリペアドステートメントでは:

  1. SQL文の構造を先に確定
  2. データは後から安全に埋め込まれる
  3. データはあくまで「データ」として扱われ、SQL文として解釈されない

攻撃者がどんな文字列を入力しても、SQL文の構造は変わりません。

対策2:入力値の検証(バリデーション)

ユーザー入力を信頼せず、必ず検証しましょう。

数値の検証

// IDは数値のはず
$id = $_GET['id'];

if (!is_numeric($id)) {
    die("不正な入力です");
}

// 整数に変換
$id = intval($id);

// プリペアドステートメントと組み合わせる
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);

文字列の長さ制限

$username = $_POST['username'];

// 長さをチェック
if (strlen($username) > 50) {
    die("ユーザー名が長すぎます");
}

// 使用可能文字をチェック(英数字とアンダースコアのみ)
if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
    die("使用できない文字が含まれています");
}

ホワイトリスト方式

許可する値をリストで定義します。

$sort = $_GET['sort'];

// 許可する値のリスト
$allowed_sorts = ['name', 'price', 'date'];

if (!in_array($sort, $allowed_sorts)) {
    $sort = 'name'; // デフォルト値
}

// 安全に使用
$stmt = $pdo->prepare("SELECT * FROM products ORDER BY $sort");

対策3:エスケープ処理

プリペアドステートメントが使えない場合の最終手段です。

PHPの例

// エスケープ処理(プリペアドステートメントの方が推奨)
$username = mysqli_real_escape_string($conn, $_POST['username']);
$password = mysqli_real_escape_string($conn, $_POST['password']);

$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

注意:エスケープ処理だけでは完全ではありません。プリペアドステートメントが最優先です。

対策4:最小権限の原則

データベースユーザーには、必要最小限の権限だけを与えましょう。

権限の分離

-- 読み取り専用のユーザー
GRANT SELECT ON database.* TO 'readonly_user'@'localhost';

-- 読み書き可能だが、削除はできないユーザー
GRANT SELECT, INSERT, UPDATE ON database.* TO 'app_user'@'localhost';

-- DROP, ALTER などの危険な操作は禁止

これにより、万が一SQLインジェクションが成功しても、被害を最小限に抑えられます。

対策5:エラーメッセージの制御

詳細なエラーメッセージを表示すると、攻撃者にデータベース構造のヒントを与えてしまいます。

悪い例

Error: Table 'mydb.users' doesn't exist

このエラーから、データベース名がmydbであることが分かってしまいます。

良い例

エラーが発生しました。管理者にお問い合わせください。

詳細なエラーは、ログファイルにのみ記録します。

フレームワークやORMの活用

現代的な開発では、フレームワークやORMを使うのが一般的です。

ORM(Object-Relational Mapping)とは

ORMは、データベース操作をオブジェクト指向的に扱える仕組みです。

適切に使えば、SQLインジェクションのリスクを大幅に減らせます。

Laravelの例(PHP)

// Eloquent ORM を使用(安全)
$user = User::where('username', $username)
            ->where('password', $password)
            ->first();

if ($user) {
    echo "ログイン成功";
}

Eloquentは内部的にプリペアドステートメントを使用しているため、安全です。

Django ORMの例(Python)

# Django ORM を使用(安全)
user = User.objects.filter(username=username, password=password).first()

if user:
    print("ログイン成功")

Spring JDBCの例(Java)

// JdbcTemplate を使用(安全)
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
User user = jdbcTemplate.queryForObject(sql, 
    new Object[]{username, password}, 
    new UserRowMapper());

注意点

ORMを使っていても、生のSQL文を直接実行できる機能があります。

# 危険:raw SQL
User.objects.raw(f"SELECT * FROM users WHERE id = {user_id}")

# 安全:パラメータ化
User.objects.raw("SELECT * FROM users WHERE id = %s", [user_id])

生のSQLを使う場合は、必ずパラメータ化しましょう。

脆弱性の検出方法

自分のアプリケーションに脆弱性がないか確認する方法です。

手動テスト

基本的なテスト入力

以下のような入力を試してみます。

' OR '1'='1
' OR '1'='1' --
' OR '1'='1' /*
admin' --
' UNION SELECT NULL --

注意:自分が管理するシステムにのみテストしてください。他人のシステムへの攻撃は犯罪です。

自動脆弱性スキャナー

ツールの例

  • SQLMap:SQLインジェクション専門の検出ツール
  • OWASP ZAP:Webアプリケーションの総合セキュリティスキャナー
  • Burp Suite:プロフェッショナル向けのセキュリティツール

これらのツールは、自動的に多数のパターンでテストを行います。

コードレビュー

チェックポイント

  • ユーザー入力を直接SQL文に埋め込んでいないか
  • プリペアドステートメントを使用しているか
  • 入力検証を行っているか
  • エラーメッセージが詳細すぎないか

セキュリティ診断サービス

専門業者に依頼して、プロの目で診断してもらう方法もあります。

重要なシステムでは、定期的な診断が推奨されます。

実際の事件から学ぶ

SQLインジェクションによる実際の被害事例です。

大規模な個人情報流出

過去、大手企業のWebサイトで、SQLインジェクションにより数百万件の個人情報が流出した事件がありました。

被害内容

  • 氏名、住所、電話番号、メールアドレス
  • 購入履歴
  • 一部のクレジットカード情報

原因
Webアプリケーションの入力フォームに適切な対策が施されていなかった。

ECサイトでの不正注文

ECサイトで、SQLインジェクションにより商品価格が改ざんされた事件もあります。

被害内容

  • 商品価格が0円に変更される
  • 在庫数の改ざん
  • 不正な注文の発生

原因
管理画面の脆弱性を突かれた。

教訓

これらの事件に共通するのは、基本的なセキュリティ対策が不十分だったことです。

プリペアドステートメントや入力検証といった基本を守れば、防げた被害だったのです。

開発者が守るべきベストプラクティス

SQLインジェクションを防ぐための、開発時の心得です。

1. ユーザー入力を絶対に信頼しない

すべての入力は悪意があるものとして扱いましょう。

  • フォーム入力
  • URLパラメータ
  • Cookie
  • HTTPヘッダー

2. プリペアドステートメントを必ず使う

生のSQL文字列を組み立てる必要がある場面は、ほとんどありません。

どうしても必要な場合は、十分に検証してください。

3. セキュリティを後回しにしない

「後で対策すればいい」は危険です。

開発の初期段階から、セキュリティを考慮しましょう。

4. 定期的な学習と情報収集

セキュリティの脅威は常に進化しています。

定期的に最新情報をチェックしましょう。

参考リソース

  • OWASP(Open Web Application Security Project)
  • IPA(情報処理推進機構)のセキュリティ情報
  • セキュリティ関連のカンファレンスや勉強会

5. コードレビューの徹底

複数人でコードを確認し合いましょう。

第三者の目で見ることで、見落としを防げます。

6. ライブラリとフレームワークを最新に保つ

古いバージョンには既知の脆弱性が含まれていることがあります。

定期的にアップデートしましょう。

チェックリスト

自分のアプリケーションをチェックしてみましょう。

基本対策

  • [ ] すべてのSQL実行にプリペアドステートメントを使用している
  • [ ] ユーザー入力の検証を行っている
  • [ ] エラーメッセージが詳細すぎない
  • [ ] データベースユーザーの権限を最小限にしている

コード品質

  • [ ] ORMやフレームワークを適切に使用している
  • [ ] 生のSQL文を直接組み立てていない
  • [ ] コードレビューを実施している

テストと検証

  • [ ] セキュリティテストを実施している
  • [ ] 脆弱性スキャナーでチェックしている
  • [ ] 定期的なセキュリティ診断を受けている

継続的改善

  • [ ] セキュリティ情報を定期的にチェックしている
  • [ ] ライブラリを最新版に保っている
  • [ ] セキュリティインシデント対応計画がある

まとめ

SQLインジェクションについて解説してきました。

重要なポイント

  • SQLインジェクションは、悪意のあるSQL文を注入する攻撃手法
  • 個人情報漏洩、データ改ざん、不正ログインなど深刻な被害をもたらす
  • プリペアドステートメントの使用が最も確実な対策
  • ユーザー入力は絶対に信頼せず、必ず検証する
  • エラーメッセージは最小限にする
  • データベースユーザーには最小権限を与える
  • ORMやフレームワークを適切に使えば、リスクを大幅に減らせる
  • 定期的なテストと学習が重要

SQLインジェクションは古くから知られた攻撃手法ですが、今でも多くのWebアプリケーションで被害が発生しています。

しかし、正しい知識と適切な対策を行えば、確実に防ぐことができます。

この記事で学んだ対策を実践して、安全なWebアプリケーションを開発していきましょう!

コメント

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