- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
このコースは、番号を読む前に何を数えた一覧かを読む、という1本の線でつながっています。今回は Web 2025 の新項目を、例外が起きたあとアプリがどこへ落ちるかで読みます。
まず結論
A10:2025 Mishandling of Exceptional Conditions は 2025 で新設された項目で、24 の CWE に失敗時に開く設計(CWE-636)を含みます。2021 の A10 だった SSRF は A01:2025 に統合されており、同じ番号が別の項目を指しています。
手元の模型で DB を止めると、catch がログだけ書いて先へ進む版は権限の無い利用者に 200 で管理レポートを返し、拒否する版は 503 を返しました。送金の途中で止めた実験では応答はどちらも 500 で、差は DB に残った状態にだけ出ました。
手元の模型で、権限を毎回 DB に問い合わせる認可処理の catch 節が、例外をログに書くだけで処理を続けるとします。DB を止めた状態で、管理者でない利用者が管理ページを要求すると、何が返るでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
新しい項目と、再利用された番号
先に、Web Top 10 の 2025 版が何を数えた一覧かを押さえます。提供データは 280 万超のアプリ・589 の CWE で、数えたのは弱点の件数ではなく「その CWE を1つ以上持っていたアプリの割合」です。導入文は、データで並べた項目に実務者の調査票で2枠を加えたと書きます(同じ文書に言い方が2通りあり、詳しくは第2回で扱います)。A03 と A09 の説明文は調査票の投票に触れていますが、A10 の説明文にはデータと調査票のどちらで入ったかは書かれていません(確認範囲: 導入文の項目別の段落)。
その A10 の Background は、新設であることから書き始めます(A10 本文のまま。and systems は原文どおり)。
Mishandling of Exceptional Conditions is a new category for 2025. This category contains 24 CWEs and focuses on improper error handling, logical errors, failing open, and other related scenarios stemming from abnormal conditions and systems may encounter.
24 という数は、スコア表の CWEs Mapped・末尾の CWE 一覧の項目数・本文の3箇所で一致します。代表として挙がる CWE の1つが CWE-636 Not Failing Securely ('Failing Open') です。
範囲は例外処理のバグより広く、一覧にはゼロ除算(CWE-369)・NULL 参照(CWE-476)・多分岐の default 欠落(CWE-478)も入ります。この回が実演するのは、24 のうち認可判定の fail open と複数段の更新の途中での失敗の2つだけです。
- ・ログを書いたかどうかは、どちらへ落ちるかを決めない
- ・CWE-636 は、失敗時により安全でない状態へ後退する設計を弱点とする
DB を止めたとき、2つの catch が返したもの
手元の模型です(実在の製品ではありません)。Docker 28.4.0(macOS 26.5.2・arm64)上で、postgres:18(PostgreSQL 18.6)と node:24.21.0-bookworm-slim(Node.js v24.21.0、npm パッケージ pg 8.23.0)を動かしました。利用者はダミーのヘッダ x-user で名乗り、alice は admin、bob は user です。2つの版は catch の中身だけが違います。
async function authzFailOpen(user) {try {const r = await pool.query("SELECT role FROM users WHERE name = $1", [user]);if (r.rows[0]?.role !== "admin") return { allow: false, status: 403, why: "role is not admin" };} catch (e) {// "don't let a DB hiccup break the page" -- swallow and continuelog(...);}return { allow: true, status: 200, why: "allowed" };}async function authzFailClosed(user) {try {const r = await pool.query("SELECT role FROM users WHERE name = $1", [user]);if (r.rows[0]?.role !== "admin") return { allow: false, status: 403, why: "role is not admin" };return { allow: true, status: 200, why: "allowed" };} catch (e) {log(...);return { allow: false, status: 503, why: "authorization unavailable" };}}
docker stop で DB を止めて、bob で両方を叩きました。
## phase A: DB up$ curl -s -w " => HTTP %{http_code}\n" -H "x-user: bob" http://localhost:18080/open/adminrole is not admin=> HTTP 403## phase B: docker stop l4-pgl4-pg$ curl -s -w " => HTTP %{http_code}\n" -H "x-user: bob" http://localhost:18080/open/adminADMIN REPORT (secret dummy data) for bob=> HTTP 200$ curl -s -w " => HTTP %{http_code}\n" -H "x-user: bob" http://localhost:18080/closed/adminauthorization unavailable=> HTTP 503## app log since 2026-09-28T10:16:41Z2026-09-28T10:16:44.457Z [open] authz lookup failed: ENOTFOUND -- continuing2026-09-28T10:16:44.457Z [open] GET /open/admin user=bob -> 200 (allowed)2026-09-28T10:16:44.484Z [closed] authz lookup failed: ENOTFOUND -- denying2026-09-28T10:16:44.484Z [closed] GET /closed/admin user=bob -> 503 (authorization unavailable)
DB が動いている間、bob は両版とも 403 でした。止めると fail open 版は bob に管理レポートを返します。ログには失敗が書かれているのに、応答は許可です。 例外は Docker 内部の名前解決が止まったコンテナ名を引けなくなった ENOTFOUND で、別の環境では別の種類になりえますが、この2関数は例外の種類で分岐していません。
MITRE の CWE-636 はこの形を、失敗時に the most permissive access control restrictions のような安全でない状態へ後退する設計と書き、原因を wanting to "fail functional" と説明します。「DB の不調でページを落とさない」という善意の1行が、そのまま当てはまります。
実演で、DB 停止中に fail open 版が bob へ 200 を返し、fail closed 版が 503 を返しました。結果を分けたのは何ですか。
途中で止まった更新 — 原文の fail closed は「全部戻す」
A10:2025 の How to prevent 節は、途中まで進んだ処理についてこう書きます。
If you are part way through a transaction of any kind, it is extremely important that you roll back every part of the transaction and start again (also known as failing closed). Attempting to recover a transaction part way through is often where we create unrecoverable mistakes.
この節の failing closed は「トランザクションを全部戻すこと」です。 一般には認可の拒否も fail closed(フェイルクローズ)と呼ばれ、既存クイズもその用法です。A10:2025 のこの節は、同じ語をトランザクションの全戻しに使っています。原文を引くときは、どちらの意味かを確かめてください。
原文の Scenario #3 は、引き落とし・入金・記録の3段の途中で失敗すると状態が壊れる構造を挙げます。この順を模型で再現し、引き落としの直後に DB を止めました。
応答は同じ 500、残った状態が違う
引き落としと入金の間に SELECT pg_sleep(6) を挟み、2秒後に docker stop しました。postgres:18 イメージの停止シグナルは SIGINT で、PostgreSQL の Fast Shutdown にあたります。ネットワーク断の再現ではなく、DB の側が接続を切った実験です。/transfer/plain は文ごとに自動コミット、/transfer/tx は BEGIN … COMMIT で括っています。
## phase B: reset, interrupt /transfer/plain (no transaction)$ curl -s http://localhost:18080/balancesalice=1000 bob=0 ledger_rows=0$ curl -s -w " => HTTP %{http_code}\n" "http://localhost:18080/transfer/plain?slow=1&amount=100" & # runs in background$ docker stop l4-pg # 2s later, while the app is between debit and credittransfer failed: 57P01=> HTTP 500l4-pg$ docker start l4-pgl4-pg$ curl -s http://localhost:18080/balancesalice=900 bob=0 ledger_rows=0## phase C: reset, interrupt /transfer/tx (BEGIN ... COMMIT)$ curl -s http://localhost:18080/balancesalice=1000 bob=0 ledger_rows=0$ curl -s -w " => HTTP %{http_code}\n" "http://localhost:18080/transfer/tx?slow=1&amount=100" & # runs in background$ docker stop l4-pg # 2s later, while the app is between debit and credittransfer failed: 57P01=> HTTP 500l4-pg$ docker start l4-pgl4-pg$ curl -s http://localhost:18080/balancesalice=1000 bob=0 ledger_rows=0
応答は両方とも 500 と 57P01 です。応答だけを見ても違いは分かりません。 自動コミット版は alice から 100 が引かれたまま bob に届かず、記録もありません。BEGIN 版は元のままです。
では、誰が戻したのか。
## app log since 2026-09-28T10:19:22Z2026-09-28T10:19:30.777Z [tx] debit done (uncommitted)2026-09-28T10:19:32.828Z [tx] client 'error' event: Connection terminated unexpectedly2026-09-28T10:19:32.828Z [tx] ROLLBACK failed: Connection terminated unexpectedly2026-09-28T10:19:32.828Z [tx] GET /transfer/tx?slow=1 -> 500 (57P01)## postgres log since 2026-09-28T10:19:22Z (grep shutdown|terminat|ready)2026-09-28 10:19:32.823 UTC [1] LOG: received fast shutdown request2026-09-28 10:19:32.825 UTC [36] FATAL: terminating connection due to administrator command
アプリの ROLLBACK は接続が切れていて失敗しています。PostgreSQL 18 の文書は Fast Shutdown で既存のプロセスが abort their current transactions と書きます。戻したのはサーバ側で、言えるのは COMMIT が届いていない変更は残らなかった、までです。
副産物もありました。最初の走行では、取り出した接続に 'error' のリスナを付けておらず、DB 停止で throw er; // Unhandled 'error' event と出て Node のプロセスごと終了しました。A10:2025 の一覧にある CWE-248 Uncaught Exception がそのまま起きた形です(観察は pg 8.23.0 と Node v24.21.0 だけ)。言語ごとの無視のされ方は第25期で扱います。
BEGIN で括った送金を途中で止めた実験で、DB 復帰後の残高が元に戻っていました。戻したのは何だと言えますか。
認可判定の例外(CWE-636)
DB 停止中、fail open 版はログに失敗を書いたうえで bob に 200 と管理レポートを返した。落ち着き先は関数末尾の許可で、catch の外側の書き方が決めた。
複数段の更新の途中の例外(Scenario #3 の構造)
両版とも 500 と 57P01 で応答は同じ。自動コミット版は引き落としだけが残り、BEGIN 版は元のまま。差は DB の状態にだけ出た。
片方は応答に差が出て、片方は応答に差が出ませんでした。それでも共通する見るべき場所はどこでしょうか。
「例外が起きた」の次に、何が残っているか・何が許されたかを両方で探してください。
この章のまとめ
- A10:2025 は新設。24 の CWE に CWE-636 Failing Open を含む。2021 の A10(SSRF)は A01:2025 に統合され、番号だけが再利用された。
- 範囲は例外処理のバグより広い。今回はそのうち fail open と途中の更新の2つだけを試した。
- fail open 版は DB 停止中に bob へ 200 と管理レポート。ログに失敗が出ていても応答は許可だった。
- 送金の途中で止めると、応答はどちらも 500。差は状態にだけ出た。BEGIN 版を戻したのはアプリの ROLLBACK ではなく、サーバの Fast Shutdown。
- fail closed は一般には認可の拒否も指すが、A10:2025 の How to prevent 節ではトランザクションを全部戻す意味。引くときはどちらの意味かを確かめる。
- 詳しすぎるエラーの露出は A02:2025 と A10:2025 の両方にある。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
2021 の A10 だった Server-Side Request Forgery(SSRF)は、Web Top 10 の 2025 版ではどう扱われていますか。
用語集(3語)
- fail open(失敗時に開く)
- 判定に失敗したとき、より安全でない状態へ落ちる設計。CWE-636 Not Failing Securely ('Failing Open') が扱い、例として最も緩いアクセス制御への後退を挙げる。
- fail closed(フェイルクローズ)
- 一般には、判定に失敗したらアクセスを拒否する側に倒すこと。A10:2025 の How to prevent 節は同じ語を、途中まで進んだトランザクションを全部戻してやり直す意味で使っている。
- Fast Shutdown
- PostgreSQL が SIGINT で行う停止。新しい接続を断り、既存のプロセスに進行中のトランザクションを中断させて終了する。postgres:18 イメージの停止シグナルは SIGINT だった。
出典(一次ソース)
- OWASP Top 10:2025 — A10:2025 Mishandling of Exceptional Conditions(GitHub OWASP/Top10 コミット 3a31f35、取得 2026-09-28) ↗
- OWASP Top 10:2025 — Introduction(同コミット 3a31f35、取得 2026-09-28) ↗
- OWASP Top 10:2025 — A01:2025 Broken Access Control / A02:2025 Security Misconfiguration(同コミット、取得 2026-09-28) ↗
- OWASP Top 10:2021 — A10:2021 Server-Side Request Forgery (SSRF)(旧版。同コミット、取得 2026-09-28) ↗
- MITRE CWE-636: Not Failing Securely ('Failing Open')(ページ表示 CWE 4.20、取得 2026-09-28) ↗
- OWASP Proactive Controls v4(2024)— C3: Validate all Input & Handle Exceptions(コミット 4f5cb10、取得 2026-09-28) ↗
- PostgreSQL 18 Documentation — 18.5 Shutting Down the Server(取得 2026-09-28) ↗