Skip to content

Commit

Permalink
[Dashboard] Prevent Directory Traversal (#39018)
Browse files Browse the repository at this point in the history
Ensure we can only go to subdirectories of logs and static resources.
  • Loading branch information
ijrsvt committed Sep 26, 2023
1 parent 7b1e636 commit 542ca64
Showing 1 changed file with 21 additions and 7 deletions.
28 changes: 21 additions & 7 deletions dashboard/http_server_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@
import errno
import ipaddress
import logging
from math import floor
import os
import pathlib
import sys
import time
from ray._private.utils import get_or_create_event_loop
from ray._private.usage.usage_lib import TagKey, record_extra_usage_tag
from math import floor

from packaging.version import Version

import ray.dashboard.optional_utils as dashboard_optional_utils
import ray.dashboard.utils as dashboard_utils

from ray._private.usage.usage_lib import TagKey, record_extra_usage_tag
from ray._private.utils import get_or_create_event_loop
from ray._raylet import GcsClient
from ray.dashboard.dashboard_metrics import DashboardPrometheusMetrics

# All third-party dependencies that are not included in the minimal Ray
# installation must be included in this file. This allows us to determine if
# the agent has the necessary dependencies to be started.
from ray.dashboard.optional_deps import aiohttp, hdrs
from ray._raylet import GcsClient


# Logger for this module. It should be configured at the entry point
# into the program using Ray. Ray provides a default configuration at
Expand Down Expand Up @@ -128,6 +127,20 @@ def get_address(self):
assert self.http_host and self.http_port
return self.http_host, self.http_port

@aiohttp.web.middleware
async def path_clean_middleware(self, request, handler):
if request.path.startswith("/static") or request.path.startswith("/logs"):
parent = pathlib.Path(
"/logs" if request.path.startswith("/logs") else "/static"
)

# If the destination is not relative to the expected directory,
# then the user is attempting path traversal, so deny the request.
request_path = pathlib.Path(request.path).resolve()
if request_path != parent and parent not in request_path.parents:
raise aiohttp.web.HTTPForbidden()
return await handler(request)

@aiohttp.web.middleware
async def metrics_middleware(self, request, handler):
start_time = time.monotonic()
Expand Down Expand Up @@ -166,7 +179,8 @@ async def run(self, modules):
# Http server should be initialized after all modules loaded.
# working_dir uploads for job submission can be up to 100MiB.
app = aiohttp.web.Application(
client_max_size=100 * 1024**2, middlewares=[self.metrics_middleware]
client_max_size=100 * 1024**2,
middlewares=[self.metrics_middleware, self.path_clean_middleware],
)
app.add_routes(routes=routes.bound_routes())

Expand Down

0 comments on commit 542ca64

Please sign in to comment.