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

huangapple go评论111阅读模式
英文:

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

问题

我遇到了以下错误。在使用ResponseWriter时。我该如何解决它?目标是解决Sonarqube错误,但是我下面提出的解决方案,使用Sonar文档并不起作用。

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("无法获取更新密码错误响应主体", ex);
	}
}

错误:

这种使用java/io/PrintWriter.write(Ljava/lang/String;)V的方式可能容易受到XSS攻击

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

我的输出是JSON,HTMLEscape和OWASP会创建糟糕的格式,但我需要它。

{ 
    "message": "这里是测试消息"
}

OWASP输出:

{"message":"这里是测试消息 (例如 !@#$)."}

SonarQube规则:

我尝试了SonarQube的官方解决方案,仍然出现错误。SonarQube规则链接

英文:

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

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

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/

答案1

得分: 3

以下是已翻译的代码部分:

response.setContentType(MediaType.TEXT_PLAIN);
writer.print(e.getResponseBodyAsString());
response.setContentType(MediaType.APPLICATION_JSON);
String jsonEscaped = new ObjectMapper().writeValueAsString(e.getResponseBodyAsString());
writer.print(jsonEscaped);
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>
import com.fasterxml.jackson.databind.ObjectMapper;
...
try {
    unifiedResponse = testController.updateData(testRequest);
} catch (RestClientResponseException e) {
    try {
        PrintWriter writer = response.getWriter();
        response.setContentType(MediaType.APPLICATION_JSON);

        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);
    }
}
英文:

You have:

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

That looks dangerous, especially if the response is the string &quot;message&quot;: &quot;&lt;script&gt;alert(&#39;Hacked!&#39;);&lt;/script&gt;&quot;!

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 &lt;dependencies&gt; section:

&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
    &lt;version&gt;2.15.2&lt;/version&gt;
&lt;/dependency&gt;

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(&quot;Error occurred&quot;, e);
        
        // Send a generic error message or a sanitized version of the actual error message.
        String sanitizedOutput = new ObjectMapper().writeValueAsString(Collections.singletonMap(&quot;error&quot;, &quot;An error occurred.&quot;));
        writer.print(sanitizedOutput);

    } catch (IOException ex) {
        throw new ServiceException(&quot;Cannot get update Password error response body&quot;, ex);
    }
}

答案2

得分: 1

XSS漏洞通常意味着请求中的数据与响应之间存在未断开的连接。通常,最简单的解决方案就是在来自请求的数据上使用类似StringEscapeUtils.escapeHtml4的库。这将使<script>标记(所有XSS攻击的真正根本)转换为&lt;script%gt;,这将在屏幕上呈现为<script>,但在浏览器中不会执行为脚本。希望这有意义。

英文:

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 <script> tag (the real root of all XSS attacks) would be converted to &lt;script%gt; which would render on the screen as <script> but would not execute in the browser as script. Hopefully that makes sense.

答案3

得分: 1

不久前,我写了一个答案,可能对您有所帮助:它与SonarQube错误无关,而是关于如何调整RestTemplate返回的错误消息。

思路是配置RestTemplate以使用自定义ResponseErrorHandler。从答案中适应而来:

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 {
  
  // 这个重载的方法版本仅适用于Spring 5.0以后的版本
  // 对于较早版本的库,您可以重写handleError(ClientHttpResponse response)方法
  @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);
    }
  }

  /**
   * 返回包含来自响应主体的详细错误消息:
   * &lt;pre&gt;
   * 404 Not Found: [{&#39;id&#39;: 123, &#39;message&#39;: &#39;actual mesage&#39;]
   * &lt;/pre&gt;
   *
   * 与&lt;code&gt;DefaultResponseErrorHandler&lt;/code&gt;不同,消息不会被截断。
   */
  private String getErrorMessage(
      int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset) {

    // 在此构建并返回您的自定义JSON错误消息
  }
}

然后,配置RestTemplate以使用此自定义的ResponseErrorHandler

RestTemplate restTemplate = new RestTemplate();
ResponseErrorHandler errorHandler = new CustomRestTemplateResponseErrorHandler();
restTemplate.setErrorHandler(errorHandler);
// 正常使用您的RestTemplate

请如前述答案中所述,考虑阅读此相关SO问题以及相关博客文章。

使用这种方法,返回的错误应该会被正确格式化,而无需直接使用PrintWriter,Spring将负责以适当的方式将此消息返回给客户端,而不会出现任何SonarQube问题。

有时解决SonarQube规则验证错误可能是关键的:在这种特定用例中,如果您确信返回的信息是安全的,您可能可以忽略它。无论如何,为了避免任何潜在问题,确保在使用时,您的客户端使用实际框架提供的手段对返回的信息进行净化。

英文:

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:
   * &lt;pre&gt;
   * 404 Not Found: [{&#39;id&#39;: 123, &#39;message&#39;: &#39;actual mesage&#39;]
   * &lt;/pre&gt;
   *
   * In contrast to &lt;code&gt;DefaultResponseErrorHandler&lt;/code&gt;, 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.

huangapple
  • 本文由 发表于 2023年8月4日 05:24:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76831689.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定