---
title: "Starletteの脆弱性CVE-2026-48710とは｜Hostヘッダで認可を迂回される条件と修正手順"
url: "https://www.issoh.co.jp/tech/details/18235/"
published: 2026-10-10
updated: 2026-10-10
categories: ["セキュリティ"]
publisher: "株式会社一創"
---

# Starletteの脆弱性CVE-2026-48710とは｜Hostヘッダで認可を迂回される条件と修正手順

Starletteの脆弱性として2026年に最も広く影響したのが、CVE-2026-48710（通称BadHost）です。FastAPIの土台であるStarletteが、HTTPの`Host`ヘッダを検証しないまま`request.url`を組み立てていたため、ヘッダを細工するだけでパスを見て認可するミドルウェアを素通りできました。2026年9月2日には米国CISAの悪用確認済み脆弱性カタログ（KEV）へ追加されています。ここでは仕組みを手元で再現した結果を出発点に、自社のアプリが該当するかの判定、FastAPIの依存制約で修正版へ上げられないケース、恒久対策の書き方までを2026年10月時点の一次情報で整理します。

## まとめ：Starletteの脆弱性CVE-2026-48710で先に確かめる3つの条件

結論から書きます。該当するかどうかは、（1）Starletteが1.0.0以下か、（2）`request.url.path`や`request.url`を使って認可や遮断を判断するミドルウェアがあるか、（3）RFC準拠のリバースプロキシを通さずにASGIサーバーへ直接届く経路があるか、の3条件で決まります。3つすべてが揃うと、認証なしで保護対象のエンドポイントへ到達される状態です。

対応はバージョンを上げるのが先。ただし修正版の1.0.1で止めず、2026年に続けて公表された4件のCVEまで塞がる1.3.1以上（2026年10月時点の最新は1.7.0）を狙ってください。もう1つの落とし穴がFastAPI側で、0.132.1以前は依存定義が`starlette`を1.0.0未満に固定しているため、Starletteだけを上げようとしても依存解決が失敗します。FastAPIを0.133.0以上へ同時に上げる必要がある、というのが本記事の芯です。脆弱性の一般論とCVE番号の読み方は[脆弱性の種類と発見から修正までの実務](https://www.issoh.co.jp/tech/details/13546/)を前提にします。

## CVE-2026-48710の仕組みとHostヘッダでパスがずれる理由

まず、何が起きていたのかを修正コミットの差分から確認します。

### 修正前のStarletteがrequest.urlをHostヘッダから組み立てる手順

Starletteは`request.url`を作るとき、スキームと`Host`ヘッダの値とリクエスト行のパスを文字列として連結し、それを改めてURLとして解析していました。発見者である[X41 D-Secのアドバイザリ X41-2026-002](https://www.x41-dsec.de/lab/advisories/x41-2026-002-starlette/)の説明では、`GET /foo`に`Host: example.com/abc?bar=`を付けると、連結結果は`http://example.com/abc?bar=/foo`になります。これを解析し直すと、パスは`/abc`、本来の`/foo`はクエリ文字列の一部です。

一方、ルーティングはリクエスト行の生のパス（ASGIの`scope["path"]`）で行われるため、実際に実行されるのは`/foo`のエンドポイント。ミドルウェアが見る`request.url.path`と、実行されるルートが食い違う。この食い違いが脆弱性の本体です。[修正コミット764dab0](https://github.com/Kludex/starlette/commit/764dab0dcfb9033d75442d7a359645c9f94648c6)では、`Host`の値を英数字・ドット・ハイフンとポート番号（IPv6は角括弧）に限る正規表現で検証し、外れた場合はヘッダを捨てて`scope["server"]`のホスト名へフォールバックする形に変わりました。

### KEVの名称はスマグリングでも実体はHostヘッダ検証不備という整理

情報を集めると、呼び名が揃っていないことに気づきます。[CISAのKEVカタログ](https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field%5Fcve=CVE-2026-48710)での名称は「HTTP Request/Response Smuggling Vulnerability」で、NVDも分類にCWE-444（HTTPリクエストスマグリング）を付けています。ところが開発元の[GitHubアドバイザリGHSA-86qp-5c8j-p5mr](https://github.com/Kludex/starlette/security/advisories/GHSA-86qp-5c8j-p5mr)の題名は「Host header validationの欠如がrequest.url.pathを汚染し、パスベースのセキュリティ検査を迂回させる」です。

実装の観点では後者で理解してください。プロキシとサーバーの間で1つの通信を2つに切り分けるいわゆるリクエストスマグリングとは手口が異なり、単一のリクエストのヘッダ1つで成立する手口です。KEVの名称だけで「プロキシの設定問題」と受け取ると、アプリ側のミドルウェアを点検しないまま終わる恐れがあります。CVE番号とNVD・KEVの関係そのものは[CVEの仕組みとCVSS・CWE・NVDとの違い](https://www.issoh.co.jp/tech/details/5092/)で解説しています。

### CVSS 6.5のModerateなのにKEV入りして期限14日になった経緯

時系列を並べると、評価の割れ方が見えてきます。GHSAの公開と1.0.1のリリースは2026年5月21日、NVDの登録は5月26日で、深刻度はCVSS 3.1で6.5（Moderate）。発見者のX41はCVSS 4.0で7（High）と評価し、監査を支援したOSTIFも5月26日の公表文で「アドバイザリの評価は下流での深刻さを大きく過小評価している」と書きました。

そして2026年9月2日、CISAは本CVEをKEVへ追加し、連邦機関向けの対応期限を9月16日（14日後）に設定しています。CISAが付けたSSVCの判定は、悪用が「active」、自動化が「yes」。KEVの説明文は、LiteLLMのコマンド注入であるCVE-2026-42271（CVSS 3.1で8.8・2026年6月8日にKEV追加）と連鎖し得るとも記しています。点数は中程度でも、認証の手前を崩す脆弱性は後ろに控える欠陥と組み合わさって実害に変わる、という典型です。KEVの期限がどう決まるかは[KEVの是正期限を4変数で決めるBOD 26-04の仕組み](https://www.issoh.co.jp/tech/details/18233/)で扱っています。

## 手元で再現する：Starlette 1.0.0と1.7.0で同じリクエストを比べる

サーバーを起動せず、ASGIアプリを直接呼び出す形で再現しました。

### ネットワーク不要でASGIアプリを直接呼ぶ再現スクリプトの全体

次のスクリプトは、`/health`だけを公開し、それ以外を403で止めるミドルウェアを`request.url.path`で書いた例です。`Host`ヘッダを差し替えて`/admin`を2回呼びます。依存はStarlette本体だけで、`pip install "starlette==1.0.0"`のように版を固定した仮想環境で実行してください。

```
import asyncio
import starlette
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route

class PathAuth(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        # 危険な書き方：request.url.path で認可を判定している
        if request.url.path in ("/", "/health"):
            return await call_next(request)
        return PlainTextResponse("Forbidden", status_code=403)

async def health(request):
    return PlainTextResponse("ok")

async def admin(request):
    return PlainTextResponse("secret")

app = Starlette(routes=[Route("/health", health), Route("/admin", admin)],
                middleware=[Middleware(PathAuth)])

async def call(host):
    scope = {"type": "http", "asgi": {"version": "3.0"}, "http_version": "1.1",
             "method": "GET", "scheme": "http", "path": "/admin", "raw_path": b"/admin",
             "query_string": b"", "root_path": "", "headers": [(b"host", host)],
             "server": ("127.0.0.1", 8000), "client": ("127.0.0.1", 50000)}
    status = {}
    async def receive():
        return {"type": "http.request", "body": b"", "more_body": False}
    async def send(msg):
        if msg["type"] == "http.response.start":
            status["code"] = msg["status"]
    await app(scope, receive, send)
    return status["code"]

for h in [b"example.com", b"example.com/health?x="]:
    print(starlette.__version__, h.decode(), asyncio.run(call(h)))
```

2026年10月10日に実行した結果は次のとおりです。1.0.0では、細工した`Host`を付けた2回目だけが200になり、`/admin`の中身が返りました。1.7.0では2回とも403で、修正後は不正な`Host`が無視されてサーバー側のホスト名に置き換わるため、ミドルウェアは本来のパスを見ています。

```
1.0.0 example.com 403
1.0.0 example.com/health?x= 200
1.7.0 example.com 403
1.7.0 example.com/health?x= 403
```

### FastAPIで3通りの認可の書き方を比べた実測結果と読み方

同じ手順をFastAPI 0.133.0とStarlette 1.0.0の組み合わせで、認可の書き方だけを変えて試しました。比べたのは、`@app.middleware("http")`の中で`request.url.path`を見る書き方、同じ場所で`request.scope["path"]`を見る書き方、そしてエンドポイントに`Depends`で認可関数を付ける書き方の3つです。

| 認可の書き方              | 通常のHost | 細工したHost |
| ------------------- | ------- | -------- |
| request.url.pathで判定 | 403     | 200（迂回）  |
| scope\[“path”\]で判定  | 403     | 403      |
| Dependsで判定          | 403     | 403      |

読み方は明快です。脆弱なのはStarlette 1.0.0以下と、`request.url`からパスを取り出して判断するコードの組み合わせ。`scope["path"]`はリクエスト行から来る値なので`Host`の影響を受けず、`Depends`はルーティングが確定した後のエンドポイント単位で動くため、パスの解釈がずれても認可は外れません。[BadHostの解説サイト](https://badhost.org/)も、FastAPI標準の`Depends()`と`Security()`による認可は安全側だと明記しています。

## 影響を受ける条件をバージョンと認可処理の書き方の2軸で判定する手順

再現結果から、判定は2軸に分けられます。片方だけでは結論が出ないので、両方を確認してください。

### 影響範囲はStarlette 0.8.3から1.0.0まで・修正は1.0.1以降

X41のアドバイザリによれば、影響を受けるのは0.8.3以上1.0.1未満で、0.8.3は2018年11月の公開。ほぼすべての現役環境が対象に入ります。まず実行環境に入っている版を確かめます。仮想環境ごとに違う版が入っていることがあるので、本番と同じイメージやロックファイルで確認してください。

```
python -c "import starlette, fastapi; print('starlette', starlette.__version__, '/ fastapi', fastapi.__version__)"
pip show starlette fastapi
uv pip list | grep -i -E "starlette|fastapi"
```

注意したいのは、OSのパッケージで入れたStarletteです。[Debianのセキュリティトラッカー](https://security-tracker.debian.org/tracker/CVE-2026-48710)では、bookwormは`0.26.1-1+deb12u1`、trixieは`0.46.1-3+deb13u2`でバックポートにより修正済み（DSA-6302-1）。版番号だけを見れば1.0.0未満でも、パッケージのリビジョンで直っている場合があります。逆に、OS側を更新しても`pip`や`uv`で作った仮想環境の中は別物で、何も変わりません。

### request.url.pathを認可に使うミドルウェアをgrepで洗い出す手順

版が該当したら、次はコード側です。危ないのは、ミドルウェアで`request.url`から取ったパスを使い、許可リスト・拒否リスト・CSRFの除外・レート制限・課金判定などを決めている箇所。自作のコードと、インストール済みの依存パッケージの両方を検索します。

```
grep -rn --include="*.py" -E "request\.url(\.path)?|URL\(scope" ./app
grep -rln --include="*.py" -E "request\.url\.path" .venv/lib
```

2行目はライブラリ側を見る検索で、Windowsの仮想環境なら`.venv/Lib`に読み替えてください。ヒットした箇所が「表示用のURL生成」なのか「通してよいかの判断」なのかを1件ずつ仕分けます。前者は影響が限られ、後者は即時の修正対象です。社内で仕分けの判断がつかない、あるいは外部に公開しているAPIが多く手が回らない場合は、[脆弱性診断・セキュリティ診断](https://www.issoh.co.jp/service/system/vulnerability/)で外側から到達可能性を確かめる方法もあります。

### vLLMやLiteLLM、MCPサーバーなどAI基盤が狙われやすい理由

OSTIFとBadHostの公表文が名指ししているのは、vLLM、LiteLLM、OpenAI互換のプロキシ、MCPサーバー、エージェント基盤です。これらはFastAPIやStarletteの上に作られ、`/v1/models`や管理用の`/admin`のようにパスの前方一致で保護範囲を決める実装が多い。BadHostの解説サイトはさらに、MCPの仕様がOAuthの検出用エンドポイントを認証なしで公開させるため、`Host`に差し込む「公開パス」が確実に手に入ると指摘しています。

自前でLLMの推論サーバーやMCPサーバーを立てている場合は、上流のプロジェクトが修正版のStarletteを要求しているか、コンテナイメージの中の版を必ず確認してください。推論サーバー側の構成は[vLLMの仕組みと使い方](https://www.issoh.co.jp/tech/details/15786/)、PythonでのMCPサーバー実装は[FastMCP 4の移行判断](https://www.issoh.co.jp/tech/details/15907/)で扱っています。

## 修正手順：FastAPI 0.132以前はStarletteだけを上げられない

ここからが、日本語の解説でほとんど触れられていない部分です。修正版のStarletteを入れようとして、依存解決で止まる環境が少なくありません。

### FastAPIの依存制約がstarlette 1.0未満で止まる版の境界を確かめる

FastAPIは長くStarletteの上限を固定してきました。[PyPIのFastAPI](https://pypi.org/project/fastapi/)の各版の依存定義を確かめると、0.115.12は0.47未満、0.128.0は0.51未満、0.128.8から0.132.1までは1.0.0未満です。上限が外れたのは2026年2月24日公開の0.133.0で、0.134.0以降は下限0.46.0のみになりました。2026年10月8日公開の最新0.143.0も同じ定義です。

つまりFastAPI 0.132.1以前のまま`starlette`を1.0.1以上に指定しても、依存の矛盾で解決できません。手元の`uv`で確かめた結果が次で、`fastapi==0.132.1`と`starlette>=1.0.1`は「解なし」、`fastapi==0.133.0`と`starlette==1.7.0`、`fastapi==0.143.0`と`starlette>=1.3.1`はどちらも解決しました。直すにはFastAPIとStarletteを1回の変更で同時に上げます。

```
# pyproject.toml の dependencies 例
dependencies = [
    "fastapi>=0.133.0",
    "starlette>=1.3.1",
]

# uv を使う場合（ロックファイルも更新される）
uv add "fastapi>=0.133.0" "starlette>=1.3.1"
```

`starlette`を直接の依存に書いておくのは、FastAPIの依存定義が下限0.46.0のままだからです。FastAPIだけを上げても、ロックファイルに残った古いStarletteが据え置かれる場合があります。FastAPIの基本構造とバージョンの追い方は[FastAPIを最短で動かす手順](https://www.issoh.co.jp/tech/details/3859/)を参照してください。

### 1.0.1で止めず1.3.1以上へ上げる理由と2026年の後続CVE4件

CVE-2026-48710だけなら1.0.1で塞がります。しかし[Starletteのセキュリティアドバイザリ一覧](https://github.com/Kludex/starlette/security/advisories)を見ると、1.0.1の公開後に4件が続きました。1.0.1のまま止めると、これらが残ります。

| CVE            | 修正版   | 深刻度        |
| -------------- | ----- | ---------- |
| CVE-2026-48817 | 1.1.0 | Medium 5.3 |
| CVE-2026-48818 | 1.1.0 | High 7.5   |
| CVE-2026-54282 | 1.3.0 | Low 3.7    |
| CVE-2026-54283 | 1.3.1 | High 7.5   |

内容は順に、`HTTPEndpoint`で任意のHTTPメソッド名が属性として呼ばれる問題、Windows上の`StaticFiles`でUNCパス経由のSSRFとNTLM認証情報の漏えい、リクエストパスがホスト部に連結されて`request.url.hostname`が汚染される問題、そして`application/x-www-form-urlencoded`のフォームで上限設定が効かずDoSになる問題です。とくに[CVE-2026-54283](https://github.com/Kludex/starlette/security/advisories/GHSA-82w8-qh3p-5jfq)はフォームを受けるほぼすべてのAPIに関わります。下限は1.3.1、作業するなら[PyPIのStarlette](https://pypi.org/project/starlette/)で最新版（2026年10月時点は1.7.0・Python 3.10以上が必要）を確かめて、そこへ揃えるのが手戻りの少ない選び方。

### 更新を定例へ組み込みStarletteの次のCVEを取りこぼさない運用

Starletteは2026年5月21日の1.0.1から6月12日の1.3.1まで、約3週間でセキュリティ修正を含む版を4回出しました。依存更新の起票を自動化し、セキュリティ修正を含む更新だけはテストが通った時点で即日マージする、という線を先に決めておきます。更新の起票は[dependabot.ymlの設定とRenovateとの違い](https://www.issoh.co.jp/tech/details/6796/)の手順でプルリクエストとして届く形にでき、深刻度で止める基準は[CVSSの見方と計算方法](https://www.issoh.co.jp/tech/details/5096/)が判断材料になります。

## 恒久対策としてパスで認可しない設計へ寄せる認可処理の書き換え方

バージョンを上げれば今回の穴は塞がります。それでも、パス文字列で認可を決める設計そのものは脆いまま残ります。同じ型の欠陥は別のフレームワークでも起き得るからです。

### scope\[“path”\]で判定し直す書き換えとDependsへ寄せる判断基準

ミドルウェアで判定を続けるなら、パスは`request.url.path`ではなく`request.scope["path"]`から取ります。ただしこれは応急処置に近い位置づけです。新しく書くなら、認可はエンドポイントに`Depends`で結び付けるほうを選んでください。

```
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse

app = FastAPI()
PUBLIC_PATHS = {"/", "/health"}

# 応急：ミドルウェアで判定を続けるなら scope["path"] を使う
@app.middleware("http")
async def guard(request: Request, call_next):
    if request.scope["path"] in PUBLIC_PATHS:
        return await call_next(request)
    if request.headers.get("x-api-key") != "change-me":
        return JSONResponse({"detail": "Forbidden"}, status_code=403)
    return await call_next(request)

# 推奨：認可をエンドポイント単位の依存関数として付ける
def require_admin(request: Request):
    if request.headers.get("x-api-key") != "change-me":
        raise HTTPException(status_code=403)

@app.get("/admin", dependencies=[Depends(require_admin)])
def admin():
    return {"ok": True}
```

ミドルウェア側では`HTTPException`を投げず、応答オブジェクトを直接返している点に注目してください。[FastAPI公式のミドルウェアの説明](https://fastapi.tiangolo.com/tutorial/middleware/)にあるとおり、ミドルウェアはルーティングより手前ですべてのリクエストに割り込む仕組みで、エンドポイント向けの例外処理とは経路が異なります。判断の基準は単純で、「どのパスを守るか」をミドルウェアの文字列比較で表しているなら、ルート定義側へ移す対象です。

### リバースプロキシでHostヘッダを正規化しても完全な対策にならない理由

nginx・Caddy・Traefik・HAProxyのようにRFCに沿って`Host`を検証するリバースプロキシを前に置けば、不正な値はそこで弾かれます。BadHostの解説サイトも有効な緩和策として挙げています。

それでも完全な対策にはなりません。理由は3つあります。開発・検証環境やコンテナ間の内部通信では、ASGIサーバーへ直接届く経路が残りがちなこと。`X-Forwarded-Host`のように攻撃者が値を決められるヘッダを信頼する設定にしていると、プロキシの検証を素通りすること。そしてプロキシの設定変更1つで防御が消える、という依存関係ができることです。Uvicornを直接公開する構成と本番での前段の置き方は[Uvicornの基礎から本番デプロイまで](https://www.issoh.co.jp/tech/details/4843/)で整理しています。

## 更新時期の判断：即日上げる構成と、次の定例に回してよい構成の線引き

最後に、対応の速さを条件で言い切ります。KEV入りした以上、悪用の前提で考えるのが妥当です。

即日で上げるのは、Starletteが1.0.0以下で、かつ`request.url`系のパスで認可するミドルウェアがあり、ASGIサーバーへ直接届く経路が1つでもある構成。LLMの推論サーバーやMCPサーバー、管理APIを外部に出している場合はこれに当たると考えてください。FastAPIが0.132.1以前なら、FastAPIの更新を含めて同じ日に作業します。

次の定例に回してよいのは、認可がすべて`Depends`か外部の認証基盤で行われ、`grep`で`request.url.path`の判定箇所が見つからない構成です。ただし前述の後続CVE、特にフォームのDoSは別の問題として残るため、定例で1.3.1以上へ上げることは見送らないでください。見送ってよいのは、Starletteを含むサービスが社内ネットワークに閉じ、前段のプロキシで`Host`を固定していると設定ファイルで確認できたときだけで、その根拠はチケットに残します。

## よくある質問

検索の多い5つの質問に、2026年10月時点の一次情報で答えます。

### FastAPIを使っていればStarletteの脆弱性の影響を受けますか？

FastAPIはStarletteの上に作られているため、Starletteが1.0.0以下なら版の条件には該当します。ただし実際に迂回されるのは、ミドルウェアで`request.url.path`などを見て認可しているアプリに限られます。手元の実測でも、`Depends`で認可したエンドポイントは細工した`Host`でも403のままでした。

### CVE-2026-48710はStarletteのどのバージョンで修正されましたか？

2026年5月21日公開の1.0.1で修正されました。不正な`Host`を無視してサーバー側のホスト名へ切り替える実装に変更されています。ただし1.0.1以降も4件のCVEが続いたため、1.3.1以上、可能なら2026年10月時点の最新である1.7.0へ上げるのが妥当です。DebianのOSパッケージはbookworm・trixieともバックポートで修正済みです。

### Starletteを1.0.1以上に指定するとインストールが失敗するのはなぜですか？

FastAPI 0.132.1以前が、依存定義でStarletteを1.0.0未満に固定しているためです。上限が外れたのは2026年2月24日公開のFastAPI 0.133.0からです。FastAPIを0.133.0以上へ、Starletteを1.3.1以上へ、1回の変更で同時に上げれば解決します。

### KEVに追加されたことは日本企業にも関係がありますか？

KEVの対応期限が法的に拘束するのは米国の連邦機関ですが、「実際に悪用が確認された」という事実はどの組織にも当てはまります。本CVEの期限は追加から14日後の2026年9月16日でした。優先度を決める材料として、KEV入りの有無はCVSSの点数より強い信号として扱ってください。

### リバースプロキシを置いていれば対応は不要ですか？

RFCに沿って`Host`を検証するプロキシは有効な緩和策ですが、対応不要にはなりません。検証環境や内部通信の直接経路、`X-Forwarded-Host`の信頼設定、プロキシの設定変更といった抜け道が残るからです。

## 関連記事

- [脆弱性とは？種類・CVE/CVSSの仕組みと発見から修正までの実務を解説](https://www.issoh.co.jp/tech/details/13546/)：脆弱性対応の全体像
- [BOD 26-04とは？KEVの是正期限を4変数で決める仕組みと自社運用への移し方](https://www.issoh.co.jp/tech/details/18233/)：KEVの期限の決まり方
- [FastAPIとは｜最短で動かす手順とasync/defの使い分け【2026年版】](https://www.issoh.co.jp/tech/details/3859/)：FastAPIの基本構造
- [Djangoの脆弱性への対応｜セキュリティリリースの追い方と標準で防げない穴](https://www.issoh.co.jp/tech/details/17089/)：Pythonフレームワークの脆弱性運用
- [脆弱性診断とは？種類・費用相場・進め方と外注時の判断基準を解説](https://www.issoh.co.jp/column/details/13101/)：外部診断の進め方

---

出典: [Starletteの脆弱性CVE-2026-48710とは｜Hostヘッダで認可を迂回される条件と修正手順](<https://www.issoh.co.jp/tech/details/18235/>)（株式会社一創）
