用户云卷云舒 发表于 2024-8-25 04:45:03

功能性的安全保障:输入校验

前言

在软件开发过程中,确保系统的安全性是至关重要的一环。它不仅关乎保护用户数据的完整性和隐私性,也是维护系统稳固运行的基石。我以为,从宏观角度审视,软件开发的安全性保障主要可分为两大类:功能性的安全性保障和系统性的安全性校验。
功能性的安全性保障专注于应用程序层面,它着眼于那些直接影响用户数据和交互过程安全的特性。这些特性是构建用户信托和保障数据安全的关键。
而系统性的安全性校验则放眼于更为广阔的视角,它涵盖了整个系统架构和网络层面,确保从服务器到网络的每一环节都具备足够的防御能力,以抵御各种潜在的攻击。
在之前 功能性的安全性保障:实现逼迫登录和密码加密功能 和 功能性的安全性保障:TOKEN鉴权校验 两文里。我们分别讨论了功能性的安全性保障中的几个核心议题:身份验证和密码加密、TOKEN鉴权校验和认证。在本文中,我们仍旧继续功能性的安全性保障这个话题,通过对概念的细致解读、实施策略的全面分析,以及现实代码实现的展示,深入讨论其中的别的一个核心议题:输入校验。
1. 怎么控制输入校验

输入校验,一般是对用户输入举行验证,比说文本框输入、图片文件上传、地点输入等。防止出现SQL注入、跨站脚本(XSS)等攻击。
2. 什么是XSS?

XSS(Cross-Site Scripting),跨站脚本攻击,是一种常见的网络安全漏洞,主要情势是攻击者将恶意脚本注入到用户浏览的页面中。这种攻击通常发生在当一个网站答应用户输入未经适当处理的数据到页面时。XSS攻击的影响范围较广,常常被用来盗取用户Cookie、会话劫持、访问敏感数据、冒充用户实行操纵、传播恶意软件等。
3. 代码实现:简单模仿XSS攻击

3.1 前端代码实现

这里书接上文,将 MePage.vue 的 个人简介输入框改成TXT文件上传组件。模仿简单的恶意的TXT文件上传导致系统被攻击的场景。
<!-- MePage.vue -->
<template>
<div class="me">
    <el-form :model="user" label-width="auto" style="max-width: 600px" enctype="multipart/form-data">
      <el-form-item label="头像">
      <el-upload
            class="avatar-uploader"
            :action="uploadUrl"
            accept="image/*"
            :show-file-list="false"
            :before-upload="beforeUpload"
            :on-success="handleSuccess"
            :on-error="handleError"
            :on-change="handleFileChange"
      >
          <img v-if="imageUrl" style="height: 200px; width: 200px" alt="头像" class="avatar" :src="imageUrl"/>
          <el-icon class="avatar-uploader-icon" v-else-if="!islook"/>
      </el-upload>
      </el-form-item>
      <el-form-item label="用户名">
      <el-input v-model="user.username"/>
      </el-form-item>
      <el-form-item label="性别">
      <el-input v-model="user.sex"/>
      </el-form-item>
      <el-form-item label="年龄">
      <el-input v-model="user.age"/>
      </el-form-item>
      <el-form-item label="邮箱">
      <el-input v-model="user.mailbox"/>
      </el-form-item>
      <el-form-item label="个人简介">
      <el-input v-html="user.introduce"/>
      <el-upload
            :action="uploadTextUrl"
            accept=".txt"
            :show-file-list="false"
            :on-success="handleTextSuccess"
            :on-error="handleTextError">
          <el-button type="primary" style="margin-top: 10px;">上传TXT文件</el-button>
      </el-upload>
      </el-form-item>
    </el-form>
</div>
</template>

<script>
import axios from 'axios';
import {ElMessage} from 'element-plus';

export default {
name: 'MePage',
data() {
    return {
      user: {
      userId: '',
      sex: '',
      age: '',
      mailbox: '',
      username: '',
      introduce: '',
      headPortrait: ''
      },
      uploadUrl: 'http://localhost:8081/upload/avatar',
      uploadTextUrl: 'http://localhost:8081/upload/introduce',
      islook: false,
      imgUrl: '../assets/logo.png',
      imageUrl: ''
    }
},
components: {},
created() {
    this.getuser();
},
methods: {
    getuser() {
      const token = localStorage.getItem('token');
      console.log('token:', token);
      if (!token) {
      ElMessage.error('请先登录');
      this.$router.push('/login');
      }
      axios.get('http://localhost:8081/user/getById?userId=123', {headers: {Authorization: `Bearer ${token}`}}).then(response => {
      console.log('服务器返回的数据:', response.data);
      this.user = response.data.body;
      this.imageUrl = this.user.headPortrait ? `${this.user.headPortrait}` : '@/assets/logo.png';
      console.log('图片 URL:', this.imageUrl);
      this.islook = true;
      }).catch(error => {
      console.log(error);
      ElMessage.error('获取用户信息失败');
      });
    },
    beforeUpload(file) {
      const isImage = file.type.startsWith('image/');
      if (!isImage) {
      ElMessage.error('只能上传图片文件!');
      return false;
      }
      return isImage;
    },
    handleSuccess(response) {
      console.log('图片 上传成功响应:', response);
      if (response.code === 200) {
      ElMessage.success('上传成功');
      console.log('上传成功');
      // 重新调用 getuser 方法以获取最新的头像地址
      this.getuser();
      } else {
      ElMessage.error('上传失败: ' + response.msg);
      }
    },
    handleError(err) {
      console.error('图片上传失败:', err);
      ElMessage.error('上传失败: ' + (err.message || '未知错误'));
    },
    handleFileChange(file) {
      // 更新显示的图片
      const fileURL = file.raw;
      this.imageUrl = URL.createObjectURL(fileURL);
    },
    handleTextSuccess(response) {
      console.log('TXT文件上传成功响应:', response);
      if (response && response.code === 200) {
      // 直接赋值未编码的内容
      this.user.introduce = decodeURIComponent(response.body);
      console.log('txt文件内容:', this.user.introduce);
      ElMessage.success('TXT文件上传成功');
      } else {
      ElMessage.error('TXT文件上传失败: ' + (response ? response.msg : '未知错误'));
      }
    },
    handleTextError(error) {
      ElMessage.error('上传失败: ' + (error.message || '未知错误'));
    }
}
}
</script>

<style scoped>
.me {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}

.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
height: 200px;
width: 200px
}

.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
</style>
3.2 后端代码实现

后端只必要提供接口,用于将前端上传的TXT文件举行解析,并将内容回传给前端即可。
/**
   * 个人简介上传
   */
    @PostMapping("/introduce")
    public RestResult<String> uploadIntroduce(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {
      if (!file.isEmpty()) {
            // 检查文件类型
            if (!Objects.equals(file.getContentType(), "text/plain")) {
                Map<String, Object> extension = new HashMap<>();
                extension.put("error", "上传格式错误,请上传txt文件!");
                return RestResult.buildFailure(extension);
            }

            // 读取文件内容
            StringBuilder content = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                  content.append(line).append("\n");
                }
            }

            return RestResult.build(content.toString());
      }
      throw new Exception("文件上传失败");
    }
让我们来看看效果,上传了一个恶意文件,其内容为:<img src="url" onerror="alert('告诫,系统正受到Xss攻击!');">。通过界说一个不存在的图片路径,当系统尝试加载该图片时会触发错误事件,从而实行恶意脚本,弹出非常提示。这种运动模仿了现实生产环境中,非法分子利用系统漏洞上传恶意文件或可实行插件,诱使系统实行他们预设的脚本,以达到非法目的。
https://i-blog.csdnimg.cn/direct/07b3d647f03048baafd356bc9fedf86c.gif
4. 防范Xss攻击

防范跨站脚本攻击(XSS)是Web开发中非常重要的一部门。现在市面上有好几种常用的防范措施:

[*] 对输入验证和过滤。这点可以利用正则表达式或其他验证工具来实现。
[*] 前端对后端的输出举行编码。防止浏览器将其解析为可实行的脚本。可以利用模板引擎或手动编码。
[*] 利用安全的文件上传组件,利用经过安全验证的文件上传组件,并对上传的文件举行严格的检查,确保文件类型和内容符合要求。
[*] 配置内容安全策略(CSP),限制网页可以加载的资源,防止实行未经授权的脚本。
[*] 在设置 Cookie 时,利用 HttpOnly 和 Secure 标志,防止通过 JavaScript 访问 Cookie。
4.1 代码实现:防范Xss攻击

针对上面的Xss攻击环境,只必要在后端页面在对文件内容解析后,再对内容举行过滤编码即可,如许传到前端的数据,就不会被当成脚本实行。
/**
   * 个人简介上传
   */
    @PostMapping("/introduce")
    public RestResult<String> uploadIntroduce(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {
      if (!file.isEmpty()) {
            // 检查文件类型
            if (!Objects.equals(file.getContentType(), "text/plain")) {
                Map<String, Object> extension = new HashMap<>();
                extension.put("error", "上传格式错误,请上传txt文件!");
                return RestResult.buildFailure(extension);
            }

            // 读取文件内容
            StringBuilder content = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                  content.append(line).append("\n");
                }
            }
            // 过滤并编码内容,防止恶意脚本被执行
            String filteredContent = filterContent(content.toString());
            return RestResult.build(filteredContent);
      }
      throw new Exception("文件上传失败");
    }

    /**
   * 功能描述 : 过滤内容,防止恶意脚本被执行
   *
   * @param content 文件内容
   * @return {@code String }
   * @author xhb
   * @since 2024/07/29
   */
    private String filterContent(String content) {
      return content.replaceAll("<script>", "")
                .replaceAll("</script>", "")
                .replaceAll("<iframe>", "")
                .replaceAll("</iframe>", "")
                .replaceAll("&", "&amp;")
                .replaceAll("<", "&lt;")
                .replaceAll(">", "&gt;")
                .replaceAll("\"", "&quot;")
                .replaceAll("'", "&#x27;")
                .replaceAll("/", "&#x2F;");
    }
https://i-blog.csdnimg.cn/direct/f867e4a7612d4b539adfd69a9e4fec46.png
5. 什么是SQL注入?

SQL注入(SQL Injection)攻击是指攻击者通过在输入字段中注入恶意SQL代码,从而绕过应用程序的身份验证和授权机制,访问或修改数据库中的数据。SQL注入攻击可以导致数据泄露、数据篡改、权限提升等严峻后果。
6. 代码实现:简单模仿SQL注入攻击

6.1 后端代码实现

我们将前面的上传接口实现改一改,通过自界说SQL的情势去更新表数据。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.interestplfm.mapper.UserMapper">

    <update id="sqlInjectionToUpdateAvatar">
      UPDATE user
      SET avatar = #{headPortrait}
      WHERE userid = ${userid}
    </update>
</mapper>
为了模仿SQL注入,我们可以转达一个包含恶意SQL代码的 id 参数。这里界说一个测试类模仿恶意入参。
@SpringBootTest
class InterestplfmApplicationTests {

    @Autowired
    private UserRepository userRepository;

    @Test
    void UploadTest() {
      String avatar = "new_avatar.png";
      String id = "1; DROP TABLE user; --";
      userRepository.sqlInjectionToUpdateAvatar(id , avatar);
    }

}
在这种环境下,实行的SQL语句就会变成:
https://i-blog.csdnimg.cn/direct/4ef2dec45ddc4fafb9c6cd77adc2d98f.png
导致在更新用户头衔的同时,将其他用户的个人信息删除掉。
这里趁便小提一嘴,如果有小伙伴在实行的时间出现了org.springframework.jdbc.BadSqlGrammarException,提示SQL语法错误。且利用的是Mybatis-plus。那么大概是由于MyBatis-Plus在处理SQL语句时,默认会举行参数化查询,不答应多条SQL合并情势,从而防止SQL注入。
解决方法的话,可以在配置文件中,datasource配置的url参数中,加上&allowMultiQueries=true。答应在单个SQL语句中实行多个查询,从而达到我们想要模仿的效果。
7. 防范SQL注入

防范SQL注入同样是Web开发中非常重要的一部门。常见的防范措施有:

[*] 利用参数化查询。通过将SQL语句和参数分开处理,数据库引擎会主动转义输入参数,防止恶意代码的实行。
[*] 对用户输入的数据举行严格的验证和过滤,确保输入的内容符合预期的格式和类型。
[*] 为数据库用户分配最小须要的权限,避免利用具有高权限的用户实行SQL操纵。
[*] 利用ORM(对象关系映射)框架,如 MyBatis-Plus、Hibernate等,这些框架通常内置了防止SQL注入的机制。
7.1 代码实现:防范SQL注入攻击

其实措施也很简单,我们只必要利用参数化查询,而不是直接拼接SQL语句。在MyBatis中,可以利用 #{} 来取代 ${},如许MyBatis会主动处理参数化查询。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.interestplfm.mapper.UserMapper">

    <update id="sqlInjectionToUpdateAvatar">
      UPDATE user
      SET avatar = #{headPortrait}
      WHERE userid = #{userid}
    </update>
</mapper>
8. 总结

输入校验是确保应用程序安全的第一道防线。通过对用户输入举行严格的验证,可以制止恶意数据进入系统,从而减少安全漏洞的风险。有效的输入校验策略可以保护数据的完整性,维护系统稳固性,并加强用户对应用的信托。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 功能性的安全保障:输入校验