Information disclosure via filesystem existence oracle in web_main.py catch-all route

Đang mở Phù hợp với người mới
#1,112 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@0x5t4l1n đang làm issue này rồi.

Từ ngày 5/10/2026.

  • #1133 của @0x5t4l1n — đang mở

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
78/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
flask, python
Lĩnh vực
backend, security

Hướng nghiên cứu

Bắt đầu tại application/web/web_main.py:747-753 và kiểm tra route index catch-all, sau đó tái hiện các phản hồi khác nhau bằng Flask test client với các path tồn tại và không tồn tại bên ngoài thư mục static. Thêm một regression test bao quát các trường hợp đó và xác minh rằng cả hai đều được phân giải nhất quán thành phản hồi dự kiến mà không để lộ sự tồn tại trong filesystem.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

@northdpole as discussed in #project-cre.

Summary

The catch-all route that serves the React app answers differently depending on whether a path exists on the server filesystem, so an unauthenticated caller can test for arbitrary paths outside the static directory. File contents do not leak; existence does.

Where

application/web/web_main.py:747-753 (on fbacf559):

@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def index(path: str) -> Any:
    if path != "" and os.path.exists(os.path.join(app.static_folder, path)):
        return send_from_directory(app.static_folder, path)
    else:
        return send_from_directory(app.static_folder, "index.html")

Why it happens

Two functions decide what is in bounds and they disagree.

os.path.join applies no containment check, and Flask's <path:path> converter passes .. through, so the guard resolves paths outside the static root and os.path.exists answers about them truthfully.

send_from_directory does check, through Werkzeug's safe_join, and refuses anything that escapes, raising 404.

The read is therefore blocked. But the two branches return different responses, and that difference is the leak: an existing path takes the first branch and 404s, a missing one falls through and serves index.html with 200.

Reproduction

Flask 3.1.3, Werkzeug 3.1.8, route copied verbatim into a test client:

GET /../../../../etc/passwd                 404   207 bytes   (exists)
GET /../../../../etc/definitely-not-here    200    16 bytes   (does not exist)
GET /../../../../etc/shadow                 404   207 bytes   (exists, unreadable)
GET /../../../../root                       404   207 bytes   (directory, exists)
GET /../../../../home/nonexistentuser       200    16 bytes   (does not exist)

404 means the path exists, 200 means it does not. It reports existence for files the process cannot read, and for directories.

Impact

Low. No file contents are disclosed and there is no read primitive; safe_join holds. What leaks is filesystem structure, to an unauthenticated remote caller: host fingerprinting, which packages are installed, whether particular users, config files or deployment paths exist. Roughly CWE-204, observable response discrepancy, reached through a path-traversal-shaped input.

Suggested fix

Have both branches use the same containment rule:

from werkzeug.security import safe_join

@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def index(path: str) -> Any:
    if path:
        full = safe_join(app.static_folder, path)
        if full is not None and os.path.exists(full):
            return send_from_directory(app.static_folder, path)
    return send_from_directory(app.static_folder, "index.html")

Happy to open a PR with this and a regression test if useful.

Ngôn ngữ chính
Python
Star
210
Fork
167
Merge trung bình
4 ngày 12 giờ
Pull request đã merge (30 ngày)
28

Chuẩn bị môi trường

Mở trong Codespaces

Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của OWASP/OpenCRE

Tất cả issue của OWASP/OpenCRE

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.