javascripthtmlhttpanchor

How to avoid XSS issues when clicking HTML link to retrieve file from database and display it in new tab?


In the success callback of an AJAX request, I need to append a link to a modal. The link's href attribute points to an API route, which calls a Spring Boot controller method that writes the PDF file stored in the back-end to a Response object. The user can click on the link to open a new tab that displays the PDF file.

JS:

success: function (data) {    
  var fileId = data.id
  $('#modalBody').append(`<a id="viewFile" target="_blank" href="../api/getFile?fileId=${fileId}">View File</a>`)
}

Controller:

    @RequestMapping(value = "api/getFile", method = RequestMethod.GET)
    public void getFile(HttpServletResponse response, @RequestParam(value = "fileId") String fileId) {

        Response fileResponse = fileService.getInputStream(fileId);
        if (fileResponse == null) {
            response.setStatus(500);
            return;
        }
    
        InputStream is = response.body().byteStream();
        response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=sampleFile.pdf");
        response.setContentType("application/pdf");
        
        IOUtils.copy(is, response.getOutputStream);
        response.setStatus(202);
        response.flushBuffer();
        is.close();
        fileResponse.close();
    }

The problem is the vulnerability scanning tool flags the href as an XSS issue because I am passing in unvalidated data as query parameters to the front-end. Is there another way to open the file retrieved from the back-end when clicking on an HTML link?

I tried using a click event handler and setting href to a default route, but it just opens the current page instead of the file in the new tab. Does anyone have suggestions on how to avoid passing query params to the href route and how to use onclick instead of href?


Solution

  • This vulnerability means you should not echo HTML without escaping it first.

    If you were using EJS or some other template engine, it usually escapes the HTML for you. I use this function, esc_attr:

    function esc_attr(string) {
      if (!string) {
        return "";
      }
      return ("" + string).replace(/[&<>"'\/\\]/g, function(s) {
        return {
          "&": "&amp;",
          "<": "&lt;",
          ">": "&gt;",
          '"': '&quot;',
          "'": '&#39;',
          "/": '&#47;',
          "\\": '&#92;'
        }[s];
      });
    }
    
    // usage:
    div.innerHTML = esc_attr("<scr"+"ipt>alert(13)</scr"+"ipt>");
    <div id="div"></div>

    Or in your case,

    $('#modalBody').append(`<a id="viewFile" target="_blank" href="../api/getFile?fileId=${esc_attr(fileId)}">View File</a>`)
    

    Actually, since you are using it inside a URL, you should take it a step further and encode it for URL as well using encodeURIComponent.

    I think this is the best combination:

    $('#modalBody').append(`<a id="viewFile" target="_blank" href="../api/getFile?fileId=${esc_attr(encodeURIComponent(fileId))}">View File</a>`)