1

I am getting this error below. When using ResponseWriter. How can I resolve it? Goal is to resolve the Sonarqube error, however my proposed solution below using Sonar documentation isn't working.

try {
   unifiedResponse = testController.updateData(testRequest);
} catch (RestClientResponseException e) {
    try {
        PrintWriter writer = response.getWriter();
        response.setContentType(MediaType.TEXT_PLAIN);
        response.setStatus(e.getRawStatusCode());
        writer.print(e.getResponseBodyAsString());
    } catch (IOException ex) {
        throw new ServiceException("Cannot get update Password error response body", ex);
    }
}

Error:

This use of java/io/PrintWriter.write(Ljava/lang/String;)V could be vulnerable to XSS

enter image description here

My output is JSON, HTMLEscape and OWASP creates bad formatting, which I need.

{ 
    "message": "Here is a test message"
}

OWASP Output:

{"message":"Here is a test message (e.g. !@#$)."}

SonarQube Rules:

I tried SonarQube official solution and still giving error. https://rules.sonarsource.com/java/RSPEC-5131/

mattsmith5
  • 540
  • 4
  • 29
  • 67
  • There is a large quantity of information missing from your question e.g. your server version and whether it's configuration contains directives can be used against XSS ! Tomcat has security headers can be configured, https://docs.bmc.com/docs/security/basic-tomcat-security-configuration-recommendations-924057229.html Sonarqube also has protections too. https://cwe.mitre.org/data/definitions/79.html should be some help too... https://semgrep.dev/docs/cheat-sheets/java-jsp-xss/ XSS is more about preventing X domain analytical scripts attaching or entering by destroying chars. – Samuel Marchant Aug 12 '23 at 02:49
  • Probably the best solution would be to urlencode your Jason and place it in a hidden field in the document, then urldecode it with a .js file from the same folder the jsp comes from (Another Point , is the page dynamically created or pre made?). https://stackoverflow.com/questions/54490611/how-to-embed-json-in-html – Samuel Marchant Aug 12 '23 at 03:11
  • Sonarqube and .js (requires Node.js) https://docs.sonarsource.com/sonarqube/latest/analyzing-source-code/languages/javascript-typescript-css/ Example only Tomcat and .js control. https://stackoverflow.com/questions/23342948/tomcat-access-control-allow-origin-and-javascript – Samuel Marchant Aug 12 '23 at 03:18
  • And one more point, the writer output Content-type must match the the output, therefore, if it is a jsp and effectively the client is sent html, it is more text/html , the trouble arises having to know if JSON is present. Note Node.js and jQuery use JSON. https://stackoverflow.com/questions/1150381/what-is-the-meaning-of-sign-in-javascript#:~:text=The%20%24()%20is%20the%20shorthand,used%20in%20the%20jQuery%20Library.&text=In%20addition%20to%20the%20above,object%20and%20jQuery()%20function. – Samuel Marchant Aug 12 '23 at 03:37
  • The main trouble about servers and XSS is the standard policy on scripting that only allows an associate client script to be in the same folder or deeper same folder as the page XML or html, higher or aside folders are automatically illegitimate security with the call to the script. It seems wherever "printwriter" is getting input may be from an input output stream resource outside accepted boundaries more than its string coding contents. Another place banned may be from url parameters of requests. ( Extra inf only https://heynode.com/tutorial/readwrite-json-files-nodejs/ ) – Samuel Marchant Aug 12 '23 at 03:57
  • Of the printwriter, it's main problem is it is editable with append() so it poses risk with error outputs, request and response info in a context of XSS apart the fact wherever scripting could be sourced is unchecked e.g. request response or upload or vulnerable located file e.g. FTP folder e.getResponseBodyAsString() could contain scripting characters. – Samuel Marchant Aug 12 '23 at 07:32

3 Answers3

3

You have:

response.setContentType(MediaType.TEXT_PLAIN);
writer.print(e.getResponseBodyAsString());

That looks dangerous, especially if the response is the string "message": "<script>alert('Hacked!');</script>"!

Essentially, whenever you output data directly to a browser (in this case via PrintWriter), if that data contains malicious scripts from user input or any non-trusted sources, it can be executed on the client side, leading to Cross-Site Scripting (XSS) attacks.

If you know you want a JSON content, specify it explicitly:

response.setContentType(MediaType.APPLICATION_JSON);

And always escape the data before sending it as output. You mentioned that HTMLEscape causes bad formatting. That makes sense because you are working with JSON. Instead of HTML escaping, you should try and use JSON escaping, with, for instance, FasterXML/jackson. See Reading and Writing Using ObjectMapper from Eugen Baeldung.

String jsonEscaped = new ObjectMapper().writeValueAsString(e.getResponseBodyAsString());
writer.print(jsonEscaped);

With you pom.xml including in the <dependencies> section:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>

Your code would then would be:

import com.fasterxml.jackson.databind.ObjectMapper;
...
try {
    unifiedResponse = testController.updateData(testRequest);
} catch (RestClientResponseException e) {
    try {
        PrintWriter writer = response.getWriter();
        response.setContentType(MediaType.APPLICATION_JSON);

        // Avoid directly printing exception. Instead, you can log it.
        // log.error("Error occurred", e);
        
        // Send a generic error message or a sanitized version of the actual error message.
        String sanitizedOutput = new ObjectMapper().writeValueAsString(Collections.singletonMap("error", "An error occurred."));
        writer.print(sanitizedOutput);

    } catch (IOException ex) {
        throw new ServiceException("Cannot get update Password error response body", ex);
    }
}
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • my "text/plain" solution was found in https://rules.sonarsource.com/java/RSPEC-5131/ , did you try and test this in Sonar qube? I will check myself, and if sonar qube gives an error, I will probably disable the SonarRule for that code line,thanks – mattsmith5 Aug 06 '23 at 23:02
  • @mattsmith5 I did not test it directly, I was only addressing the "My output is JSON" from your question. If your output is JSON, then my answer should avoid your error. – VonC Aug 07 '23 at 06:40
  • thanks, yeah, my Goal is to resolve the Sonarqube error, however my proposed solution above using Sonar documentation isn't working. – mattsmith5 Aug 07 '23 at 06:54
  • @mattsmith5 Did my answer helped (in specifying JSON and using jackson)? – VonC Aug 07 '23 at 06:59
1

An XSS vulnerability usually means that there is an unbroken connection of data from the request back to the response. The easiest solution for this is usually just to use a library like StringEscapeUtils.escapeHtml4 on the data coming from the request. That would make it so that a tag (the real root of all XSS attacks) would be converted to <script%gt; which would render on the screen as but would not execute in the browser as script. Hopefully that makes sense.

Steve
  • 692
  • 1
  • 7
  • 18
  • do you have corresponding sample code in Java? trying to research – mattsmith5 Aug 03 '23 at 22:01
  • Sample code for Apache StringEscapeUtils? `String escaped = StringEscapeUtils.escapeHtml4(original);` – Steve Aug 03 '23 at 22:30
  • this won't format the data in clean form however, given the json – mattsmith5 Aug 05 '23 at 06:54
  • I understand that you are limited in your ability to include your actual code in discussing this. The core issue is probably in your implementation of getResponseBodyAsString. There is probably something in there which is coming directly from the client. Without knowing what is in that method, it's impossible to diagnose this fully ... but that is ALWAYS the way that an XSS happens. Somehow there is a path in which raw data from the client is echoed back to the client. That is the root bug you need to fix. You can remove the request data from the response, or you can escape it. – Steve Aug 10 '23 at 14:35
1

Some time ago I wrote an answer that could be of help: it is not related to the SonarQube error, but on how to adapt the error messages returned by RestTemplate.

The idea is configuring RestTemplate to use a custom ResponseErrorHandler. Adapted from the answer:

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.UnknownHttpStatusCodeException;

public class CustomRestTemplateResponseErrorHandler extends DefaultResponseErrorHandler {
  
  // This overloaded method version is only available since Spring 5.0
  // For previous versions of the library you can override
  // handleError(ClientHttpResponse response) instead
  @Override
  protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
    String statusText = response.getStatusText();
    HttpHeaders headers = response.getHeaders();
    byte[] body = getResponseBody(response);
    Charset charset = getCharset(response);
    String message = getErrorMessage(statusCode.value(), statusText, body, charset);

    switch (statusCode.series()) {
      case CLIENT_ERROR:
        throw HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset);
      case SERVER_ERROR:
        throw HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset);
      default:
        throw new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset);
    }
  }

  /**
   * Return error message with details from the response body:
   * <pre>
   * 404 Not Found: [{'id': 123, 'message': 'actual mesage']
   * </pre>
   *
   * In contrast to <code>DefaultResponseErrorHandler</code>, the message will not be truncated.
   */
  private String getErrorMessage(
      int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset) {

    // Build and return your custom json error message here
  }
}

Then, configure RestTemplate to use this custom ResponseErrorHandler:

RestTemplate restTemplate = new RestTemplate();
ResponseErrorHandler errorHandler = new CustomRestTemplateResponseErrorHandler();
restTemplate.setErrorHandler(errorHandler);
// use your RestTemplate normally

Please, as also stated in the mentioned answer, consider read this related SO question and this or this related blog posts.

Using this approach the returned error should be properly formatted without the need of using PrintWriter directly, Spring will take care of everything to return this message to the client in a proper way without any SonarQube issue.

Sometimes resolving a SonarQube rule validation error could be cornerstone: in this specific use case, if you are sure that the information returned is safe, you can probably ignoring it. In any case, to avoid any potential problem, be sure that your client sanitizes the returned information when you use it using the means provided by the actual framework you are using.

jccampanero
  • 50,989
  • 3
  • 20
  • 49