当前位置: 首页 > 科技观察

一口气彻底摆脱令人厌恶的SQL注入漏洞!

时间:2023-03-18 01:51:24 科技观察

简介文章主要内容包括:Java持久层技术/框架简单介绍了不同的容易导致SQL注入的场景/框架如何避免和修复SQL注入JDBCJDBC简介:全称JavaDatabaseConnectivity是Java访问数据库的API依赖于特定的数据库(数据库无关)所有的Java持久层技术都是基于JDBC来描述直接使用JDBC的场景。如果代码中有拼接的SQL语句,很可能会发生注入,比如//concatsqlStringsql="SELECT*FROMusersWHEREname='"+name+"'";Statementsstmt=connection.createStatement();ResultSetrs=stmt.executeQuery(sql);安全的写法是使用参数化查询(parameterizedqueries),即在SQL语句(?字符)和PreparedStatement中使用参数绑定,比如//use?tobindvariablesStringsql="SELECT*FROMusersWHEREname=?";PreparedStatementps=connection.prepareStatement(sql);//参数索引从1开始ps.setString(1,name);有一些情况,比如orderby和columnname不能被参数绑定。这时候就需要手动过滤了。比如orderby的字段名通常是有限制的,可以使用白名单来限制参数值。这里需要注意的是,使用PreparedStatement并不代表不会发生注入。如果在使用PreparedStatement之前有拼接sql语句,还是会造成注入,比如//拼接sqlStringsql="SELECT*FROMusersWHEREname='"+name+"'";PreparedStatementps=connection.prepareStatement(sql);看到这里,大家肯定会好奇PreparedStatement是如何防止SQL注入的。我们理解一下,一般情况下,用户输入作为参数值,但是在SQL注入中,用户输入作为SQL命令的一部分,会被数据库编译/解释。使用PreparedStatement时,带有占位符(?)的sql语句只会被编译一次,然后执行时只会将占位符替换为用户输入,不会再次编译/解释,从而从本质上防止了SQL注入问题。Mybatis在JDBC(原始SQL)和Hibernate(ORM)之间引入了一流的持久化框架。它简化了大部分JDBC代码,手动设置参数并获得灵活的结果。用户可以完全控制SQL并支持高级映射。更多信息请参考:http://www.mybatis.org说明在MyBatis中,使用XML文件或Annotation进行配置和映射,将接口和JavaPOJOs(PlainOldJavaObjects)映射到数据库记录。XML示例Mapper接口@MapperpublicinterfaceUserMapper{UsergetById(intid);}XML配置文件SELECT*FROMuserWHEREid=#{id}注解示例@MapperpublicinterfaceUserMapper{@select("SELECT*FROMuserWHEREid=#{id}")UsergetById(@Param("id")intid);}可以看到需要用户自己写SQL语句,所以使用不当会导致注入问题和使用与JDBC不同,MyBatis使用#{}和${}进行参数值替换。推荐:Mybatis多参数传递的4种方式。当使用#{}语法时,MyBatis会自动生成PreparedStatement并使用参数绑定(?)来设置值。上面两个例子的等效JDBC查询代码如下:Stringsql="SELECT*FROMusersWHEREid=?";PreparedStatementps=connection.prepareStatement(sql);ps.setInt(1,id);因此#{}可以有效的防止SQL注入。具体可以参考http://www.mybatis.org/mybatis-3/sqlmap-xml.htmlStringSubstitution部分。使用${}语法时,MyBatis会直接注入原始字符串,相当于将字符串拼接起来,会导致SQL注入,如SELECT*FROMuserWHEREname='${name}'limit1name的值为'or'1'='1,实际执行语句为SELECT*FROMuserWHEREname=''or'1'='1'limit1,所以建议尽量使用#{},但是有时候,比如orderby语句,使用#{}会报错,比如ORDERBY#{sortBy}sortBy参数值为name,替换后会变成ORDERBY"name",即使用字符串"name"来排序,而不是。关于按name字段排序的详细信息,请参考:https://stackoverflow.com/a/32996866/6467552这种情况下,你需要使用${}ORDERBY${sortBy}使用${}后,用户需要自己过滤输入。是的:代码层使用白名单的方式限制sortBy的允许取值,比如只能有name和email字段,异常情况下默认设置为name。XML配置文件中使用if标签判断Mapper接口方法ListgetUserListSortBy(@Param("sortBy")StringsortBy);xml配置文件/select>因为Mybatis不支持else,需要一个默认值,可以使用choose(when,otherwise)<selectid="getUserListSortBy"resultType="org.example.User">SELECT*FROMuserorderby${sortBy}orderbyname除了orderby之外,还有一些情况${}可能在更多的场景下用到,可以通过使用其他方式来避免,比如if之类的语句需要使用通配符(通配符%和_),可以在代码层在参数值两边加上%,然后用#{}使用bind标签构造新参数,再用#{}Mapper接口方法ListgetUserListLike(@Param("name")Stringname);xml配置文件SELECT*FROMuserWHEREnameLIKE#{pattern}语句中的值为OGNL表达式。详情请参考:http://www.mybatis.org/mybatis-3/dynamic-sql.htmlbind使用SQLconcat()函数SELECT*FROMuserWHEREnameLIKEconcat('%',#{name},'%')除了注入问题,这里还需要过滤用户的输入,不能使用通配符,否则当有大量表中的数据,假设用户输入的是%%,将对整个表进行模糊查询,严重时可能导致DOS。参考:http://www.tothenew.com/blog/sql-wildcards-is-your-application-safeIN条件使用和#{}Mapper接口方法ListgetUserListIn(@Param("nameList")ListnameList);xml配置文件"close=")">#{name}详情请参考http://www.mybatis。org/mybatis-3/dynamic-sql.htmlForeach部分limit语句可以直接使用#{}Mapper接口方法ListgetUserListLimit(@Param("offset")intoffset,@Param("limit")intlimit);xml配置文件SELECT*FROMuserlimit#{offset},#{limit}query=session.createQuery("fromUserwherename='"+name+"'",User.class);Useruser=query.getSingleResult();这里User是类名,类似于原生SQL,拼接会造成注入。正确用法:位置参数(Positionalparameter)Queryquery=session.createQuery("fromUserwherename=?",User.class);query.setParameter(0,name);命名参数(namedparameter)Queryquery=session.createQuery("fromUserwherename=:name",User.class);query.setParameter("name",name);命名参数列表(namedparameterlist)Queryquery=session.createQuery("fromUserwherenamein(:nameList)",User.class);query.setParameterList("nameList",Arrays.asList("lisi","zhaowu"));classinstance(JavaBean)Useruser=newUser();user.setName("zhaowu");Queryquery=session.createQuery("fromUserwherename=:name",User.class);//用户类需要有getName()方法query.setProperties(user);NativeSQL存在SQL注入Stringsql="select*fromuserwherename='"+name+"'";//deprecated//Queryquery=session.createSQLQuery(sql);Queryquery=session.createNativeQuery(sql);使用参数绑定设置参数值字符串sql="select*fromuserwherename=:name";//deprecated//Queryquery=session.createSQLQuery(sql);Queryquery=session.createNativeQuery(sql);query.setParameter("name",name);JPAJPA使用JPQL(JavaPersistenceQueryLanguage)也支持原生sql,所以Hibernate也有类似的问题,这里就不多说了,有兴趣的可以参考:https://software-security.sans.org/developer-how-to/修复-sql-injection-in-java-persistence-api-jpa