java源代码审计-sql注入
SQL注入是一种常见Web漏洞。所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串, 最终达到欺骗服务器执行恶意的SQL命令。sql注入产生的原因是由于程序没有经过任何过滤就将外部可控的参数拼接进入SQL语句,直接放入数据库执行,导致欺骗服务器执行攻击者恶意SQL 命令的目的。
1 SQL注入常见类型
在渗透测试的范围内,sql注入通常分为数字型和字符型,由于利用技巧的原因,也可以区分为堆注入,布尔盲注,时间盲注等等,但是对于源代码审计,sql注入的分类可以归纳总结为两种类型,一种是直接拼接,另一种是sql注入防护措施绕过。
1.1 输入的参数直接动态拼接
在一些早期的系统中没有成熟的框架处理数据库业务,连接数据库使用了jdbc的方式进行连接,如果采用了拼接的方式直接将参数拼接到了sql语句当中,如果传入的参数未进行过滤或者预编译处理,就会产生sql注入。
1.2 预编译使用不当
在部分代码审计的项目当中,我们发现部分研发人员虽采用了预编译的方式对数据进行查询,但是使用的方式不对,仍然存在sql注入风险。
下面代码就是典型的预编译有误:
String query = "SELECT * FROM usersWHERE userid ='"+ userid + "'" + " AND password='" + password + "'";
PreparedStatement statement = connection.prepareStatement(query);
ResultSet resultSet = statement.executeQuery();
对已经拼接完成的sql语句调用预编码方法进行处理,已无实际用途,仍然存在sql注入风险。正确的方法如下:
Connection conn = JDBCUtils.getConnection();
String sql = "select * from users where username = ? and password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);//用预编译传入sql语句
pstmt.setString(1,username);//设置第一个参数为username
pstmt.setString(2,password);//设置第二个参数为password
pstmt.executeQuery();
1.3 持久层框架注入–Mybatis
当前主流研发框架通常使用Mybatis或者Hiberate持久层框架来处理sql事务,如果配置不当,可导致sql注入。
Mybatis获取参数的方式有两种,分别是${}和#{}。
#{}:解析为一个 JDBC 预编译语句(prepared statement)的参数标记符。 |
${}:仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换 |
使用${}
语法,MyBatis不会进行安全检查和转义,直接拼接到最终的语句中执行,可导致sql注入。
审计案例如下:
<select id="getBlogById"resultType="Blog"parameterType=”int”>
SELECT id,title,author,content
FROM blog
WHERE id=${id}
</select>
1.4 持久层框架注入–Hiberate
在Hibernate中,其中HQL查询存在HQL注入,原生SQL查询和之前JDBC存在相同的注入问题
Hiberate查询方式如下图:
HQL注入
HQL原生sql查询注入
1.5 http头注入
如果直接从http头中取值,并拼接到sql语句中,可导致sql注入,http头注入参数如下:
审计样例如下:
1.6 order by 绕过预编译
order by后的ASC/DESC也不能被预编译,当业务场景涉及到用户可控制排序方式,且ASC/DESC是由前台传入并拼接到SQL语句上时,就可能出现危险了。
1.7 模糊查询绕过预编译
(Oracle)预编译是不能处理%
,需要手动过滤,否则会造成慢查询和DOS。对于持久层框架中模糊查询场景,需要根据数据库类型按照官方要求进行配置,否则可导致sql注入。
1.8 SQLi检测绕过
在设置过滤器的系统中,对传入的参数进行过滤,如果过滤的规则不全或未统一进行编码,可导致sql注入检测绕过。
2 逆向数据流分析常见定位关键字
%
、_
、#
、$
、batchUpdate
、createQuery
、createSQLQuery
、delete
、execute
、executeBatch
、executeQuery
、executeUpdate
、findByExample
、getConnection
、hibernate.Query
、hibernate.Session
、HibernateTemplate
、insert
、JdbcTemplate
、prepareQuery
、preparedStatement
、query
、queryForList
、queryForMap
、queryForObject
、queryForRowSet
、queryForStream
、save
、saveOrUpdate
、select
、selectList
、selectMap
、setInt
、setObject
、setParameter
、setParameterList
、setSQLXML
、setString
、SqlSession
、statement
、update
参考来源关系如下:
3 SQL注入防护
3.1 SQL注入防护——原生SQL
正确使用预编译(参数化查询)
PreparedStatement statement =connection.prepareStatement("SELECT * FROM users WHERE userid=? AND password=?");
statement.setString(1, userid);
statement.setString(2, password);
ResultSet resultSet = statement.executeQuery();
存储过程
通过CallableStatement
调用 数据库中已创建好的存储过程来执行数据库查询,其中SQL代码的定义则已经存储在了数据库中,与预编译在防SQL注入方面的效果是相同的。
String urname = request.getParameter("userName");
try {
CallableStatement callableStatement = connection.prepareCall("{callsp_getAccountBalance(?)}");
callableStatement.setString(1, urname);
ResultSet resultSet = callableStatement.executeQuery();
} catch (SQLException se) {
}
对输入数据进行黑/白名单过滤
使用正则表达式匹配、类型转换或排序之类的操作。
public String method(boolean sortWorkNo) {
String SQLquery = "select * from table order by work_no " + (sortOrder ? "ASC" :"DESC");`
}
使用ESAPI针对输入数据进行过滤
在查询前对用户输入进行转义。
Codec ORACLE_CODEC = new OracleCodec();
String query = "SELECT user_id FROM user_data WHERE user_name = '"
+ ESAPI.encoder().encodeForSQL( ORACLE_CODEC,req.getParameter("userID"))
+ "'and user_password = '"
+ ESAPI.encoder().encodeForSQL( ORACLE_CODEC,req.getParameter("pwd")) + "'";
3.2 SQL注入防护——MyBatis
默认情况下,使用
#{}
语法,MyBatis会产生PreparedStatement
语句中,并且安全的设置PreparedStatement
参数,这个过程中MyBatis会进行必要的安全检查和转义。执行SQL:
Select * from password where name = #{username};
参数:username =>admin' or '1'='1
解析后执行的SQL:Select * from password where name = 'admin\' or \'1\'=\'1'
执行SQL:
Select * from password where name = '${username}';
参数:username
传入值为:admin' or '1'='1
解析后执行的SQL:Select * from password where name ='admin' or '1'='1';
模糊查询:
DB | SQL |
---|---|
MySQL | select * from table where name like concat('%',#{name},'%'); |
Oracle | `select * from table where name like '%' |
SQL Server | select * from table where name like '%' + #{name} + '%'; |
DB2 | select * from table where name like concat('%', #{name}, '%'); |
排序:
order by
只能用${}
了,用#{}
会因进行预编译而插入多个''导致sql语句失效,过滤输入的内容,如判断一下输入的参数的长度是否正常(注入语句一般很长),更精确的过滤,则可以查询一下输入的参数是否在预期的参数集合中。
3.3 SQL注入防护——HQL
按参数名称绑定
Query query = session.createQuery("from User user where user:name=:user_name and user:age=:user_age");
query.setString("user_name", name);
query.setInteger("user_age", age);
按参数位置绑定
Query query = session.createQuery("from User user where user.name=? and user.age=?");
query.setString(0, name);
query.setInteger(1, age);
setParameter()方法
String hql = "from User user where user.name=:use_rname ";
Query query = session.createQuery(hql);
query.setparameter("user_name", name, Hibernate.STRING);
setProperties()方法
User user = new User();
user.setName("test");
user.setAge(80);
Query query = session.createQuery("from User c where c.name=:name and c.age=:age");
query.setProperties(user);