jdbc注入
经典拼接
@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);if (!con.isClosed())System.out.println("Connect to database successfully.");// sqli vuln codeStatement statement = con.createStatement();String sql = "select * from users where username = '" + username + "'";logger.info(sql);ResultSet rs = statement.executeQuery(sql);......}
代码逻辑非常简单,直接一句:@RequestParam得到输入
RequestMapping是一个用来处理请求地址映射的注解
先测试了数据库是否正常连接,然后执行SQL语句,属于拼接,注入漏洞显而易见。而说这个的重点不在这里,而是去挖掘此类漏洞的时候。使用CodeQL去查找这种形式的注入,首先要找项目中所有以*Mapping结尾的注释,然后get其输入,也就是@RequestParam,通过数据污点追踪到最后执行方法*.executeQuery()
//主要逻辑语句exists(Call call |sink.asExpr() = call.getArgument(0) andcall.getCallee().hasQualifiedName("java.sql", "Statement", "executeQuery"))
预编译
@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 codeString sql = "select * from users where username = ?";PreparedStatement st = con.prepareStatement(sql);st.setString(1, username);logger.info(st.toString()); // sql after prepare statementResultSet rs = st.executeQuery();......}
这里代码逻辑也很清晰,使用了预编译,先使用占位符然后再赋值
st.setString(1, username);
关于PreparedStatement.setString():将指定的参数设置为给定的Java字符串值。驱动程序将其发送到数据库时将其转换为SQL VARCHAR或LONGVARCHAR值(取决于参数的大小相对于驱动程序对VARCHAR值的限制)。也就是说在启用预编译的情况下,在进入数据库执行时,会自动加上引号(即转换字符串)。
最后再去执行
ResultSet rs = st.executeQuery();
下面调试下看看预编译都做了些什么,在executeQuery()下断点
进入跟进
发现什么有用的信息都没有,继续向下

跟进查看,sql语句(含有占位符的)和用户输入的内容时分成两部分的,

漏洞常见处
- 预编译无法处理
order by,因为order by不能参数化。原因是 order by 子句后面需要加字段名或者字段位置,而字段名是不能带引号的,否则就会被认为是一个字符串而不是字段名,简单来说就是会报错。
使用 PreapareStatement 将会强制给参数加上',所以,在使用 order by 语句时就必须得使用拼接的 Statement,所以就会造成 SQL 注入,需要手动过滤
String sql = "Select * from news where title =?" + "order by '" + time + "' asc"
- 使用like语句
String sql = "select * from users where password like '%" + con + "%'"; //存在sql注入
- 没有手动过滤
%
预编译无法处理这个符号,需要加入黑名单,否则会造成慢查询,进而造成DOS
- 以为自己使用了预编译,实则并没有
预编译必须使用?做占位符
String sql = "select * from users where username = '" + username + "'";PreparedStatement st = con.prepareStatement(sql);logger.info(st.toString());ResultSet rs = st.executeQuery();
- in语句
删除语句中可能会存在此类语句,由于无法确定delIds含有对象个数而直接拼接sql语句
String sql = "delete from users where id in("+delIds+"); //存在sql注入
MyBatis
MyBatis存在注入的主要一点就是${Parameter}

跟进查看,具体为什么MyBatis不在配置文件*.xml中写着,这属于Spring自身的开发特点,也可以使用Java类作为配置文件,使用注释来开发,以实现这种效果。
@Select("select * from users where username = '${username}'")List<User> findByUserNameVuln01(@Param("username") String username);
可以看到使用了${}的就是直接拼接。
在MyBatis中使用预编译则是换了一种方式#{Parameter}
@Select("select * from users where username = #{username}")User findByUserName(@Param("username") String username);
Mybatis中易发生SQL注入的情况
- like关键字模糊查询
<select id="findByUserNameVuln02" parameterType="String" resultMap="User">select * from users where username like '%${_parameter}%'</select>
原因就是在使用like进行模糊查询的时候,使用#{}会报错,那么这个时候开发会将#{}改为${}来解决报错,但是这样就会造成拼接问题。
正确的解决方法应该是
<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}+'%'
- 使用in语句
同样的,使用in语句也会报错,改为${}则会造成拼接。
<select id="findByUserNameVuln04" parameterType="String" resultMap="User">select * from users where id in (${id})</select>
// http://localhost:8080/sqli/mybatis/vuln04?id=1)%20or%201=1%23@GetMapping("/mybatis/vuln04")public List<User> mybatisVuln04(@RequestParam("id") String id) {return userMapper.findByUserNameVuln04(id);}
正确的改写方法是使用 foreach
<select id="findByIdSec04" parameterType="String" resultMap="User">SELECT* from users WHERE id IN <foreach collection="id" item="id" open="(" close=")" separator=",">#{id}</foreach></select>
注意定义接口的时候,类型就要写成List了
@GetMapping("/mybatis/sec04")public List<User> mybatisSec04(@RequestParam("id") List id){return userMapper.findByIdSec04(id);}
在Mapper中写一下
List<User> findByIdSec04(@Param("id") List id);

- 至于order by则需要做好过滤,写个Filter过滤器。
MyBatis-plus框架下的SQL注入
参考:https://xz.aliyun.com/t/11672#toc-8
mark一下~
Hibernate框架下的SQL注入
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 HQL语句,也是采用拼接的方式做增删查改等操作。而HQL一般采用参数绑定来有效避免注入问题。- 命名参数
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);
- 位置参数
String parameter = "g1ts";Query<User> query = session.createQuery("from users name = ?1", User.class);query.setParameter(1, parameter);
- 命名参数列表
List<String> names = Arrays.asList("g1ts", "g2ts");Query<User> query = session.createQuery("from users where name in (:names)", User.class);query.setParameter("names", names);
- 类实例
user1.setName("g1ts");Query<User> query = session.createQuery("from users where name =:name", User.class);query.setProperties(user1);
- HQL拼接
<font style="color:rgb(51, 51, 51);">StringEscapeUtils.escapeSql()</font>对参数进行过滤:(算是黑名单吧)
import org.apache.commons.lang.StringEscapeUtils;public static void main(String[] args) {String str = StringEscapeUtils.escapeSql("'");System.out.println(str);}
Hibernate也支持原生SQL语句执行,也会有使用拼接而直接造成SQL注入问题。
