项目安全问题及解决方法-----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:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/371491.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ReactNative实现文本渐变

我们直接上图&#xff0c;可以看到上面文本的效果&#xff0c;使用SVG实现 1.首先还是要引入react-native-svg库 2.使用该库下面的LinearGradient和Text 好&#xff0c;话不多说&#xff0c;我们看具体代码 <Svg width{422} height{30} viewBox{0 0 422 30}><Defs&…

力扣 第 383 场周赛 解题报告 | 珂学家 | Z函数/StringHash

前言 谁言别后终无悔 寒月清宵绮梦回 深知身在情长在 前尘不共彩云飞 整体评价 T3是道模拟题&#xff0c;但是感觉题意有些晦涩&#xff0c;T4一眼Z函数&#xff0c;当然StringHash更通用些。 新年快乐, _. T1. 将单词恢复初始状态所需的最短时间 I 思路: 模拟 就是前缀和为…

构建高效直播美颜系统:美颜SDK集成与性能优化指南

如今&#xff0c;美颜技术的广泛应用成为各类直播平台的标配之一。今天&#xff0c;小编将与大家进一步讨论如何构建高效的直播美颜系统&#xff0c;重点关注美颜SDK的集成和性能优化方面。 一、美颜SDK的选择与集成 选择合适的美颜SDK是构建高效直播美颜系统的第一步。不同的…

速过计算机二级python——第六讲:文件操作

第六讲:文件操作 文件夹创建文件夹移动文件夹复制文件夹删除文件夹文件操作文件读取文件写入文件文件夹 创建文件夹 定义创建文件夹函数:chmk_path()定义一个函数 chmk_path(),这个函数的功能是创建文件夹。 首先需要导入操作系统接口模块——os 模块,这个模块中包含某些函…

基于单片机控制的智能门锁设计

摘要&#xff1a;阐述基于STC15F2K60S2单片机控制的智能门锁设计&#xff0c;包括CPU控制单元模块、液晶显示LCD、 Wi-Fi模块&#xff0c;实现远程控制开门&#xff0c;密码开门的智能化功能。 关键词&#xff1a;控制技术&#xff0c;单片机&#xff0c;智能门锁&#xff0c;…

cesium-测量高度垂直距离

cesium做垂直测量 完整代码 <template><div id"cesiumContainer" style"height: 100vh;"></div><div id"toolbar" style"position: fixed;top:20px;left:220px;"><el-breadcrumb><el-breadcrumb-i…

WebChat——一个开源的聊天应用

Web Chat 是开源的聊天系统&#xff0c;支持一键免费部署私人Chat网页的应用程序。 开源地址&#xff1a;https://github.com/loks666/webchat 目录树 TOC &#x1f44b;&#x1f3fb; 开始使用 & 交流&#x1f6f3; 开箱即用 A 使用 Docker 部署B 使用 Docker-compose…

C++ 之LeetCode刷题记录(二十八)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅。 目标&#xff1a;执行用时击败90%以上使用 C 的用户。 144. 二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍…

JAVA Web 学习(五)Nginx、RPC、JWT

十二、反向代理服务器——Nginx 支持热部署&#xff0c;几乎可以做到 7 * 24 小时不间断运行&#xff0c;即使运行几个月也不需要重新启动&#xff0c;还能在不间断服务的情况下对软件版本进行热更新。性能是 Nginx 最重要的考量&#xff0c;其占用内存少、并发能力强、能支持…

docker下nacos(1.2.0)的持久化

一、创建数据库 运行以下代码自动创建数据库和表 CREATE DATABASE IF NOT EXISTS nacos_config /*!40100 DEFAULT CHARACTER SET utf8 */; USE nacos_config;SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for config_…

【LeetCode】刷题总结 - 15. 三数之和

15. 三数之和 | LeetCode 思路 首先对 nums 进行排序&#xff0c;然后设置三个指针&#xff1a;left、mid、right&#xff1a; left 从最左边开始&#xff0c;依次向后遍历每次固定住 left 后&#xff0c;就化为一个 2sum 问题&#xff1a; mid left 1&#xff0c;right …

go-redis hash slot 之旅

搭建redis 集群 创建一个网桥 docker network create -d bridge --subnet192.168.148.0/24 --gateway192.168.148.1 -o parenteno1 redis-net通过docker 文件创建redis 集群&#xff0c; 这里注意要不要使用redis 7以上的版本&#xff0c;不然会出问题 version: "3&quo…

详解spring6.0新特性汇总

spring6新特性汇总 part1 spring6.0新特性 spring6.0 2022年11月。新一代框架带jdk17&jakarta ee9 https://www.graalvm.org/ part2 AOP&事务 1.AOP:面向切面编程 通过预编译方式和运行期动态 代理实现程序功能的统一维护的一种技术。 使用场景&#xff1a; 权…

vivado 手动设置自下而上的流量并导入网表、创建较低级别的网表

手动设置自下而上的流量并导入网表 要手动运行自下而上的流&#xff0c;请将较低级别的网表或第三方网表实例化为黑色盒子&#xff0c;Vivado工具在合成完成后将黑盒子融入完整的设计中。这个以下部分描述了该过程。 重要&#xff01;Vivado合成不合成或优化加密或非加密合成…

信任与创新 | 回顾通付盾的2023!

-END- 数信云&#xff0c;基于区块链与人工智能的数据安全应用与服务平台

ReactNative实现弧形拖动条

我们直接看效果 先看下面的使用代码 <CircularSlider5step{2}min{0}max{100}radius{100}value{30}onComplete{(changeValue: number) > this.handleEmailSbp(changeValue)}onChange{(changeValue: number) > this.handleEmailDpd(changeValue)}contentContainerStyle{…

【自动化测试】----Java的单元测试工具Junit5

目录 支持Java的最低版本为8在pom.xml添加依赖Junit提供的注解功能 断言 Assertion类提供的一些方法测试用例执行顺序 &#xff08;为了预防测试用例执行顺序错误&#xff09;参数化 &#xff08;假设登陆操作&#xff0c;用户名和密码很多&#xff0c;尽可能通过一个测试用例…

springboot+vue实现excel导出

后端 导入pom依赖 <dependency>x<groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.2.0</version> </dependency> Entity实体类 这里以User为例&#xff0c;可按照自己实际…

【leetcode题解C++】450.删除二叉搜索树中的节点 and 669.修剪二叉搜索树 and 108.将有序数组转换为二叉搜索树

450. 删除二叉搜索树中的节点 给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key 对应的节点&#xff0c;并保证二叉搜索树的性质不变。返回二叉搜索树&#xff08;有可能被更新&#xff09;的根节点的引用。 一般来说&#xff0c;删除节点可…

032-安全开发-JavaEE应用Servlet路由技术JDBCMybatis数据库生命周期

032-安全开发-JavaEE应用&Servlet路由技术&JDBC&Mybatis数据库&生命周期 #知识点&#xff1a; 1、JavaEE-HTTP-Servlet技术 2、JavaEE-数据库-JDBC&Mybatis 演示案例&#xff1a; ➢JavaEE-HTTP-Servlet&路由&周期 ➢JavaEE-数据库-JDBC&Mybat…