Information disclosure via filesystem existence oracle in web_main.py catch-all route
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 78/100
Direzione di ricerca
Inizia da application/web/web_main.py:747-753 e ispeziona la route index catch-all, quindi riproduci le risposte differenti con un client di test Flask usando percorsi esistenti e inesistenti al di fuori della directory statica. Aggiungi un test di regressione che copra questi casi e verifica che entrambi vengano risolti coerentemente nella risposta prevista senza esporre l’esistenza nel filesystem.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
@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.
- Lingua principale
- Python
- Stelle
- 210
- Fork
- 167
- Merge medio
- 4g 12h
- PR unite (30g)
- 28
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Include un Dockerfile o un file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di OWASP/OpenCRE
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
OWASP/OpenCRE#1215 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Map Analysis continues polling after a background job failsForse già presa @amantebriwal4321 l’ha presa 4 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
OWASP/OpenCRE#1211 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
OSCAL Compass adopterAperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 66/100
I maintainer di solito rispondono entro 1 giorno
-
MCP tools return "Internal MCP tool error." when OpenCRE is unreachable or times outForse già presa @ShaanGS l’ha presa 23 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
OWASP/OpenCRE#1163 · 5 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
find_node_by_name endpoint does not enforce MAX_ITEMS_PER_PAGE, allowing unbounded pagination queries`Forse già presa @Jeffy123-zhu l’ha presa 26 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
OWASP/OpenCRE#1149 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di OWASP/OpenCRE
Issue simili
-
Unknown stop ID in slow zone data crashes post formatting and skips the rest of that day's postsApertabackend bug tm-new-volunteer
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
transitmatters/mbta-slow-zone-bot#81 · 1 commento ·
-
area:schema documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
risk:high type:ci
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
spacetelescope/rdmt-spire#49 ·
I maintainer di solito rispondono entro 1 giorno