Jackson XSS filtering the returned data

Jackson XSS filtering the returned data

Define a class that inherits ObjectMapper

package demo;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.web.util.HtmlUtils;

import java.io.IOException;

public class CustomObjectMapper extends ObjectMapper {
    private static final long serialVersionUID = -3448961813323784217L;

    public CustomObjectMapper() {
        SimpleModule module = new SimpleModule("HTML XSS Serializer",
                new Version(1, 0, 0, "FINAL","com.yihaomen","ep-jsonmodule"));
        module.addSerializer(new JsonHtmlXssSerializer(String.class));
        this.registerModule(module);
    }

    class JsonHtmlXssSerializer extends JsonSerializer<String> {

        public JsonHtmlXssSerializer(Class<String> string) {
            super();
        }

        @Override
        public Class<String> handledType() {
            return String.class;
        }

        @Override
        public void serialize(String value, JsonGenerator jsonGenerator,
                              SerializerProvider serializerProvider) throws IOException,
                JsonProcessingException {
            if (value != null) {
                String encodedValue = HtmlUtils.htmlEscape(value.toString());
                jsonGenerator.writeString(encodedValue);
            }
        }
    }
}  

A Spring configuration file

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean
            class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json;charset=UTF-8</value>
                    <value>text/html;charset=UTF-8</value>
                </list>
            </property>
            <property name="objectMapper">
                <bean class="demo.CustomObjectMapper" />
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

Add test Controller, verify the configuration above is successful

@RequestMapping("/testXSS")
@ResponseBody
public UserDTO testXSS() {
    UserDTO userDTO = new UserDTO();
    userDTO.setFullName("<script>alert(1)</script>");
    return userDTO;
}

Postman with test interface can be seen that the data field has been fullName html coding filtered

The above is for return data xss jackson filtering configuration, the following is a method achieved by my own java code

package demo;

import org.apache.commons.lang.StringUtils;
import org.springframework.web.util.HtmlUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author zhangkun
 * @date 2020/3/10 11:34
 */
public class XssUtil {
    public static void xssObject(Object obj) {
        // 获取返回对象类所有字段
        List<Field> fields = new ArrayList<>(Arrays.asList(obj.getClass().getDeclaredFields()));
        // 获取返回对象的父类的所有字段,(如果有的话,根据自己情况删减)
        List<Field> parentFields = new ArrayList<>(Arrays.asList(obj.getClass().getSuperclass().getDeclaredFields()));
        // 合并父类字段和本类字段(如果有的话,根据自己情况删减)
        fields.addAll(parentFields);

        // 维护一个以转义的名单列表,避免同一字段重复被转义,因为父类和子类可能存在相同的字段名(没有父类的情况可删减)
        List<String> nameList = new ArrayList<>();
        
        for (Field field : fields) { //遍历所有属性
            String type = field.getGenericType().getTypeName();
            String name = field.getName(); //获取属性的名字
            if (type.equals(String.class.getTypeName()) && !nameList.contains(name)) {
                // 如果使用到了这个字段,则添加到名单中,以免下次重复被使用
                nameList.add(name);
                
                // 根据javaBean规范,set或get方法字段的首字母大写
                name = name.substring(0, 1).toUpperCase() + name.substring(1);

                Method getMethod = null;
                Method setMethod = null;
                try {
                    // 获取本类的get、set方法
                    getMethod = obj.getClass().getMethod("get" + name);
                    setMethod = obj.getClass().getMethod("set" + name, new Class[]{String.class});
                } catch (NoSuchMethodException e) {
                    try {
                        // 如果没有获取到则去父类获取(如果有的话,根据自己情况删减)
                        getMethod = obj.getClass().getSuperclass().getMethod("get" + name);
                        setMethod = obj.getClass().getSuperclass().getMethod("set" + name, new Class[]{String.class});
                    } catch (NoSuchMethodException e1) {
                    }
                }
                
                if (getMethod != null && setMethod != null) {
                    String value = null;
                    try { 
                        // 通过get方法获取到值
                        String value = (String) getMethod.invoke(obj);
                        if (StringUtils.isBlank(value)) {
                            continue;
                        }
                        // 对字段进行html编码,然后通过set方法再塞回去
                        String s = HtmlUtils.htmlEscape(value);
                        setMethod.invoke(obj, s);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

Test Controller

@RequestMapping("/testXSS")
@ResponseBody
public UserDTO testXSS() {
    UserDTO userDTO = new UserDTO();
    userDTO.setFullName("<script>alert(\"佛山馆所属管'\")</script>");
    // 使用我自己写的XSS过滤工具类
    XssUtil.xssObject(userDTO);
    return userDTO;
}

Postman test returns results

We're done!

Guess you like

Origin www.cnblogs.com/dagger9527/p/12464029.html