この記事を書いたきっかけ
こんな経験、ありませんか?
面接官: 「ソフトウェア開発でセキュリティ面、何か意識されていることは?」
私(ドヤ顔): 「OWASP TOP10に書かれている内容は本番リリース前に必ず潰すようにしています」
面接官: 「いいですね。では具体的に、過去対応されたOWASP TOP10の例を教えてください」
私: 「えーっと……クロスサイトスクリプティング……を……」
面接官: 「……他には?」
私:「……。」
こんな状況を二度と味わわないために、OWASP Top10を脳にぶちこんでおきましょう。
前提: 本記事は OWASP Top 10 2025(公開ドラフト段階) の構成に合わせて書いています。正式版で名称・順位が変わる可能性があるため、最新は owasp.org/Top10 を参照してください。2021 版との主な差分は末尾にまとめています。
ここから順番に見ていきましょう!
A01: Broken Access Control(アクセス制御の不備)— 1位は不動
問題: 認証済みユーザーが他人のリソースに触れる(IDOR=Insecure Direct Object References)、権限昇格。
// ❌ IDOR: {id} に他人のIDを入れれば見える
@GetMapping("/api/accounts/{id}")
public Account get(@PathVariable Long id) {
return repo.findById(id).orElseThrow();
}
// ✅ オーナーシップを認可で縛る
@PreAuthorize("@accountSec.isOwner(#id, authentication)")
@GetMapping("/api/accounts/{id}")
public Account get(@PathVariable Long id) { ... }
覚え方: 「認証 ≠ 認可」。ログイン確認だけでは足りない。Spring Security の @PreAuthorize / method security を常用。Deny by default(デフォルト拒否)。
A02: Security Misconfiguration(設定ミス)— 🆙 急上昇
問題: デフォルトパスワード、不要エンドポイント露出、詳細エラーページ、CORSゆるゆる。
# ❌ 本番でこれは致命的
management.endpoints.web.exposure.include=*
server.error.include-stacktrace=always
spring.h2.console.enabled=true
# ✅ 本番設定
management.endpoints.web.exposure.include=health,info
server.error.include-stacktrace=never
server.error.include-message=never
// ✅ CORSは明示的に絞る
@Bean
CorsConfigurationSource cors() {
var c = new CorsConfiguration();
c.setAllowedOrigins(List.of("https://app.example.com"));
c.setAllowedMethods(List.of("GET","POST"));
...
}
覚え方: 「デフォルトは敵」。Actuator、H2コンソール、Swagger UI、* CORS — "便利な初期値"はだいたい本番では脆弱性。環境別プロファイル (application-prod.properties) を必ず分ける。
A03: Software Supply Chain Failures(サプライチェーンの失敗)— 🆕
問題: Log4Shell (CVE-2021-44228) のような依存ライブラリ由来。依存の依存(推移的)まで含む。ビルドパイプラインの改ざんも含む。
<!-- ❌ 古いまま放置 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.0</version>
</dependency>
対策:
-
mvn versions:display-dependency-updates/gradle dependencyUpdates - OWASP Dependency-Check / Snyk / Dependabot をCIに組み込む
- Spring Boot の BOM (
spring-boot-dependencies) を使い版ズレ防止 - SBOM (CycloneDX) を生成・保管
- ビルド成果物に署名 (Sigstore 等)
覚え方: 「自分のコードより依存コードの方が多い」。Log4Shellを思い出せ。CIで自動スキャン+SBOMが基本セット。
A04: Cryptographic Failures(暗号化の失敗)
問題: 平文パスワード、MD5/SHA-1、HTTP通信、ハードコードされた鍵。
// ❌
String hash = DigestUtils.md5Hex(password);
// ✅
@Bean
PasswordEncoder encoder() { return new BCryptPasswordEncoder(12); }
// または Argon2PasswordEncoder (OWASP推奨)
server.ssl.enabled=true
# HSTS は Spring Security がデフォルトで付与
覚え方: 「保存時・通信時・使用時」の3状態を暗号化で守る。パスワードはハッシュ(BCrypt/Argon2/PBKDF2)、データは暗号化(AES-GCM)、鍵はKMS/Vault。自前で暗号アルゴリズムを書かない。
A05: Injection(インジェクション)— XSS もここに統合済
問題: SQLi、コマンドインジェクション、LDAPi、XSS(2021 からここに統合)。
// ❌ SQLi
em.createQuery("SELECT u FROM User u WHERE u.name = '" + name + "'");
// ✅ パラメータバインド
em.createQuery("SELECT u FROM User u WHERE u.name = :n")
.setParameter("n", name);
// ✅ Spring Data JPA
userRepo.findByName(name);
<!-- ❌ XSS -->
<div th:utext="${comment}"></div>
<!-- ✅ Thymeleaf のデフォルト th:text はエスケープされる -->
<div th:text="${comment}"></div>
覚え方: 「文字列連結でクエリ/コマンドを作ったら負け」。PreparedStatement / JPA パラメータ / Thymeleaf の th:text がデフォルト安全。utext・Runtime.exec(String)・ProcessBuilder で文字列連結は赤信号。
A06: Insecure Design(安全でない設計)
問題: 実装バグではなく設計そのものが脆弱。
例: パスワードリセットの秘密の質問、レート制限なしログイン、購入フローで金額をクライアント送信。
// ❌ 金額をクライアントから受け取る
@PostMapping("/checkout")
public void buy(Long productId, BigDecimal price) { ... }
// ✅ サーバ側でマスタから取得
@PostMapping("/checkout")
public void buy(Long productId) {
var price = productRepo.findById(productId)
.orElseThrow()
.getPrice();
}
- レート制限: Bucket4j + Spring Boot Starter
- MFA、リスクベース認証の設計
覚え方: 「後付けのセキュリティは無力」。脅威モデリング(STRIDE)を設計段階で。「クライアントを信じない」「信頼境界を図に書けるか」。
A07: Authentication Failures(認証の失敗)
問題: ブルートフォース許容、セッションID漏洩、弱いパスワードポリシー、ログアウトでセッション破棄しない。
// ✅ Spring Security
http
.formLogin(f -> f.loginPage("/login"))
.sessionManagement(s -> s
.sessionFixation().migrateSession() // セッション固定攻撃対策
.maximumSessions(1))
.logout(l -> l.invalidateHttpSession(true)
.deleteCookies("JSESSIONID"));
- ログイン試行制限(Bucket4j / Spring Security の
LockedException) - MFA、パスワードレス(WebAuthn/Passkey)
- JWT を Cookie で 運用するなら短寿命+リフレッシュトークン、
HttpOnly; Secure; SameSite=Strict。Authorization: Bearerヘッダで送るなら XSS で窃取されない保管場所(メモリ等)を選ぶ。
覚え方: 「ID・セッション・パスワード」の3点セットを守る。ログイン後は必ずセッションID再発行(固定化攻撃対策)。
A08: Software or Data Integrity Failures(完全性の失敗)
問題: 署名なし更新、CI/CD汚染、安全でないデシリアライゼーション。
// ❌ 信頼できない入力をJavaデシリアライズ — RCE直行
ObjectInputStream ois = new ObjectInputStream(req.getInputStream());
Object o = ois.readObject();
// ✅ JSONを使い、型を限定
objectMapper.readValue(json, UserDto.class);
// Jackson の Default Typing は絶対に有効化しない
- ライブラリは署名検証付きで取得(Maven Central はチェックサム検証)
- CI/CDの秘匿情報・権限を絞る(SLSA準拠)
覚え方: 「readObject は RCE の入口」。Java 標準シリアライズを受け入れ口に使わない。更新配信・依存取得は署名検証。
A09: Security Logging and Alerting Failures(ロギングと検知の失敗)
問題: ログイン失敗が記録されない・監視されない、ログにパスワードやトークンを書く、ログインジェクション。
// ❌ 機密をログに
log.info("Login attempt: user={}, password={}", user, password);
// ❌ ログインジェクション(改行注入でログ偽装)
log.info("User input: " + userInput);
// ✅ 改行・制御文字を除去してから出力
String safe = user == null ? "" : user.replaceAll("[\\r\\n\\t]", "_");
log.info("Login attempt for user={}", safe);
// Logback なら %replace{%msg}{[\r\n]}{_} をパターンで適用する手もある
- 監査ログ: Spring Boot Actuator +
AuditEventRepository - 集約: ELK / Loki / CloudWatch、異常検知アラート
覚え方: 「攻撃に気づけないと、侵入は"成功"する」。ログは出す・守る・見るの3つ揃えて初めて価値。機密はマスク、改行はサニタイズ。
A10: Mishandling of Exceptional Conditions(例外処理の不備)— 🆕
問題: スタックトレースがレスポンスに出る、catch (Exception e) {} で握り潰し、エラー時にトランザクションやリソースが中途半端に残る。
// ❌ 例外握り潰し + 情報漏洩
try {
payment.process();
} catch (Exception e) {
return ResponseEntity.status(500).body(e.toString()); // 内部情報漏洩
}
// ✅ 集中ハンドリング + 汎用メッセージ
@RestControllerAdvice
class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDto> handle(Exception e) {
String traceId = UUID.randomUUID().toString();
log.error("[{}] Unexpected error", traceId, e); // 詳細はログへ
return ResponseEntity.status(500)
.body(new ErrorDto("Internal error", traceId)); // クライアントには最小限
}
}
server.error.include-stacktrace=never
server.error.include-message=never
server.error.include-exception=false
覚え方: 「例外はログへ、ユーザーには一般化したメッセージ+追跡ID」。catch (Exception e) {} は禁止、@RestControllerAdvice で集中管理。エラー時も不変条件(トランザクション/ロック/ファイルハンドル)を壊さない。
🧠 まとめて覚えるための語呂・構造
番号順キーワード(日本語)
| # | 英語短縮 | 日本語一言 |
|---|---|---|
| A01 | Access | 権限はがし |
| A02 | Misconfig | 初期設定は敵 |
| A03 | Supply Chain | 依存は爆弾(Log4Shell) |
| A04 | Crypto | 暗号・ハッシュ・TLS |
| A05 | Injection | 連結するな、バインドしろ |
| A06 | Insecure Design | 設計でしくじるな |
| A07 | AuthN | ログイン・セッション・パスワード |
| A08 | Integrity | 署名とデシリアライズ |
| A09 | Logging | 気づけない攻撃は成功する |
| A10 | Exception | 例外から漏らすな |
3グループに圧縮
- 入口を守る(1, 5, 7): 誰が・何を・どう入れるか → Access / Injection / Auth
- 土台を守る(2, 3, 4, 6, 8): 設定・依存・暗号・設計・完全性
- 出口を守る(9, 10): ログとエラー、漏らさず・見逃さず
Spring Boot 定番対策セット
| 対策 | カバーする項目 |
|---|---|
| Spring Security | A01, A07 |
@PreAuthorize |
A01 |
| BCrypt / Argon2 | A04 |
| Bean Validation | A05 |
| Parameterized JPA | A05 |
| Dependency-Check / SBOM | A03 |
@RestControllerAdvice |
A09, A10 |
| Actuator + マスキング | A02, A09 |
| プロファイル分離 | A02 |
まずは番号順に"一言"を覚え → 次に Spring Boot の対策クラス/アノテーションを紐付け → 最後に攻撃名の具体例(IDOR, Log4Shell, SQLi, CSRF, 固定化攻撃, デシリアライズ RCE)を肉付けする、の3段階がおすすめです。
ちなみに 2021 → 2025 の主な変化はこちら (ドラフト段階):
- 🆙 Security Misconfiguration が A05 → A02 に急上昇
- 🆕 Software Supply Chain Failures (A03) — 旧「脆弱性のある・古いコンポーネント」を拡張
- 🆕 Mishandling of Exceptional Conditions (A10) が新登場
- ❌ SSRF (Server-Side Request Forgery) は単独枠から外れた(他項目に統合)