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

时间:2018-06-14 12:31:32

标签: java json jackson xss

在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);

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

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

我已经阅读了OWASP XSS Guide,到目前为止我提出的最好的是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";
    }
}

4 个答案:

答案 0 :(得分:4)

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

根据上面的评论,我使用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;
    }
}

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 { // 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;
    }
}

示例输入(两个库):

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

示例输出(两个库):

{
    "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;"
}

答案 1 :(得分:1)

我认为Paul Benn's answer是最好的方法,但是如果你不想迭代json节点,你可以考虑使用Encode.forHtmlContent,它不会逃避引号。我觉得这可能是安全的,因为我无法想到如何在引用的字符串中引入额外的引用可能会导致漏洞利用。我会留给读者审阅文档并自行决定!

的ivy.xml

<dependency org="org.owasp.encoder" name="encoder" rev="1.2.1"/>

和一些代码来做html编码

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

答案 2 :(得分:1)

更新Gson版本的Paul Benn's答案,以将json值包含为数组

PhotoshopApp = Activator.CreateInstance(Type.GetTypeFromProgID("Photoshop.Application"))

答案 3 :(得分:1)

使用Jackson和Esapi添加JKRo版本。

private JsonNode clean(JsonNode node, ObjectMapper mapper) {
    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(ESAPI.encoder().encodeForHTML(node.asText()));
        } else {
            return node;
        }
    } else if(node.isArray()) { // If the object is an array  "cars": ["toyota", "nissan", "bmw"]
        ArrayNode cleanedNewArray = mapper.createArrayNode();
        for (final JsonNode objNode : node) {
            cleanedNewArray.add(clean(objNode, mapper));
        }
        return cleanedNewArray;
    } 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(ESAPI.encoder().encodeForHTML(entry.getKey()), clean(entry.getValue(), mapper));
        }
        return clean;
    }
}

请求正文:

{
"param1": "<input class='btn btn-default' value='0' placeholder='Ingrese sus datos'></input>",
"param3": [
{
    "nombre" : "<input class='btn btn-default' value='0' placeholder='Ingrese sus datos'></input>",
    "apellido": "<script>alert('Hola mundex');</script>"
},
{
    "param4": {
        "nombre" : "<input class='btn btn-default' value='0' placeholder='Ingrese sus datos'></input>",
        "apellido": "<script>alert('Hola mundex');</script>"
    }
}],
"param2": "alert('Hola')"

}

响应正文:

{
"param1": "&lt;input class&#x3d;&#x27;btn btn-default&#x27; value&#x3d;&#x27;0&#x27; placeholder&#x3d;&#x27;Ingrese sus datos&#x27;&gt;&lt;&#x2f;input&gt;",
"param3": [
    {
        "nombre": "&lt;input class&#x3d;&#x27;btn btn-default&#x27; value&#x3d;&#x27;0&#x27; placeholder&#x3d;&#x27;Ingrese sus datos&#x27;&gt;&lt;&#x2f;input&gt;",
        "apellido": "&lt;script&gt;alert&#x28;&#x27;Hola mundex&#x27;&#x29;&#x3b;&lt;&#x2f;script&gt;"
    },
    {
        "param4": {
            "nombre": "&lt;input class&#x3d;&#x27;btn btn-default&#x27; value&#x3d;&#x27;0&#x27; placeholder&#x3d;&#x27;Ingrese sus datos&#x27;&gt;&lt;&#x2f;input&gt;",
            "apellido": "&lt;script&gt;alert&#x28;&#x27;Hola mundex&#x27;&#x29;&#x3b;&lt;&#x2f;script&gt;"
        }
    }
],
"param2": "alert&#x28;&#x27;Hola&#x27;&#x29;"

}