Skip to content

Commit e7c2140

Browse files
committed
resolved comments: standard whitelist check
1 parent ef74d61 commit e7c2140

File tree

2 files changed

+13
-13
lines changed

2 files changed

+13
-13
lines changed

services/web/src/sagas/vehicleSaga.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ export function* getServiceReport(action: MyAction): Generator<any, void, any> {
377377
throw responseJSON;
378378
}
379379

380-
const filename = `report_${reportId}.pdf`;
380+
const filename = `report_${reportId}`;
381381
responseJSON.downloadUrl =
382382
APIService.WORKSHOP_SERVICE +
383383
requestURLS.DOWNLOAD_SERVICE_REPORT +

services/workshop/crapi/mechanic/views.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"""
1818
import os
1919
import bcrypt
20-
import logging
20+
import re
2121
from urllib.parse import unquote
2222
from django.template.loader import get_template
2323
from xhtml2pdf import pisa
@@ -46,8 +46,6 @@
4646
)
4747
from rest_framework.pagination import LimitOffsetPagination
4848

49-
logger = logging.getLogger()
50-
5149
class SignUpView(APIView):
5250
"""
5351
Used to add a new mechanic
@@ -379,7 +377,6 @@ def get(self, request, user=None, service_request_id=None):
379377
class DownloadReportView(APIView):
380378
"""
381379
A view to download a service report.
382-
This view contains an intentional LFI vulnerability.
383380
"""
384381
def get(self, request, format=None):
385382
filename_from_user = request.query_params.get('filename')
@@ -388,19 +385,15 @@ def get(self, request, format=None):
388385
{"message": "Parameter 'filename' is required."},
389386
status=status.HTTP_400_BAD_REQUEST
390387
)
391-
#Checks for directory traversal in plain as well as single URL-encoded form
392-
#Since Django automatically decodes URL-encoded parameters once
393-
if '../' in filename_from_user:
388+
#Checks if input before decoding contains only allowed characters
389+
if not validate_filename(filename_from_user):
394390
return Response(
395391
{"message": "Forbidden input."},
396392
status=status.HTTP_400_BAD_REQUEST
397393
)
394+
398395
filename_from_user = unquote(filename_from_user)
399-
#VULNERABLE: Double URL-encoded path can be used for exploit
400396
full_path = os.path.abspath(os.path.join(settings.BASE_DIR, "reports", filename_from_user))
401-
print(f"Attempting to serve file from: {full_path}")
402-
logger.info(f"Attempting to serve file from: {full_path}")
403-
404397
if os.path.exists(full_path) and os.path.isfile(full_path):
405398
return FileResponse(open(full_path, 'rb'))
406399
elif not os.path.exists(full_path):
@@ -414,14 +407,21 @@ def get(self, request, format=None):
414407
status=status.HTTP_403_FORBIDDEN
415408
)
416409

410+
def validate_filename(input: str) -> bool:
411+
"""
412+
Allowed: alphanumerics, _, :, %HH
413+
"""
414+
url_encoded_pattern = re.compile(r'^(?:[A-Za-z0-9:_]|%[0-9A-Fa-f]{2})*$')
415+
return bool(url_encoded_pattern.fullmatch(input))
416+
417417

418418
def service_report_pdf(response_data, report_id):
419419
"""
420420
Generates service report's PDF file from a template and saves it to the disk.
421421
"""
422422
reports_dir = os.path.join(settings.BASE_DIR, 'reports')
423423
os.makedirs(reports_dir, exist_ok=True)
424-
report_filepath = os.path.join(reports_dir, f"report_{report_id}.pdf")
424+
report_filepath = os.path.join(reports_dir, f"report_{report_id}")
425425

426426
template = get_template('service_report.html')
427427
html_string = template.render({'service': response_data})

0 commit comments

Comments
 (0)