概要
セキュリティとは“後から足すもの”ではない。
それは設計段階から織り込むべき構造的防御力であり、書かれていないコードにも責任が発生する領域である。
本稿では、JavaScriptにおける代表的な脆弱性とその防止策を、開発・設計・運用それぞれの観点から整理する。
1. XSS(クロスサイトスクリプティング)
✅ 症状:ユーザー入力をHTMLやDOMに直接埋め込む
element.innerHTML = userInput; // ❌ 危険
→ 攻撃者による <script>alert('XSS')</script>
などの注入が可能に
✅ 対策:
-
textContent
を使う(エスケープ済み) - innerHTMLを使う場合、信頼されたテンプレートのみ
- サーバー側で Content Security Policy(CSP) を設定
- フレームワークのテンプレートは自動エスケープされるか確認
element.textContent = userInput; // ✅ 安全
2. CSRF(クロスサイトリクエストフォージェリ)
✅ 症状:認証済みユーザーの代わりに意図しないリクエストが送られる
✅ 対策:
- サーバー側で SameSite Cookie を有効に設定
- CSRFトークン(ランダム値)をHTMLに埋め込み、POST時に送信
- 認証付きAPIは トークンベース(JWTなど)で制御
<input type="hidden" name="csrf_token" value="ランダム値">
→ ✅ クライアント側でもトークン付加を忘れず送信
3. 入力検証とサニタイズ
✅ 症状:信頼しない入力値をそのまま利用(DOM・SQL・APIなど)
const query = new URLSearchParams(location.search);
const userId = query.get('user_id'); // ❌ 検証なし
✅ 対策:
- 数値チェック →
Number.isInteger()
- メール →
RegExp
+ メール形式検証 - 文字列長・禁止記号チェック
-
DOMPurify
などのライブラリでHTMLサニタイズ
if (!/^[a-z0-9]{1,20}$/.test(userId)) {
throw new Error('不正なID');
}
→ ✅ 入力は信頼できない前提で扱う
4. 安全な通信とHTTPS強制
- 常時HTTPS(ローカルでも
localhost
専用証明書を使う設計) -
fetch()
使用時はプロトコル確認(HTTPS外はブロック) -
Secure
,HttpOnly
,SameSite
を付けたCookieで通信制御
5. セキュアなストレージ設計
-
localStorage
→ 永続、XSSに弱い(避ける or 最小限) -
sessionStorage
→ タブ間非共有 -
IndexedDB
→ 大容量データの保持 - 機密情報(トークンなど)はJS側に残さない設計が望ましい
6. セキュリティ設計の構造原則
原則 | 意味 |
---|---|
最小権限の原則(POLP) | 必要最小限の操作だけを許す |
明示的な許可 | デフォルトは「禁止」、許可されたものだけ通す |
失敗時の安全設計 | 想定外の入力・エラー時は、安全側に倒す |
表層と構造の分離 | 表示のHTML/CSSとロジックを分け、攻撃面を削減 |
ログと監査の明示 | エラーや操作の記録は常に検出可能な形で残す |
設計判断フロー
① ユーザー入力をDOMに表示? → textContent or エスケープ必須
② 通信にCookieを使っている? → SameSite/CSRF対策を確認
③ 入力値を使ってAPIやSQL呼び出し? → 検証と正規化を導入
④ 通信はHTTPSか? → fetch/axiosなどでプロトコル強制
⑤ 状態やトークンを保存? → ストレージの選定とアクセス制限を設計
よくあるミスと対策
❌ innerHTMLで出力するが、入力にエスケープがない
→ ✅ 表示内容とロジックを分離し、textContentやテンプレートで制御
❌ Cookieベースの通信でSameSiteを未指定
→ ✅ SameSite=Lax
or Strict
を明示
❌ サーバー側でCSRF対策があるが、フロントからトークン未送信
→ ✅ fetch
やaxios
でCSRFトークンをヘッダーに付加
結語
セキュアなコードとは、**“攻撃されても壊れない構造を設計すること”である。
それは単なるルールではなく、「信頼できないものを信頼しない設計習慣」**の集合である。
- UIの裏にあるリスクを知り
- 通信の先にある責任を持ち
- 書かないコードにも防御を施す
攻撃は常に外からやってくる。
防御は、常にコードの中で始まっていなければならない。