首页 > 数据库 >Mybatis下的SQL注入

Mybatis下的SQL注入

时间:2022-11-07 12:55:47浏览次数:43  
标签:test session user SQL Mybatis import public 注入

Mybatis概述

mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。mybatis 通过 xml注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回

Mybatis配置

搭建mybatis环境的几个步骤

1:创建maven工程并导入坐标

2:创建实体类和dao的接口

3:创建Mybatis的主配置文件Conifg.xml

4:创建映射配置文件UserDao.xml

创建一个maven工程,然后配置pom.xml文件。

 <packaging>jar</packaging>   //打包方式设置为jar
    <dependencies>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>

完成后,创建一个userdao的接口

package com.test.dao;

import com.test.domain.User;

import java.util.List;

public interface Userdao {
    List<User> findAll();
}

然后需要创建一个config.xml文件来指定连接数据库的参数

mapconfig.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- mybatis的主配置文件 -->
<configuration>
    <!-- 配置环境 -->
    <environments default="mysql">
        <!-- 配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务的类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源(连接池) -->
            <dataSource type="POOLED">
                <!-- 配置连接数据库的4个基本信息 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
    <mappers>
        <mapper resource="com/test/dao/Userdao.xml"/>
    </mappers>
</configuration>

还得添加一个映射配置文件

<?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.test.dao.Userdao">

    <select id="findAll" resultType="com.test.domain.User">
        select * from user
    </select>


</mapper>

这里就搭建完成了,这里还有几个注意事项:

1.创建UserDao.xml 和 UserDao.java时名称是为了和我们之前的知识保持一致。
在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
所以:UserDao 和 UserMapper是一样的

2.mybatis的映射配置文件位置必须和dao接口的包结构相同		
3.映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
4.映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名

这样我们就不需要再创建dao的实现类来完成jdbc的操作了。到这一步mybatis的就简单配置好了。

Mybatis基本使用

mybatis配置完成后,建立一个test类,查询数据库里面的信息。

使用Mybatis步骤:

1.读取配置文件
2.创建SqlSessionFactory工厂
3.创建SqlSession
4.创建Dao接口的代理对象
5.执行dao中的方法
6.释放资源

代码:

package com.test;


import com.test.dao.Userdao;
import com.test.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;


import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class mybatistest {


    public static void main(String[] args)throws Exception {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("mapconfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        Userdao userDao = session.getMapper(Userdao.class);
        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user.toString());
        }
        //6.释放资源
        session.close();
        in.close();
    }

}

以上的是配置xml的写法,注解配置Mybatis如下:

我们只需要在接口处加一个select的注解就可以了

package com.test.dao;

import com.test.domain.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface Userdao {
    @Select("select * from user")
    List<User> findAll();
}

这样我们就可以删除前面创建的userdao.xml文件。

使用注解配配置,还需要修改一下mapconfig.xml的配置
原本的xml配置:

<mappers>
        <mapper resource="com/test/dao/Userdao.xml"/>
    </mappers>

改为:

<mappers>
        <mapper class="com.test.dao.Userdao"/>
    </mappers>

mapper 这里使用注解使用class指定被select注解的接口就完成了。

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.test.dao.Userdao">

    <select id="findAll" resultType="com.test.domain.User">
        select * from user
    </select>
    <insert id="SaveUser" parameterType="com.test.domain.User">
    insert into user(name,address,gender,age,qq,email,username,password) values(#{name},#{address},#{gender},#{age},#{qq},#{email},#{username},#{password})
    </insert >

</mapper>

test类:

package com.test;
import com.test.dao.Userdao;
import com.test.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;


import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class mybatistest {
        @Test
        public void selecttest()throws Exception{
//    public static void main(String[] args) {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("mapconfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        Userdao userDao = session.getMapper(Userdao.class);
        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user.toString());
        }
        //6.释放资源
        session.close();
        in.close();
    }
    @Test
    public void Savetest() throws IOException {
        User user = new User();
        user.setName("nice");
        user.setAge(20);
        user.setGender("男");
        user.setAddress("gd");
        user.setQq("2548266145");
        user.setEmail("[email protected]");
        user.setUsername("nice0e3");
        user.setPassword("nize");


        InputStream in = Resources.getResourceAsStream("mapconfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        Userdao userDao = session.getMapper(Userdao.class);
        userDao.SaveUser(user); //调用SaveUser方法参数user对象
        session.commit();//提交事务
    }
}

在这里面分别有2个方法,一个是查询,一个是添加。虽然说功能完成了,但是这里可以看到前面的一些代码都是重复的,我们可以直接把他重复的代码封装起来,想要执行某一个功能的时候直接调用就可以了。

package com.test;


import com.test.dao.Userdao;
import com.test.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;


import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class mybatistest {
    private InputStream in;
    private SqlSessionFactoryBuilder builder;
    private Userdao userDao;
    private SqlSession session;
    private SqlSessionFactory factory;
    private User user = new User();

    @Before  //被注解后在测试方法执行前会执行
    public void init() throws IOException {


        in = Resources.getResourceAsStream("mapconfig.xml");

        builder = new SqlSessionFactoryBuilder();
        factory = builder.build(in);

         session = factory.openSession();
         session.commit();//提交事务

        userDao = session.getMapper(Userdao.class);
    }

        @Test
        public void selecttest()throws Exception{
//    public static void main(String[] args) {
        //1.读取配置文件

        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user.toString());
        }
        //6.释放资源

    }


    @Test
    public void Savetest() throws IOException {

        user.setName("nice");
        user.setAge(20);
        user.setGender("男");
        user.setAddress("gd");
        user.setQq("2548266145");
        user.setEmail("[email protected]");
        user.setUsername("nice0e3");
        user.setPassword("nize");

        userDao.SaveUser(user);



    }

    public void Update(){
        

    }
    @After  //测试方法执行后执行
    public void destroy() throws IOException {
        session.close();
        in.close();
    }

}

把配置的信息放正在init方法里面,运行时候自动运行,进行赋值。

关闭资源的放到destroy方法里面,关闭的时候自动执行关闭资源。

封装完成后,我们的Savetest的方法除了设置值外,其实也就一行代码就完成了改功能。

调试好了前面的后面的就好写了,基本上就是复制粘贴。

Mybatis修改

前面的框架大体定义好后,在来写个修改的操作。

userdao接口添加updata方法:

void UpdataUser(User user);

映射文件添加updata标签:

    <update id="UpdataUser" parameterType="com.test.domain.User">
        update user set username = #{username},address=#{address},age=#{age} where id=#{id}

    </update>

基于上面的代码添加一个方法:

@Test
    public void Update(){
        user.setUsername("nnicee0e3");
        user.setAddress("dgd");
        user.setAge(18);
        user.setId(8);
        userDao.UpdataUser(user);
    }

运行后内容成功被修改。

Mybatis delete

至于删除的操作就更简单了。

userdao接口中添加一个方法:

 void deleteUser(Integer id);

映射文件添加delete标签:

    <delete id="deleteUser" parameterType="Integer">
        delete from user where id = #{id}
    </delete>

test类里面依旧复制粘贴一下代码:

@Test
    public void delete() {

        userDao.deleteUser(6);
    }

Mybatis 查询单条信息

一如既往的接口添加方法:

List<User> findone(Integer id);

一如既往的添加映射文件内容:

<select id="findone" resultType="com.test.domain.User" parameterType="int">

        select * from user where id =#{id}
    </select>

一如既往的test类里面调用方法:

    @Test
    public void findone(){
        List<User> findone = userDao.findone(1);
        System.out.println(findone.toString());
    }

Mybatis 模糊查询

接口添加方法:

  List<User> findlike(String name);

添加映射文件内容:

<select id="findlike" resultType="com.test.domain.User" parameterType="string">
        select * from user where name like  #{name}

    </select>

test类里面调用方法:

    @Test
    public void findlike(){
        List<User> ming = userDao.findlike("%xiao%");
        for (User user1 : ming) {
            System.out.println(user1);
        }

    }

安全

SQL注入的根本原因在于SQL语句的拼接构造

JDBC拼接不当造成SQL注入

JDBC有两种方式执行SQL语句,分别是是PrepareStatementStatement

  • Statement方法每次执行都需要编译
  • PrepareStatement会对SQL语句进行预编译,直接执行

预编译为什么能防注入

Java中预编译使用preparestatement,对SQL语句进行语法分析,编译和优化,其中用户传入的参数用占位符?代替。当语句真正运行时,传过来的参数只会被看成纯文本,不会重新编译,不会被当做sql指令。

之所以PreparedStatement能防止注入,是因为当运行时动态地把参数传给PreprareStatement时,即使参数里有敏感字符如 or '1=1',它也会作为一个字段的值来处理,而不会作为一个SQL指令,从根本上讲,其实就是~data OR code~的问题,确保data永远是data,不会是可执行的code,就永远的杜绝了SQL注入这种问题。

但是PreparedStatement也不一定能彻底安全。如下

String sql = "select * from goods where min_name = ?";  // 含有参数
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, "儿童"); // 参数赋值
System.out.println(st.toString()); 
//com.mysql.jdbc.JDBC4PreparedStatement@d704f0: select * from goods where min_name = '儿童'

最终的SQL语句如下:

select * from goods where min_name = '儿童'

此刻尝试注入一下,拼接一个单引号,最终SQL语句如下

select * from goods where min_name = '儿童\''

尝试另一种拼接,儿童后拼接一个%

select * from goods where min_name = '儿童%'

发现PreparedStatement竟然没有转义,百分号恰好是like查询的通配符。 正常情况下,like查询是这么写的:

select * from goods where min_name like '%儿童%'

查询min_name字段中间有"儿童"的所有记录,其中"儿童"二字是用户输入的查询条件

假如传入的参数是 st.setString(1, "%儿童%" + "%");sql语句变为:

select * from goods where min_name like '%儿童%%'

假如传入的只是一个%,执行的SQL语句如下:

select * from goods where min_name like '%儿童%%'

虽然此种SQL注入危害不大,但这种查询会耗尽系统资源,从而演化成拒绝服务攻击。

动态参数

String sql = " SELECT passwd FROM test_table1 WHERE username = ? ";

ps = conn.prepareStatement(sql);
ps.setString(1, username);  //# 通过setString()指明该参数是字符串类型
rs = ps.executeQuery();

ps.setString(1, username)会自动给值加上引号。比如假设username=“ls”,那么拼凑成的语句会是

String sql = " SELECT passwd FROM test_table1 WHERE username = 'ls' ";

order by后一般是接字段名,而字段名是不能带引号的,比如 order by username;如果带上引号成了order by 'username',那username就是一个字符串不是字段名了,这就产生了语法错误。

所以order by后不能参数化的本质是:

  • 预编译又只有自动加引号的setString()方法,没有不加引号的方法;
  • order by后接的字段名不能有引号。(至于为什么不弄个能不自动加引号的set方法那就不太懂了)

更本质的说法是:不只order by,凡是字符串但又不能加引号的位置都不能参数化;包括sql关键字、库名表名字段名函数名等等

Mybaits使用不当

了解完框架的使用,就需要关注安全问题了

上面已经说过,MybaitsMybatis的SQL语句可以基于注解的方式写在类方法上面,更多的是以xml的方式写到xml文件,这时候我们需要更多关注Mybaits配置xml文件。

Mybatis支持两种参数符号,一种是#,另一种是$。比如:

<select id="queryAll"  resultMap="resultMap">
  SELECT * FROM NEWS WHERE ID = #{id}
</select>
<select id="queryAll"  resultMap="resultMap">
  SELECT * FROM NEWS WHERE ID = ${id}
</select>

两种区别如下:

  • #{}:占位符号(在对数据解析时会自动添加' '),其实也就是进行了预编译
  • ${}:SQL拼接符号(替换结果,不会添加' ',like和order by 后面使用,存在SQL注入问题,需手动添加过滤)

一般来说能用#{}的都用#{},首先是为性能考虑,相同的预编译SQL可以重复使用,其次${}在预编译之前已经将变量进行替换,容易产生SQL注入问题。

表名、字段名和order by后的做为变量时必须使用${},主要原因是表名、字段名和order by 后是字符串,使用SQL占位符替换字符串时会带上' ',这样会使SQL报错。

关于${}就不多说了,而#{}易产生SQL注入漏洞的情况主要分为以下三种

1、模糊查询

Select * from news where title like ‘%#{title}%’

在这种情况下使用#程序会报错,新手程序员就把#号改成了$,这样如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。正确写法:

select * from news where tile like concat(‘%’,#{title}, ‘%’)

2、in之后的多个参数

n之后多个id查询时使用# 同样会报错,

Select * from news where id in (#{ids})

正确用法为使用foreach,而不是将#替换为$

id in
<foreach collection="ids" item="item" open="("separatosr="," close=")">
#{ids} 
</foreach>

3.order by之后的参数

上边的动态参数已经说过了

小结

做代码审计时候可以利用IDEA的搜索快速定位到xml文件,看使用是否正确,后边再写一下Hibernate 框架

在用Hibernate作为查询中间层的时候,Hibernate引擎会对HQL进行解析,翻译成SQL,再交给数据库执行。

所以,HQL查询并不直接发送给数据库,而是由Hibernate引擎对查询进行解析并解释。
这也就导致了,有两种错误消息的来源,一种来自Hibernate引擎,一种来自数据库。

HQL注入相比较于常规的SQL注入而言,十分特殊。
它的特殊性在于,它没有延时函数,没有系统函数,更没有元数据表等。

很多地方说HQL不支持UNION,其实是错误的。Hibernate支持UNION的。
但是,想要使用UNION,必须在模型的关系明确后,这种情况比较少见,所以会导致UNION失败。

所以,想要利用HQL注入,是一件比较极限和小众的事情。

标签:test,session,user,SQL,Mybatis,import,public,注入
From: https://www.cnblogs.com/gk0d/p/16865551.html

相关文章

  • centos8 安装mysql8 国内源
    从mysql.com下载了安装源之后,发现在yuminstallmysql-server之后一直卡住,要么就报timeout错误其实完全可以从国内下载 推荐地址:http://mirrors.ustc.edu.cn/mysql-re......
  • CentOS 7下mysql数据库定时备份创建定时任务
    1.创建定时任务脚本vi/usr/bin/bakeup_mysql.sh内容:#!/bin/bash#deleteoldbakeuprm-rf/data/mysql_bak/*backup_dir='/data/mysql_bak/'current_time=$(date+'%......
  • 记在Linux系统源码包安装MySQL
    记在Linux系统源码包安装MySQL实验环境:系统版本:CentOS7MySQL版本:5.7.39(https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.39-el7-x86_64.tar.gz)实验开始步......
  • Transactional mybatis plus 不生效
    @Transactional默认是当方法抛出RuntimeException才会回滚,可以使用@Transactional(rollbackFor=Exception.class)指定具体异常时就回滚代码:@Transactional(rollbac......
  • mysql的配置
    从官网下载MySQL5.7版本的zip文件压缩放到自己的文件夹下复制MySQL文件下bin目录的路径我的电脑>属性>高级>环境变量找到下面框的PATH编辑,添加把复制好的路径粘贴上......
  • MySQL简易安装
    ** MySQL安装教程**本教程根据MySQL官方文档安装,过程简单,只适用于自己练习SQL,当做练习用。资源准备MySQL版本:mysql-8.0.31-linux-glibc2.12-x86_64.tar.xzLinux版本:cen......
  • 定时备份测试平台的mysql数据文件
    背景定时备份ATP的Mysql数据,一周一次,仅保留2个月数据(8次)解决步骤思路编写shell脚本实现备份功能,后加上定时任务第一部分(shell脚本)#!/bin/bash#定义mysql的......
  • Mysql索引的一二三问
    下面文章整理下我对mysql索引的理解1、什么是索引索引和表的关系可以类比于目录和书籍,它们的存在就是为了更快地检索到记录,在mysql中,索引是表的一部分,是数据记录的引用指......
  • MyBatis-Plus
    1.代码生成主要依赖<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.2.0</version></de......
  • SQL存储过程
    --创建存储过程(无参)createproceduresp_pro1()selectcount(*)fromstudent;--调用存储过程callsp_pro1();--创建带参数的存储过程--输入参数createproced......