SQL注入代码审计
一、JDBC拼接不当造成sql注入
JDBC存在两种方法执行SQL语句,分别为PreparedStatement和Statement,相比Statement ,PreparedStatement会对SQL语句进行预编译,Statement会直接拼接sql语句造成SQL注入漏洞
1.Statement
示例代码中直接使用statement.executeQuery(sql),存在sql注入漏洞
@RequestMapping("/jdbc/vuln") public String jdbc_sqli_vul(@RequestParam("username") String username) { StringBuilder result = new StringBuilder(); try { Class.forName(driver); Connection con = DriverManager.getConnection(url, user, password); // sqli vuln code Statement statement = con.createStatement(); String sql = "select * from users where username = '" + username + "'"; logger.info(sql); ResultSet rs = statement.executeQuery(sql); while (rs.next()) { String res_name = rs.getString("username"); String res_pwd = rs.getString("password"); String info = String.format("%s: %s\n", res_name, res_pwd); result.append(info); logger.info(info); } rs.close(); con.close(); } catch (ClassNotFoundException e) { logger.error("Sorry,can`t find the Driver!"); } catch (SQLException e) { logger.error(e.toString()); } return result.toString(); }
此处存在sql注入漏洞,执行SQL语句为select * from users where username = ‘joychoun’ or ‘1’ = ‘1’
2.PreparedStatement
与Statement的区别在于PrepareStatement会对SQL语句进行预编译,预编译的好处不仅在于在一定程度上防止了sql注入,还减少了sql语句的编译次数,提高了性能,其原理是先去编译sql语句,无论最后输入为何,预编译的语句只是作为字符串来执行,而SQL注入只对编译过程有破坏作用,执行阶段只是把输入串作为数据处理,不需要再对SQL语句进行解析,因此解决了注入问题。
PrepareStatement防御预编译的写法是使用?作为占位符然后将SQL语句进行预编译,由于”?”作为占位符已经告诉数据库整个SQL语句的结构,即?处传入的是参数,而不会是sql语句,所以即使攻击者传入sql语句也不会被数据库解析。
@RequestMapping("/jdbc/sec") public String jdbc_sqli_sec(@RequestParam("username") String username) { StringBuilder result = new StringBuilder(); try { Class.forName(driver); Connection con = DriverManager.getConnection(url, user, password); if (!con.isClosed()) System.out.println("Connecting to Database successfully."); // fix code String sql = "select * from users where username = ?"; PreparedStatement st = con.prepareStatement(sql); st.setString(1, username); logger.info(st.toString()); // sql after prepare statement ResultSet rs = st.executeQuery(); while (rs.next()) { String res_name = rs.getString("username"); String res_pwd = rs.getString("password"); String info = String.format("%s: %s\n", res_name, res_pwd); result.append(info); logger.info(info); } rs.close(); con.close(); } catch (ClassNotFoundException e) { logger.error("Sorry, can`t find the Driver!"); e.printStackTrace(); } catch (SQLException e) { logger.error(e.toString()); } return result.toString(); }
此时不存在SQL注入漏洞,传入的payload已进行转义
3.JDBC易产生漏洞点
3.1.未使用占位符
PreparedStatement只有在使用”?”作为占位符才能预防sql注入,直接拼接仍会存在sql注入漏洞
3.2.使用in语句
删除语句中可能会存在此类语句,由于无法确定delIds含有对象个数而直接拼接sql语句,造成sql注入。
String sql = "delete from users where id in("+delIds+"); //存在sql注入
解决方法为遍历传入的 对象个数,使用“?”占位符。
3.3.使用like语句
使用like语句直接拼接会造成sql注入
String sql = "select * from users where password like '%" + con + "%'"; //存在sql注入
3.4.%和_
没有手动过滤%。预编译是不能处理这个符号的, 所以需要手动过滤,否则会造成慢查询,造成 dos。
3.5.Order by、from等无法预编译
通过上面对使用in关键字和like关键字发现,只需要对要传参的位置使用占位符进行预编译时似乎就可以完全防止SQL注入,然而事实并非如此,当使用order by语句时是无法使用预编译的,原因是order by子句后面需要加字段名或者字段位置,而字段名是不能带引号的,否则就会被认为是一个字符串而不是字段名,然而使用PreapareStatement将会强制给参数加上’,所以,在使用order by语句时就必须得使用拼接的Statement,所以就会造成SQL注入,需要进行手动过滤,否则存在sql注入。
String sql = "Select * from news where title =?" + "order by '" + time + "' asc"
二.Mybatis框架下的sql注入
Mybatis使用parameterType向sql语句传参,在sql引用传参可以使用#{Parameter}和${Parameter}两种方式
1 .${Parameter}方式
${Parameter}采用拼接的方式构造SQL语句,在对用户输入过滤不严格的前提下,存在sql注入漏洞
//Mybatis @Select("select * from users where username = '${username}'") List<User> findByUserNameVuln01(@Param("username") String username);
@GetMapping("/mybatis/vuln01") public List<User> mybatisVuln01(@RequestParam("username") String username) { return userMapper.findByUserNameVuln01(username); }
2.#{Parameter}方式
#{Parameter}采用预编译的方式构造SQL语句,避免了SQL注入的产生
@GetMapping("/mybatis/sec01") public User mybatisSec01(@RequestParam("username") String username) { return userMapper.findByUserName(username); }
//Mybatis @Select("select * from users where username = #{username}") User findByUserName(@Param("username") String username);
3.MyBatis易产生SQL注入的三种情况
3.1 模糊查询
在这种情况下使用#{}程序会报错,新手程序员就把#号改成了$,这样如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。
<select id="findByUserNameVuln02" parameterType="String" resultMap="User"> select * from users where username like '%${_parameter}%' </select>
正确写法如下:
<select id="findByUserNamesec" parameterType="String" resultMap="User"> select * from users where username like concat('%',#{_parameter}, '%') </select>
正确写法:
mysql: select * from users where username like concat('%',#{username},'%') oracle: select * from users where username like '%'||#{username}||'%' sqlserver: select * from users where username like '%'+#{username}+'%'
3.2 使用in语句
使用in语句时直接使用#{}会报错,可能会存在使用${}直接拼接,造成sql注入
<select id="findByUserNameVuln04" parameterType="String" resultMap="User"> select * from users where id in (${id}) </select>
// http://localhost:8080/sqli/mybatis/vuln04?id=1)%20AND%201=1%23 @GetMapping("/mybatis/vuln04") public List<User> mybatisVuln04(@RequestParam("id") String id) { return userMapper.findByUserNameVuln04(id); }
正确用法为使用foreach,而不是将#替换为$
id in<foreach collection="ids" item="item" open="("separatosr="," close=")">#{ids} </foreach>
3.3使用order by 语句
和JDBC同理,使用#{}方式传参会导致order by语句失效,所以使用order by语句的时候还是需要做好过滤
三.Mybatis-plus框架下的sql注入
与SpringDataJpa类似,mybatis-plus提供了相关的funciton进行sql的操作,例如like(“name”,”tks”)——>name like ‘%tks%’,同时也很贴心的考虑到了SQL注入问题,对绝大部分场景进行了预编译处理。但是类似动态表名、orderby这种需要拼接的场景在实际开发中还是需要额外的注意。
1.条件构造器Wrapper
条件构造器Wrapper可以用于复杂的数据库操作:大于、小于、模糊查询等等。
比较常用的是QueryWrapper和UpdateWrapper:
- Wrapper : 条件构造抽象类,最顶端父类,抽象类中提供4个方法
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
- QueryWrapper : Entity 对象封装操作类,不是用lambda语法
- UpdateWrapper : Update 条件封装,用于Entity对象更新操作
1.1基础使用
配置基础类
import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @Data @TableName("tutorials") //tutorials public class Tutorial { private Integer Id; private String Title; private String Author; }
配置Mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.joychou.dao.Tutorial; @Mapper public interface TutorialMapper extends BaseMapper<Tutorial> { }
调用相关的api进行操作
@RequestMapping("selectauthor") public Tutorial mybatisselect(String author) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.eq("author",author); Tutorial tutorial = tutorialMapper.selectOne(wrapper); return tutorial; }
1.2mybatis-puls预编译
传统的mybaits框架对于in范围查询和like模糊查询需要做额外的处理:
like模糊查询需要在mapperxml配置中用sql的内置函数进行拼接,拼接后再采用#预编译的方式进行查询;
in范围查询的话需要在进行同条件多值查询的时候,可以使用MyBatis自带的循环指令foreach来解决SQL语句动态拼接的问题;
mybatis-puls已经考虑到sql注入的影响,相关wrapper的function已进行了相关的预编译处理。例如mybatis常见的like和in注入场景,均进行了预编译处理,例如如下例子:
like模糊查询
@RequestMapping("/like") public List<Tutorial> mybatislike(String author) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.select("*").like("author",author); List<Tutorial> tutorials = tutorialMapper.selectList(wrapper); return tutorials; }
打印相关的查询log,可以看到相关查询已使用?进行预编译处理:
in范围查询
@RequestMapping("/in") public List<Tutorial> mybatisin(String author1,String author2) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.select("*").in("author",author1,author2); List<Tutorial> tutorials = tutorialMapper.selectList(wrapper); return tutorials; }
打印相关的查询log,可以看到相关查询已使用?进行预编译处理:
2.常见注入场景
1.条件构造器常见注入场景
apply
apply(String applySql, Object... params) apply(boolean condition, String applySql, Object... params)
- 拼接sql
- 注:该方法可用于数据库函数 动态入参的
params
对应前面applySql
内部的{index}
部分.这样是不会有sql注入风险的,反之会有! - 示例:
apply()直接拼接sql语句存在sql注入
@RequestMapping("/applyvuln") public Tutorial mybatisapplyvuln(String author,String title) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.eq("author",author).apply("title="+title); Tutorial tutorial = tutorialMapper.selectOne(wrapper); return tutorial; }
使用{index}params进行预编译处理,不存在sql注入
@RequestMapping("/applysec") public Tutorial mybatisapplysec(String author,String title) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.eq("author",author).apply("title={0}",title); Tutorial tutorial = tutorialMapper.selectOne(wrapper); return tutorial; }
last
last(String lastSql) last(boolean condition, String lastSql)
- 无视优化规则直接拼接到 sql 的最后
- 注:只能调用一次,多次调用以最后一次为准 ,若相关内容用户可控,则存在sql注入风险
- 示例:
@RequestMapping("/lastvuln") public List<Tutorial> mybatislastvuln(String column) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.last("order by " + column); List<Tutorial> list = tutorialMapper.selectList(wrapper); return list; }
exists/notExists
exists(String existsSql) exists(boolean condition, String existsSql)
notExists(String notExistsSql) notExists(boolean condition, String notExistsSql)
- 拼接EXISTX / NOT EXISTS ( sql语句 )
- 注:若existsSql或notExistsSql中有关内容用户可控,则存在sql注入风险
- 示例:
@RequestMapping("/existsvuln") public List<Tutorial> mybatisexistsvuln(String title) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.exists("select title from tutorials where title = " + title); List<Tutorial> list = tutorialMapper.selectList(wrapper); return list; }
having
having(String sqlHaving, Object... params) having(boolean condition, String sqlHaving, Object... params)
- HAVING ( sql语句 ),用于Having查询,一般用配合groupby在对分组统计函数进行过滤的场景中
- 注:与apply一样,动态入参的params对应前面applySql内部的{index}部分,可以进行预编译防止SQL注入问题。
- 示例:
@RequestMapping("/havingvuln") public List<Tutorial> mybatishavingvuln(String id) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.select().groupBy("author").having("id > " +id); List<Tutorial> list = tutorialMapper.selectList(wrapper); return list; }
order by
- orderBy
orderBy(boolean condition, boolean isAsc, R... columns)
- orderByAsc
orderByAsc(R... columns) orderByAsc(boolean condition, R... columns)
- orderByDesc
orderByDesc(R... columns) orderByDesc(boolean condition, R... columns)
- 排序:ORDER BY 字段, (ASC/DESC)
- 注:Order by排序时不能进行预编译处理,故相关内容用户可控的话会存在sql注入风险。
- 示例:
@RequestMapping("/orderbyvuln") public List<Tutorial> mybatishorderbyvuln(String column) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.select().orderBy(true, true, column); List<Tutorial> list = tutorialMapper.selectList(wrapper); return list; } @RequestMapping("/orderbyAscvuln") public List<Tutorial> mybatishorderbyAscvuln(String column) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.select().orderByAsc(column); List<Tutorial> list = tutorialMapper.selectList(wrapper); return list; } @RequestMapping("/orderbyDescvuln") public List<Tutorial> mybatishorderbyDescvuln(String column) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.select().orderByDesc(column); List<Tutorial> list = tutorialMapper.selectList(wrapper); return list; }
group By
groupBy(R... columns) groupBy(boolean condition, R... columns)
- 分组:GROUP BY 字段, … 主要用于用于结合聚合函数,根据一个或多个列对结果集进行分组
- 示例:
@RequestMapping("/gropbycvuln") public List<Tutorial> mybatishsgropbycvuln(String column) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.select().groupBy(column); List<Tutorial> list = tutorialMapper.selectList(wrapper); return list; }
insql/notinsql
inSql(R column, String inValue) inSql(boolean condition, R column, String inValue)
notInSql(R column, String inValue) notInSql(boolean condition, R column, String inValue)
- 字段IN / NOT IN ( sql语句 )
- column字段、inValue字段可控的情况下存在注入风险。
- 示例:
@RequestMapping("/insqlcvuln") public List<Tutorial> mybatisinsqlvuln(String column,String id) { QueryWrapper<Tutorial> wrapper = new QueryWrapper<>(); wrapper.select().inSql(column,"select id from tutorials where id >" + id); List<Tutorial> list = tutorialMapper.selectList(wrapper); return list; }
2.使用 Wrapper 自定义SQL(特殊的预编译场景)
Wrapper提供了自定义SQL场景,与传统的mybatis一样使用$进行注解,但实际上ew已经做了预编译处理。同样的也支持注解&xml配置。
注:需要mybatis-plus版本 >= 3.0.7 param 参数名要么叫ew,要么加上注解@Param(Constants.WRAPPER) 使用${ew.customSqlSegment} 不支持 Wrapper 内的entity生成where语句
示例:
1.注解模式
- 配置mapper
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.toolkit.Constants; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.joychou.dao.Person; import java.util.List; @Mapper public interface PersonMapper { @Select("select * from persons ${ew.customSqlSegment}") List<Person> selectPerson(@Param(Constants.WRAPPER) Wrapper wrapper); }
- 配置controller
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.joychou.dao.Person; import org.joychou.dao.Tutorial; import org.joychou.mapper.PersonMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/sqlimybatis_plus") public class Mybatis_Plus_SQLI_2 { @Autowired PersonMapper personMapper; //orderby R... columns 可控,存在SQL注入风险 @RequestMapping("/selectperson") public List<Person> selectPerson(String column){ QueryWrapper<Object> wrapper = new QueryWrapper<>(); wrapper.orderByAsc(column); return personMapper.selectPerson(wrapper); } //like 自动进行预编译,不存在SQL注入风险 @RequestMapping("/selectpersonlike") public List<Person> selectPersonlike(String name) { QueryWrapper<Object> wrapper = new QueryWrapper<>(); wrapper.like("name", name); return personMapper.selectPerson(wrapper); } }
Wrapper自定义模式下like、in等会自动进行预编译,但若存在last、orderby等未进行预编译方法,若相应数据用户可控仍会存在SQL注入风险,风险API仍为2.1中常见注入场景。
2.XML模式
- 配置mapper
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.toolkit.Constants; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.joychou.dao.Person; import java.util.List; @Mapper public interface PersonMapper { @Select("select * from persons ${ew.customSqlSegment}") List<Person> selectPerson(@Param(Constants.WRAPPER) Wrapper wrapper); List<Person> selectPersonXML1(Wrapper ew); List<Person> selectPersonXML2(Wrapper ew); List<Person> selectPersonXMLOrdeyBy(Wrapper ew); }
- 配置XXXXMapper.xml
<?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="org.joychou.mapper.UserMapper"> <resultMap type="org.joychou.dao.Person" id="Person"> <id column="id" property="id" javaType="java.lang.Integer" jdbcType="NUMERIC"/> <id column="name" property="name" javaType="java.lang.String" jdbcType="VARCHAR"/> <id column="address" property="address" javaType="java.lang.String" jdbcType="VARCHAR"/> <id column="city" property="city" javaType="java.lang.String" jdbcType="VARCHAR"/> </resultMap> <select id="selectPersonXML1" resultMap="Person"> select * from persons where ${ew.customSqlSegment} </select> <select id="selectPersonXML2" resultMap="Person"> select * from persons <where> ${ew.SqlSegment} </where> </select> <select id="selectPersonXMLOrdeyBy" resultMap="Person"> select * from persons ${ew.SqlSegment} </select> </mapper>
- 配置controller
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.joychou.dao.Person; import org.joychou.dao.Tutorial; import org.joychou.mapper.PersonMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/sqlimybatis_plus") public class Mybatis_Plus_SQLI_2 { @Autowired PersonMapper personMapper; @RequestMapping("/selectperson") public List<Person> selectPerson(String column){ QueryWrapper<Object> wrapper = new QueryWrapper<>(); wrapper.orderByAsc(column); return personMapper.selectPerson(wrapper); } @RequestMapping("/selectperson1") public List<Person> selectPersonXML1(String name) { QueryWrapper<Object> wrapper = new QueryWrapper<>(); wrapper.eq("name", name); return personMapper.selectPerson(wrapper); } @RequestMapping("/selectperson2") public List<Person> selectPersonXML2(String name) { QueryWrapper<Object> wrapper = new QueryWrapper<>(); wrapper.eq("name", name); return personMapper.selectPerson(wrapper); } @RequestMapping("/selectperson3") public List<Person> selectPersonXMLOrderBy(String column) { QueryWrapper<Object> wrapper = new QueryWrapper<>(); wrapper.orderByAsc(column); return personMapper.selectPerson(wrapper); } }
以上selectPersonXML1、selectPersonXML2均实现相同功能,SQL注入问题与注解模式相同。
3.分页插件
1.PaginationInnerInterceptor
- 配置分页插件
import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { /** * 注册插件 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加分页插件 PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(); // 设置请求的页面大于最大页后操作,true调回到首页,false继续请求。默认false pageInterceptor.setOverflow(false); // 单页分页条数限制,默认无限制 pageInterceptor.setMaxLimit(500L); // 设置数据库类型 pageInterceptor.setDbType(DbType.MYSQL); interceptor.addInnerInterceptor(pageInterceptor); return interceptor; } }
- 配置controller
@RequestMapping("/selectpage") public List<Person> mybatispluspage(Long page,Long size,String order){ QueryWrapper<Person> qw = new QueryWrapper<>(); Page<Person> personPage = new Page<>(page,size); personPage.addOrder(OrderItem.asc(order)); IPage<Person> iPage = personMapper.selectPage(personPage, qw); List<Person> persons = iPage.getRecords(); return persons; }
若直接使用addOrder()未进行过滤,则存在SQL注入漏洞。
2.pagehelper
因为Order by排序时不能进行预编译处理,所以在使用插件时需要额外注意如下function,同样会存在SQL注入风险:
- com.github.pagehelper.Page
- 主要是setOrderBy(java.lang.String)方法
- com.github.pagehelper.page.PageMethod
- 主要是startPage(int,int,java.lang.String)方法
- com.github.pagehelper.PageHelper
- 主要是startPage(int,int,java.lang.String)方法
四.Hibernate框架下的SQL注入
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
Hibernate可以使用hql来执行SQL语句,也可以直接执行SQL语句,无论是哪种方式都有可能导致SQL注入
HQL
hql语句:
String hql = "from People where username = '" + username + "' and password = '" + password + "'";
这种拼接方式存在SQL注入
正确使用以下几种HQL参数绑定的方式可以有效避免注入的产生:
1.命名参数(named parameter)
Query<User> query = session.createQuery("from users name = ?1", User.class); String parameter = "g1ts"; Query<User> query = session.createQuery("from users name = :name", User.class); query.setParameter("name", parameter);
2.位置参数(Positional parameter)
String parameter = "g1ts"; Query<User> query = session.createQuery("from users name = ?1", User.class); query.setParameter(1, parameter);
3.命名参数列表(named parameter list)
List<String> names = Arrays.asList("g1ts", "g2ts"); Query<User> query = session.createQuery("from users where name in (:names)", User.class); query.setParameter("names", names);
4.类实例(JavaBean)
user1.setName("g1ts"); Query<User> query = session.createQuery("from users where name =:name", User.class); query.setProperties(user1);
5.HQL拼接方法
这种方式是最常用,而且容易忽视且容易被注入的,通常做法就是对参数的特殊字符进行过滤,推荐大家使用 Spring工具包的StringEscapeUtils.escapeSql()方法对参数进行过滤:
import org.apache.commons.lang.StringEscapeUtils; public static void main(String[] args) { String str = StringEscapeUtils.escapeSql("'"); System.out.println(str); }
SQL
Hibernate支持使用原生SQL语句执行,所以其风险和JDBC是一致的,直接使用拼接的方法时会导致SQL注入
语句如下:
Query<People> query = session.createNativeQuery("select * from user where username = '" + username + "' and password = '" + password + "'");
正确写法:
String parameter = "g1ts"; Query<User> query = session.createNativeQuery("select * from user where name = :name"); query.setParameter("name",parameter);
注:此文为总结多位师傅文章,包括不限于以下文章:
https://www.sec-in.com/article/1073
https://www.cnblogs.com/klslb/p/7146889.html
…….
本文作者:白给
本文转载于先知社区,如有侵权请联系删除
cesfe 1个月前0
好的,谢谢昶之琴 1个月前0
这个安装地址失效了,我在网上找了一个:https://xiazai.zol.com.cn/detail/35/344340.shtml 如果还是不行的话就需要您自己去网上找找了cesfe 1个月前0
帆软部署 ,访问的地址访问不到昶之琴 2年前0
我以为只要提交就行了好想告诉你 2年前0
花巨资看一下