mirror of
https://github.com/soxoj/maigret.git
synced 2026-08-17 19:25:41 +02:00
fix(web): sanitize username in report file paths to prevent path traversal (#2678)
* fix: sanitize username in report file paths to prevent path traversal (CWE-22) * address review: add unit tests for sanitize_username_for_path()
This commit is contained in:
+16
-5
@@ -99,6 +99,16 @@ async def search_multiple_usernames(usernames, options):
|
||||
return results
|
||||
|
||||
|
||||
def sanitize_username_for_path(username: str) -> str:
|
||||
"""Remove path separators and dangerous components from username for safe file path usage."""
|
||||
# Replace path separators and null bytes
|
||||
sanitized = username.replace('/', '_').replace('\\', '_').replace('\0', '_')
|
||||
# Remove . and .. components
|
||||
sanitized = sanitized.strip('.')
|
||||
# If empty after sanitization, use a fallback
|
||||
return sanitized or '_'
|
||||
|
||||
|
||||
def process_search_task(usernames, options, timestamp):
|
||||
try:
|
||||
loop = asyncio.new_event_loop()
|
||||
@@ -123,7 +133,8 @@ def process_search_task(usernames, options, timestamp):
|
||||
|
||||
individual_reports = []
|
||||
for username, id_type, results in general_results:
|
||||
report_base = os.path.join(session_folder, f"report_{username}")
|
||||
safe_username = sanitize_username_for_path(username)
|
||||
report_base = os.path.join(session_folder, f"report_{safe_username}")
|
||||
|
||||
csv_path = f"{report_base}.csv"
|
||||
json_path = f"{report_base}.json"
|
||||
@@ -162,16 +173,16 @@ def process_search_task(usernames, options, timestamp):
|
||||
{
|
||||
'username': username,
|
||||
'csv_file': os.path.join(
|
||||
f"search_{timestamp}", f"report_{username}.csv"
|
||||
f"search_{timestamp}", f"report_{safe_username}.csv"
|
||||
),
|
||||
'json_file': os.path.join(
|
||||
f"search_{timestamp}", f"report_{username}.json"
|
||||
f"search_{timestamp}", f"report_{safe_username}.json"
|
||||
),
|
||||
'pdf_file': os.path.join(
|
||||
f"search_{timestamp}", f"report_{username}.pdf"
|
||||
f"search_{timestamp}", f"report_{safe_username}.pdf"
|
||||
),
|
||||
'html_file': os.path.join(
|
||||
f"search_{timestamp}", f"report_{username}.html"
|
||||
f"search_{timestamp}", f"report_{safe_username}.html"
|
||||
),
|
||||
'claimed_profiles': claimed_profiles,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
|
||||
from maigret.web.app import sanitize_username_for_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_username, expected",
|
||||
[
|
||||
("../../tmp/x", "_.._tmp_x"),
|
||||
("..", "_"),
|
||||
("....", "_"),
|
||||
("foo/bar", "foo_bar"),
|
||||
("\0foo", "_foo"),
|
||||
("normaluser123", "normaluser123"),
|
||||
],
|
||||
)
|
||||
def test_sanitize_username_for_path(input_username, expected):
|
||||
result = sanitize_username_for_path(input_username)
|
||||
assert result == expected
|
||||
# Verify no path separators or null bytes remain
|
||||
assert "/" not in result
|
||||
assert "\\" not in result
|
||||
assert "\0" not in result
|
||||
# Verify result is not empty
|
||||
assert len(result) > 0
|
||||
# Verify no leading/trailing dots
|
||||
assert not result.startswith(".")
|
||||
assert not result.endswith(".")
|
||||
Reference in New Issue
Block a user