1717"""
1818import os
1919import bcrypt
20- import logging
20+ import re
2121from urllib .parse import unquote
2222from django .template .loader import get_template
2323from xhtml2pdf import pisa
4646)
4747from rest_framework .pagination import LimitOffsetPagination
4848
49- logger = logging .getLogger ()
50-
5149class SignUpView (APIView ):
5250 """
5351 Used to add a new mechanic
@@ -379,7 +377,6 @@ def get(self, request, user=None, service_request_id=None):
379377class 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
418418def 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