Java和XSS:如何html转义JSON字符串以防止XSS?

Bra*_*rks 5 java xss json jackson

在Java中,我们有一些代码可以获取复杂的java对象并将其序列化为json.然后它将json直接写入页面的标记,在脚本标记中,将其分配给变量.

// Get object as JSON using Jackson
ObjectWriter jsonWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = jsonWriter.writeValueAsString(complexObject);

// Write JSON out to page, and assign it to a javascript variable.
Writer out = environment.getOut();
out.write("var data = " + json);
Run Code Online (Sandbox Code Playgroud)

复杂对象可以包含最终用户内容,这可能会使我们受到XSS攻击.

如何获取每个json属性HTML转义的复杂java对象的json版本,以防止XSS注入?

我已经阅读了OWASP XSS指南,到目前为止我提出的最好的是HTML,它会转义整个JSON字符串,然后取消引号,因此可以将其分配给javascript中的变量.我确信有更好的方法可以做到这一点,但这似乎有效.有什么建议?

private String objectToHtmlEscapedJson(Object value) {
    try {
        String result = jsonWriter.writeValueAsString(value);
        result = StringEscapeUtils.escapeHtml(result);
        result = result.replace(""", "\"");
        return result;
    } catch (JsonProcessingException e) {
        return "null";
    }
}
Run Code Online (Sandbox Code Playgroud)

Pau*_*enn 11

一种可能的方法是遍历对象条目并在节点由您选择的库构建后单独转义每个键和值。

按照我上面的评论,我已经使用Jackson(来自您的问题)和GSON实现了一个简单的递归解决方案,这是一个不同的库,其中对象更容易构建并且代码更具可读性。使用的转义机制是OWASP Java Encoder

杰克逊

private static JsonNode clean(JsonNode node) {
    if(node.isValueNode()) { // Base case - we have a Number, Boolean or String
        if(JsonNodeType.STRING == node.getNodeType()) {
            // Escape all String values
            return JsonNodeFactory.instance.textNode(Encode.forHtml(node.asText()));
        } else {
            return node;
        }
    } else { // Recursive case - iterate over JSON object entries
        ObjectNode clean = JsonNodeFactory.instance.objectNode();
        for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) {
            Map.Entry<String, JsonNode> entry = it.next();
            // Encode the key right away and encode the value recursively
            clean.set(Encode.forHtml(entry.getKey()), clean(entry.getValue()));
        }
        return clean;
    }
}
Run Code Online (Sandbox Code Playgroud)

GSON

private static JsonElement clean(JsonElement elem) {
    if (elem.isJsonPrimitive()) { // Base case - we have a Number, Boolean or String
        JsonPrimitive primitive = elem.getAsJsonPrimitive();
        if(primitive.isString()) {
            // Escape all String values
            return new JsonPrimitive(Encode.forHtml(primitive.getAsString()));
        } else {
            return primitive;
        }
    } else if (elem.isJsonArray()) { // We have an array - GSON requires handling this separately
        JsonArray cleanArray = new JsonArray();
        for(JsonElement arrayElement: elem.getAsJsonArray()) {
            cleanArray.add(clean(arrayElement));
        }
        return cleanArray;
    } else { // Recursive case - iterate over JSON object entries
        JsonObject obj = elem.getAsJsonObject();
        JsonObject clean = new JsonObject();
        for(Map.Entry<String, JsonElement> entry :  obj.entrySet()) {
            // Encode the key right away and encode the value recursively
            clean.add(Encode.forHtml(entry.getKey()), clean(entry.getValue()));
        }
        return clean;
    }
}
Run Code Online (Sandbox Code Playgroud)

示例输入(两个库):

{
    "nested": {
        "<html>": "<script>(function(){alert('xss1')})();</script>"
    },
    "xss": "<script>(function(){alert('xss2')})();</script>"
}
Run Code Online (Sandbox Code Playgroud)

示例输出(两个库):

{
    "nested": {
        "&lt;html&gt;": "&lt;script&gt;(function(){alert(&#39;xss1&#39;)})();&lt;/script&gt;"
    },
    "xss": "&lt;script&gt;(function(){alert(&#39;xss2&#39;)})();&lt;/script&gt;"
}
Run Code Online (Sandbox Code Playgroud)


Bra*_*rks 5

我认为Paul Benn 的答案是总体上最好的方法,但是如果您不想遍历 json 节点,则可以考虑使用Encode.forHtmlContent,它不会转义引号。我觉得这可能是安全的,因为我想不出如何在带引号的字符串中引入额外的引号会导致漏洞利用。我将留给读者查看文档并自行决定!

常春藤.xml

<dependency org="org.owasp.encoder" name="encoder" rev="1.2.1"/>
Run Code Online (Sandbox Code Playgroud)

和一些代码来做 html 编码

private String objectToJson(Object value)
{
    String result;
    try
    {
        result = jsonWriter.writeValueAsString(value);
        return Encode.forHtmlContent(result);
    }
    catch (JsonProcessingException e)
    {
        return "null";
    }
}
Run Code Online (Sandbox Code Playgroud)