Path protection with pathlib
This commit is contained in:
parent
b4360d4a20
commit
05002d7ee4
|
@ -1,6 +1,7 @@
|
||||||
from functools import partial, wraps
|
from functools import partial, wraps
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from os import path, sep
|
from os import path, sep
|
||||||
|
from pathlib import Path
|
||||||
from time import gmtime, strftime
|
from time import gmtime, strftime
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
@ -26,31 +27,39 @@ async def _static_request_handler(
|
||||||
file_uri=None,
|
file_uri=None,
|
||||||
):
|
):
|
||||||
# Merge served directory and requested file if provided
|
# Merge served directory and requested file if provided
|
||||||
root_path = file_path = path.abspath(unquote(file_or_directory))
|
file_path_raw = Path(unquote(file_or_directory))
|
||||||
|
root_path = file_path = file_path_raw.resolve()
|
||||||
|
not_found = FileNotFound(
|
||||||
|
"File not found",
|
||||||
|
path=file_or_directory,
|
||||||
|
relative_url=file_uri,
|
||||||
|
)
|
||||||
|
|
||||||
if file_uri:
|
if file_uri:
|
||||||
# Strip all / that in the beginning of the URL to help prevent
|
# Strip all / that in the beginning of the URL to help prevent
|
||||||
# python from herping a derp and treating the uri as an
|
# python from herping a derp and treating the uri as an
|
||||||
# absolute path
|
# absolute path
|
||||||
unquoted_file_uri = unquote(file_uri).lstrip("/")
|
unquoted_file_uri = unquote(file_uri).lstrip("/")
|
||||||
|
file_path_raw = Path(file_or_directory, unquoted_file_uri)
|
||||||
|
file_path = file_path_raw.resolve()
|
||||||
|
if (
|
||||||
|
file_path < root_path and not file_path_raw.is_symlink()
|
||||||
|
) or ".." in file_path_raw.parts:
|
||||||
|
error_logger.exception(
|
||||||
|
f"File not found: path={file_or_directory}, "
|
||||||
|
f"relative_url={file_uri}"
|
||||||
|
)
|
||||||
|
raise not_found
|
||||||
|
|
||||||
segments = unquoted_file_uri.split("/")
|
try:
|
||||||
if ".." in segments or any(sep in segment for segment in segments):
|
file_path.relative_to(root_path)
|
||||||
raise InvalidUsage("Invalid URL")
|
except ValueError:
|
||||||
|
if not file_path_raw.is_symlink():
|
||||||
file_path = path.join(file_or_directory, unquoted_file_uri)
|
error_logger.exception(
|
||||||
file_path = path.abspath(file_path)
|
f"File not found: path={file_or_directory}, "
|
||||||
|
f"relative_url={file_uri}"
|
||||||
if not file_path.startswith(root_path):
|
)
|
||||||
error_logger.exception(
|
raise not_found
|
||||||
f"File not found: path={file_or_directory}, "
|
|
||||||
f"relative_url={file_uri}"
|
|
||||||
)
|
|
||||||
raise FileNotFound(
|
|
||||||
"File not found",
|
|
||||||
path=file_or_directory,
|
|
||||||
relative_url=file_uri,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {}
|
headers = {}
|
||||||
|
|
|
@ -404,11 +404,10 @@ def test_dotted_dir_ok(
|
||||||
app: Sanic, static_file_directory: str, double_dotted_directory_file: Path
|
app: Sanic, static_file_directory: str, double_dotted_directory_file: Path
|
||||||
):
|
):
|
||||||
app.static("/foo", static_file_directory)
|
app.static("/foo", static_file_directory)
|
||||||
url = (
|
dot_relative_path = str(
|
||||||
"/foo"
|
double_dotted_directory_file.relative_to(static_file_directory)
|
||||||
+ str(double_dotted_directory_file)[len(static_file_directory) :]
|
|
||||||
)
|
)
|
||||||
_, response = app.test_client.get(url)
|
_, response = app.test_client.get("/foo/" + dot_relative_path)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == b"DOT\n"
|
assert response.body == b"DOT\n"
|
||||||
|
|
||||||
|
@ -416,8 +415,11 @@ def test_dotted_dir_ok(
|
||||||
def test_breakout(app: Sanic, static_file_directory: str):
|
def test_breakout(app: Sanic, static_file_directory: str):
|
||||||
app.static("/foo", static_file_directory)
|
app.static("/foo", static_file_directory)
|
||||||
|
|
||||||
|
_, response = app.test_client.get("/foo/..%2Ffake/server.py")
|
||||||
|
assert response.status == 404
|
||||||
|
|
||||||
_, response = app.test_client.get("/foo/..%2Fstatic/test.file")
|
_, response = app.test_client.get("/foo/..%2Fstatic/test.file")
|
||||||
assert response.status == 400
|
assert response.status == 404
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
|
@ -429,6 +431,6 @@ def test_double_backslash_prohibited_on_win32(
|
||||||
app.static("/foo", static_file_directory)
|
app.static("/foo", static_file_directory)
|
||||||
|
|
||||||
_, response = app.test_client.get("/foo/static/..\\static/test.file")
|
_, response = app.test_client.get("/foo/static/..\\static/test.file")
|
||||||
assert response.status == 400
|
assert response.status == 404
|
||||||
_, response = app.test_client.get("/foo/static\\../static/test.file")
|
_, response = app.test_client.get("/foo/static\\../static/test.file")
|
||||||
assert response.status == 400
|
assert response.status == 404
|
||||||
|
|
Loading…
Reference in New Issue
Block a user