Saltar al contenido

Java y XSS: cómo escapar html de un JSON string para proteger contra XSS?

Abraham, miembro de nuestro equipo, nos hizo el favor de crear este post ya que controla a la perfección dicho tema.

Solución:

Un posible enfoque podría ser iterar sobre las entradas del objeto y escapar individualmente de cada key y valor una vez que la biblioteca elegida construya el nodo.

Siguiendo mi comentario anterior, implementé una solución recursiva simple usando Jackson (de su pregunta) y GSON, una biblioteca diferente donde los objetos son un poco más fáciles de construir y el código es más legible. El mecanismo de escape utilizado es el codificador Java OWASP:

jackson

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> it = node.fields(); it.hasNext(); ) 
            Map.Entry 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 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 entry :  obj.entrySet()) 
            // Encode the key right away and encode the value recursively
            clean.add(Encode.forHtml(entry.getKey()), clean(entry.getValue()));
        
        return clean;
    

Entrada de muestra (ambas bibliotecas):


    "nested": 
        "": ""
    ,
    "xss": ""

Salida de muestra (ambas bibliotecas):


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

Creo que la respuesta de Paul Benn es el mejor enfoque en general, pero si no desea iterar sobre los nodos json, podría considerar usar Encode.forHtmlContent, que no se escapa de las comillas. Siento que esto es probablemente seguro ya que no puedo pensar en cómo introducir una cita adicional en una cotización string podría causar un exploit. ¡Dejaré que el lector revise los documentos y decida por sí mismo!

hiedra.xml


y algo de código para hacer la codificación html

private String objectToJson(Object value)

    String result;
    try
    
        result = jsonWriter.writeValueAsString(value);
        return Encode.forHtmlContent(result);
    
    catch (JsonProcessingException e)
    
        return "null";
    

Aquí tienes las reseñas y valoraciones

Si te sientes a gusto, eres capaz de dejar una sección acerca de qué le añadirías a esta crónica.

¡Haz clic para puntuar esta entrada!
(Votos: 2 Promedio: 3.5)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *