代码之家  ›  专栏  ›  技术社区  ›  Brad Parks

Java和XSS:HTML如何逃脱JSON字符串来保护XSS?

  •  5
  • Brad Parks  · 技术社区  · 6 年前

    // 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攻击。

    我读过 OWASP XSS Guide

    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";
        }
    }
    
    2 回复  |  直到 6 年前
        1
  •  4
  •   Paul Benn    6 年前

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

    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;"
    }
    
        2
  •  1
  •   Brad Parks    6 年前

    Paul Benn's answer Encode.forHtmlContent

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

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