なぜWebセキュリティが重要なのか
2026年、サイバー攻撃の被害は年間数十兆円規模に達しています。エンジニアがセキュリティを「後から考えること」にすると、データ漏洩・サービス停止・法的責任のリスクを抱えます。本記事ではOWASP Top 10に基づき、実践的なセキュリティ対策を解説します。
OWASP Top 10(2026年版)
| 順位 | 脆弱性 | 概要 |
|---|---|---|
| A01 | アクセス制御の不備 | 認可チェック漏れによる不正アクセス |
| A02 | 暗号化の失敗 | 平文での機密データ送受信 |
| A03 | インジェクション | SQLi・XSS・コマンドインジェクション |
| A04 | セキュアでない設計 | 設計段階でのセキュリティ考慮不足 |
| A05 | セキュリティの設定ミス | デフォルト設定・不要な機能の有効化 |
| A06 | 脆弱なコンポーネント | 古いライブラリ・フレームワークの使用 |
| A07 | 認証・認可の失敗 | 弱いパスワード・不適切なセッション管理 |
| A08 | ソフトウェアとデータの完全性 | CI/CDパイプラインへの攻撃 |
| A09 | ログ・モニタリングの失敗 | 攻撃の検知・対応遅延 |
| A10 | SSRF | サーバーサイドリクエストフォージェリ |
SQLインジェクション対策
# 危険なコード(SQLインジェクション脆弱)
def get_user_bad(username: str):
query = f"SELECT * FROM users WHERE username = '{username}'"
# username = "admin' OR '1'='1" で全ユーザーが取れてしまう!
# 安全なコード(プレースホルダ使用)
import sqlite3
def get_user_safe(username: str):
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
# プレースホルダ(?)を使う
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
return cursor.fetchone()
# SQLAlchemyを使う場合(ORM)
from sqlalchemy.orm import Session
def get_user_sqlalchemy(db: Session, username: str):
return db.query(User).filter(User.username == username).first()
XSS(クロスサイトスクリプティング)対策
# バックエンド(FastAPI)でのサニタイズ
import html
from bleach import clean, linkify
def sanitize_content(content: str) -> str:
# HTMLタグをエスケープ
escaped = html.escape(content)
# または許可するタグを限定
allowed_tags = ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li']
allowed_attrs = {'a': ['href', 'title', 'rel']}
cleaned = clean(content, tags=allowed_tags, attributes=allowed_attrs)
return cleaned
# フロントエンド(React)では自動でエスケープ
const UserContent = ({ content }) => {
// ReactのJSXは自動的にXSSを防ぐ
return {content}; // 安全
// dangerouslySetInnerHTMLは使わない!
// return ; // 危険
};
認証・認可のベストプラクティス
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
import secrets
app = FastAPI()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
security = HTTPBearer()
SECRET_KEY = secrets.token_urlsafe(32) # 本番では環境変数から読む
ALGORITHM = "HS256"
# パスワードのハッシュ化
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
# JWTトークンの生成
def create_access_token(user_id: str, expires_delta: timedelta = timedelta(hours=1)) -> str:
payload = {
"sub": user_id,
"exp": datetime.utcnow() + expires_delta,
"iat": datetime.utcnow(),
"jti": secrets.token_urlsafe(16) # JWT ID(リプレイ攻撃防止)
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
# トークン検証
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
try:
payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("sub")
if not user_id:
raise HTTPException(status_code=401, detail="Invalid token")
return user_id
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
セキュリティヘッダーの設定
# FastAPIでセキュリティヘッダーを追加
from fastapi import FastAPI, Request, Response
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
app = FastAPI()
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self' 'nonce-{nonce}'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"connect-src 'self' https://api.example.com"
)
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()"
return response
脆弱性スキャンツール
- OWASP ZAP:無料のWebアプリ脆弱性スキャナー
- Burp Suite:プロ向けセキュリティテストツール
- Snyk:依存パッケージの脆弱性検出(CI/CD統合可能)
- Trivy:コンテナイメージの脆弱性スキャン
- Semgrep:静的解析によるセキュリティチェック
セキュリティチェックリスト
- □ 全通信をHTTPS化(Let’s Encrypt活用)
- □ パスワードはbcryptでハッシュ化
- □ SQLはプレースホルダ/ORMを使用
- □ ユーザー入力はサニタイズ・バリデーション
- □ 依存パッケージを定期的に更新(npm audit / pip-audit)
- □ セキュリティヘッダーを全レスポンスに設定
- □ ログに個人情報・パスワードを出力しない
- □ レート制限でブルートフォース攻撃を防ぐ
- □ CSRFトークンでCSRF攻撃を防ぐ
- □ 定期的なペネトレーションテスト実施
まとめ
セキュリティは後付けではなく、設計段階から組み込むことが重要です。OWASP Top 10を理解し、日々のコーディングでセキュアな実装を習慣づけましょう。セキュリティエンジニアの需要は2026年も高く、キャリアとしても有望な分野です。