项目安全问题及解决方法-----xss处理

XSS 问题的根源在于,原本是让用户传入或输入正常数据的地方,被黑客替换为了 JavaScript 脚本,页面没有经过转义直接显示了这个数据,然后脚本就被 执行了。更严重的是,脚本没有经过转义就保存到了数据库中,随后页面加载数据的时候,数据中混入的脚本又当做代码执行了。黑客可以利用这个漏洞 来盗取敏感数据,诱骗用户访问钓鱼网站等。


@RequestMapping("xss")
@Slf4j
@Controller
public class XssController {
    @Autowired
    private UserRepository userRepository;
    //显示xss页面
    @GetMapping
    public String index(ModelMap modelMap) {
        //查数据库
        User user = userRepository.findById(1L).orElse(new User());
        //给View提供Model
        modelMap.addAttribute("username", user.getName());
        return "xss";
    }
    //保存用户信息
    @PostMapping
    public String save(@RequestParam("username") String username, HttpServletRequest request) {
        User user = new User();
        user.setId(1L);
        user.setName(username);
        userRepository.save(user);
        //保存完成后重定向到首页
        return "redirect:/xss/";
    }
}
//用户类,同时作为DTO和Entity
@Entity
@Data
public class User {
    @Id
    private Long id;
    private String name;
}

使用Thymeleaf 模板引擎来渲染页面

<div style="font-size: 14px">
 <form id="myForm" method="post" th:action="@{/xss/}">
 <label th:utext="${username}"/> <!--对于 Thymeleaf 模板引擎,需要注意的是,使用 th:utext 来显示数据是不会进行转义的,需要使用 th:text-->
 <input id="username" name="username" size="100" type="text"/>
 <button th:text="Register" type="submit"/>
 </form>
</div>

 

解决方法可以使用 HTML 转码。既然是通过 @RequestParam 来获取请求参数,那我们定义一个 @InitBinder 实现数据绑定的时候,对字符串进行转码即 可。

@ControllerAdvice
public class SecurityAdvice {
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        //注册自定义的绑定器
        binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
            @Override
            public String getAsText() {
                Object value = getValue();
                return value != null ? value.toString() : "";
            }
            @Override
            public void setAsText(String text) {
                //赋值时进行HTML转义
                setValue(text == null ? null : HtmlUtils.htmlEscape(text));
            }
        });
    }
}

 

但是解决问题的方式不全面,@InitBinder 是 Spring Web 层面的处理逻辑,如果有代码不通过 @RequestParam 来获取数据,而是直接从 HTTP 请求 获取数据的话,这种方式就不会奏效。比如: user.setName(request.getParameter("username")); 最好的解决方式是,定义一个 servlet Filter,通过 HttpServletRequestWrapper 实现 servlet 层面的统一参数替换。

//自定义过滤器
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class XssFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletExceptio n {
        chain.doFilter(new XssRequestWrapper((HttpServletRequest) request), response);
    }
}
public class XssRequestWrapper extends HttpServletRequestWrapper {
    public XssRequestWrapper(HttpServletRequest request) {
        super(request);
    }
    @Override
    public String[] getParameterValues(String parameter) {
        //获取多个参数值的时候对所有参数值应用clean方法逐一清洁
        return Arrays.stream(super.getParameterValues(parameter)).map(this::clean).toArray(String[]::new);
    }
    @Override
    public String getHeader(String name) {
        //同样清洁请求头
        return clean(super.getHeader(name));
    }
    @Override
    public String getParameter(String parameter) {
        //获取参数单一值也要处理
        return clean(super.getParameter(parameter));
    }
    //clean方法就是对值进行HTML转义
    private String clean(String value) {
        return StringUtils.isEmpty(value)? "" : HtmlUtils.htmlEscape(value);
    }
 
} 

这种方式还是不够彻底,原因是无法处理通过 @RequestBody 注解提交的 JSON 数据。比如,有这样一个 PUT 接口,直接保存了客户端传入的 JSON User 对 象

@PutMapping
public void put(@RequestBody User user) {
 userRepository.save(user);
}

 

因此我们需要自定义一个json的反序列器进行处理:

    //注册自定义的Jackson反序列器
    @Bean
    public Module xssModule() {
        SimpleModule module = new SimpleModule();
        module.module.addDeserializer(String.class, new XssJsonDeserializer());
        return module;
    }
public class XssJsonDeserializer extends JsonDeserializer<String> {
    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String value = jsonParser.getValueAsString();
        if (value != null) {
            //对于值进行HTML转义
            return HtmlUtils.htmlEscape(value);
        }
        return value;
    }
    @Override
    public Class<String> handledType() {
        return String.class;
    }
}

 这样就实现了既能转义 Get/Post 通过请求参数提交的数据,又能转义请求体中直接提交的 JSON 数据。但是目前这种只能堵新漏,确保新数据进入数据 库之前转义。如果因为之前的漏洞,数据库中已经保存了一些 JavaScript 代码,那么读取的时候同样可能出问题。因此,我们还要实现数据读取的时候也 转义。

@GetMapping("user")
@ResponseBody
public User query() {
 return userRepository.findById(1L).orElse(new User());
}

 

修改之前的 SimpleModule 加入自定义序列化器,并且实现序列化时处理字符串转义

    //注册自定义的Jackson序列器
    @Bean
    public Module xssModule() {
        SimpleModule module = new SimpleModule();
        module.addDeserializer(String.class, new XssJsonDeserializer());
        module.addSerializer(String.class, new XssJsonSerializer());
        return module;
    }
public class XssJsonSerializer extends JsonSerializer<String> {
    @Override
    public Class<String> handledType() {
        return String.class;
    }
    @Override
    public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if (value != null) {
            //对字符串进行HTML转义
            jsonGenerator.writeString(HtmlUtils.htmlEscape(value));
        }
    }
}

 

还要考虑一种情况:如果需要在 Cookie 中写入敏感信息的话,我们可以开启 HttpOnly 属性。这样 JavaScript 代码就无法读取 Cookie 了,即便页面被 XSS 注 入了攻击代码,也无法获得我们的 Cookie。

//服务端读取Cookie
@GetMapping("readCookie")
@ResponseBody
public String readCookie(@CookieValue("test") String cookieValue) {
 return cookieValue;
}
//服务端写入Cookie
@GetMapping("writeCookie")
@ResponseBody
public void writeCookie(@RequestParam("httpOnly") boolean httpOnly, HttpServletResponse response) {
 Cookie cookie = new Cookie("test", "zhuye");
 //根据httpOnly入参决定是否开启HttpOnly属性
 cookie.setHttpOnly(httpOnly);
 response.addCookie(cookie);
}

 由于 test 和 _ga 这两个 Cookie 不是 HttpOnly 的。通过 document.cookie 可以输出这两个 Cookie 的内容:

为 test 这个 Cookie 启用了 HttpOnly 属性后,就不能被 document.cookie 读取到了,输出中只有 _ga 一项:

 

 但是服务端可以读取到这个 cookie:

相关推荐

  1. XSS 简述解决

    2024-02-03 07:40:02       20 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-03 07:40:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-03 07:40:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-03 07:40:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-03 07:40:02       20 阅读

热门阅读

  1. 1z_php

    1z_php

    2024-02-03 07:40:02      26 阅读
  2. day40_mysql

    2024-02-03 07:40:02       26 阅读
  3. docker常用命令

    2024-02-03 07:40:02       31 阅读
  4. 数据分析之数据预处理、分析建模、可视化

    2024-02-03 07:40:02       32 阅读
  5. 第8章 SpringBoot任务管理

    2024-02-03 07:40:02       33 阅读
  6. 设计模式之-适配器模式

    2024-02-03 07:40:02       38 阅读
  7. 适配器模式

    2024-02-03 07:40:02       30 阅读
  8. 前端面试题-vue-MVC和MVVM-VUE常见指令

    2024-02-03 07:40:02       32 阅读
  9. 02-OpenFeign-微服务接入

    2024-02-03 07:40:02       33 阅读
  10. k8s helm安装Tiller出错解决

    2024-02-03 07:40:02       33 阅读
  11. PyTorch和TensorFlow的简介

    2024-02-03 07:40:02       31 阅读