|
29 | 29 | from pydantic import BaseModel, Field, ValidationError, computed_field, model_validator |
30 | 30 |
|
31 | 31 | APPID_PATTERN = re.compile(r"(?:^|/)exapps/([^/]+)") |
| 32 | +# Matches `..` path segments in any literal/encoded form: separators can be `/` or %2F, |
| 33 | +# the dots themselves can be `.` or %2E (any case combination, e.g. `..`, `%2e.`, `%2E%2e`). |
| 34 | +DOT_DOT_SEGMENT_PATTERN = re.compile(r"(?:^|/|%2[fF])(?:\.|%2[eE]){2}(?:/|%2[fF]|$)") |
32 | 35 | SHARED_KEY = os.environ.get("HP_SHARED_KEY") |
33 | 36 | NC_INSTANCE_URL = os.environ.get("NC_INSTANCE_URL") |
34 | 37 | SPOA_ADDRESS = os.environ.get("HP_SPOA_ADDRESS", "127.0.0.1:9600") |
@@ -395,6 +398,11 @@ async def exapps_msg( |
395 | 398 | LOGGER.warning("IP %s is banned due to excessive failed attempts.", client_ip_str) |
396 | 399 | return reply.set_txn_var("bad_request", 1) |
397 | 400 |
|
| 401 | + if DOT_DOT_SEGMENT_PATTERN.search(path): |
| 402 | + LOGGER.warning("Rejecting path with parent-directory segment: %s (client_ip=%s)", path, client_ip_str) |
| 403 | + await record_failure_unless_trusted(client_ip_str, request_headers) |
| 404 | + return reply.set_txn_var("bad_request", 1) |
| 405 | + |
398 | 406 | match = APPID_PATTERN.search(path) |
399 | 407 | if not match: |
400 | 408 | LOGGER.error("Invalid request path, cannot find AppID: %s", path) |
|
0 commit comments