Skip to content

Commit d2f668a

Browse files
committed
[Security] Mitigate the risk of path traversal attacks by adding a request validator that rejects requests containing a 'path' argument matching the unsafe path traversal pattern.
1 parent 3c2ba6b commit d2f668a

File tree

3 files changed

+65
-5
lines changed

3 files changed

+65
-5
lines changed

api/tests/validation/test_api_custom_validators.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
from marshmallow import ValidationError
33

4-
from api.validation.validators import size_not_exceeding
4+
from api.validation.validators import size_not_exceeding, is_safe_path
55

66

77
def test_size_not_exceeding():
@@ -15,4 +15,41 @@ def test_size_not_exceeding_failing():
1515
test_str_not_exceeding = 'a' * max_size # will produce "aaa...", max_size + 2
1616

1717
with pytest.raises(ValidationError):
18-
size_not_exceeding(test_str_not_exceeding, max_size)
18+
size_not_exceeding(test_str_not_exceeding, max_size)
19+
20+
21+
@pytest.mark.parametrize(
22+
"path, expected_result", [
23+
pytest.param(
24+
"/whatever_api_version/whatever_api_resource",
25+
True,
26+
id="safe api path absolute"
27+
),
28+
pytest.param(
29+
"whatever_api_version/whatever_api_resource",
30+
True,
31+
id="safe api path relative"
32+
),
33+
pytest.param(
34+
"/../whatever",
35+
False,
36+
id="unsafe path traversal 1"
37+
),
38+
pytest.param(
39+
"./../whatever",
40+
False,
41+
id="unsafe path traversal 2"
42+
),
43+
pytest.param(
44+
"/whatever/../whatever",
45+
False,
46+
id="unsafe path traversal 3"
47+
),
48+
pytest.param(
49+
"whatever/../whatever",
50+
False,
51+
id="unsafe path traversal 4"
52+
),
53+
])
54+
def test_is_safe_path(path: str, expected_result: bool):
55+
assert is_safe_path(path) == expected_result

api/validation/schemas.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from marshmallow import Schema, fields, validate, INCLUDE, validates_schema
22

33
from api.validation.validators import aws_region_validator, is_alphanumeric_with_hyphen, \
4-
valid_api_log_levels_predicate, size_not_exceeding
4+
valid_api_log_levels_predicate, size_not_exceeding, is_safe_path
55

66

77
class EC2ActionSchema(Schema):
@@ -119,7 +119,7 @@ class PriceEstimateSchema(Schema):
119119
PriceEstimate = PriceEstimateSchema(unknown=INCLUDE)
120120

121121
class PCProxyArgsSchema(Schema):
122-
path = fields.String(required=True, validate=validate.Length(max=512))
122+
path = fields.String(required=True, validate=validate.And(is_safe_path, validate.Length(max=512)))
123123

124124
PCProxyArgs = PCProxyArgsSchema(unknown=INCLUDE)
125125

api/validation/validators.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,27 @@ def size_not_exceeding(data, size):
3131
bytes_ = bytes(json.dumps(data), 'utf-8')
3232
byte_size = len(bytes_)
3333
if byte_size > size:
34-
raise ValidationError(f'Request body exceeded max size of {size} bytes')
34+
raise ValidationError(f'Request body exceeded max size of {size} bytes')
35+
36+
def is_safe_path(arg: str):
37+
"""
38+
Validates if a given path is safe from path traversal attacks.
39+
40+
This function checks for the presence of directory traversal patterns
41+
(../ or ..\) in the provided path string. These patterns are commonly
42+
used in path traversal attacks to access files outside the intended
43+
directory.
44+
45+
Args:
46+
arg (str): The path string to validate.
47+
Examples:
48+
- "/v3/clusters" (safe)
49+
- "v3/clusters" (safe)
50+
"/v3/../../stage/v3/clusters" (safe)
51+
- "../stage/v3/clusters" (unsafe)
52+
53+
Returns:
54+
bool: True if the path is safe (no traversal patterns)
55+
False if the path contains traversal patterns
56+
"""
57+
return not re.search(r'\.\.[\\/]', arg)

0 commit comments

Comments
 (0)