Vary ヘッダーの役割と Flask における検証

CVE-2026-27205 において Flask の Vary ヘッダーの不備が報告されていた (修正済み) ので, Vary ヘッダーの学習用の覚え書き

Vary Header とは?

例 (モバイルサイトの対応)

Vary ヘッダーに関する実験

from flask import Flask, make_response, request

app = Flask(__name__)


@app.get("/")
def index():
    user_agent = request.headers.get("User-Agent", "unknown")
    resp_txt = f"Hello from Flask. User-Agent: {user_agent}\n"
    user_hello = request.headers.get("Hello", "see you!")
    resp_txt += user_hello + "\n"
    response = make_response(resp_txt)
    response.headers["Cache-Control"] = "public, max-age=60"
    response.headers["Vary"] = "User-Agent"
    return response
        

curl によるキャッシュの検証

curl -v -A "curl" -H "Hello:Hello!" http://localhost:3210/
        
* Host localhost:3210 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:3210...
* Connected to localhost (::1) port 3210
> GET / HTTP/1.1
> Host: localhost:3210
> User-Agent: curl
> Accept: */*
> Hello:Hello!
> 
< HTTP/1.1 200 OK
< Server: nginx/1.30.0
< Date: Thu, 07 May 2026 13:16:26 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 42
< Connection: keep-alive
< Cache-Control: public, max-age=60
< Vary: User-Agent
< X-Cache-Status: MISS
< 
Hello from Flask. User-Agent: curl
Hello!
* Connection #0 to host localhost left intact
        
curl -v -A "curl" -H "Hello:Hi!" http://localhost:3210/
        
< X-Cache-Status: HIT
< 
Hello from Flask. User-Agent: curl
Hello!
* Connection #0 to host localhost left intact
        

CVE-2026-27205 について

Docker を用いた検証

    if "authenticated" in session:
        resp_txt = "Hello Pumpkin!\n"
    else:
        resp_txt = "Please Login\n"
        
    resp_txt += "Session accessed:" + str(session.accessed)
        
import requests

BASE_URL = "http://localhost:3210"


def login(passwd):
    # Session keeps cookies received during redirects and reuses them for later requests.
    client = requests.Session()

    # requests follows redirects by default, but keep it explicit for this verification.
    resp = client.post(
        f"{BASE_URL}/",
        data={"password": passwd},
        allow_redirects=True,
        timeout=10,
    )

    if "Please Login" in resp.text:
        print("Login Miss!, headers:", resp.headers)
    elif "Hello Pumpkin!" in resp.text:
        print("Login Success!, headers:", resp.headers)
        
    # Print only the session access line from the response body.
    for line in resp.text.splitlines():
        if "Session accessed:" in line:
            print(line)

if __name__ == "__main__":
    login(passwd="Labu")
    login(passwd="Labu")
    login(passwd="Pumpkin")
        
Login Success!, headers: {'Server': 'nginx/1.30.0', 'Date': 'Sun, 17 May 2026 12:56:10 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '211', 'Connection': 'keep-alive', 'Vary': 'Cookie', 'X-Cache-Status': 'MISS'}
Session accessed:True
Login Success!, headers: {'Server': 'nginx/1.30.0', 'Date': 'Sun, 17 May 2026 12:56:10 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '211', 'Connection': 'keep-alive', 'Vary': 'Cookie', 'X-Cache-Status': 'HIT'}
Session accessed:True
Login Miss!, headers: {'Server': 'nginx/1.30.0', 'Date': 'Sun, 17 May 2026 12:56:10 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '209', 'Connection': 'keep-alive', 'Vary': 'Cookie', 'X-Cache-Status': 'MISS'}
Session accessed:True
        
Login Success!, headers: {'Server': 'nginx/1.30.0', 'Date': 'Sun, 17 May 2026 12:53:51 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '212', 'Connection': 'keep-alive', 'X-Cache-Status': 'EXPIRED'}
Session accessed:False
Login Success!, headers: {'Server': 'nginx/1.30.0', 'Date': 'Sun, 17 May 2026 12:53:51 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '212', 'Connection': 'keep-alive', 'X-Cache-Status': 'HIT'}
Session accessed:False
Login Success!, headers: {'Server': 'nginx/1.30.0', 'Date': 'Sun, 17 May 2026 12:53:51 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '212', 'Connection': 'keep-alive', 'X-Cache-Status': 'HIT'}
Session accessed:False