今天在解决小组内代码质量检查严重级别问题时遇到一条规则错误'"count"is provided externally to the method and not sanitized before use'(“count”在方法外部提供,在使用前未经消毒--谷歌翻译)本来感觉是挺容易解决的一个问题,却让我很是郁闷了一下,在这里跟大家共享一下!
接下来我们可以看一下SQ给出来的示例
Noncompliant Code Example
public User getUser(Connection con, String user) throws SQLException {
Statement stmt1 = null;
Statement stmt2 = null;
PreparedStatement pstmt;
try {
stmt1 = con.createStatement();
ResultSet rs1 = stmt1.executeQuery("GETDATE()"); // Compliant; parameters not used here
stmt2 = con.createStatement();
ResultSet rs2 = stmt2.executeQuery("select FNAME, LNAME, SSN " +
"from USERS where UNAME=" + user); // Noncompliant; parameter concatenated directly into query
pstmt = con.prepareStatement("select FNAME, LNAME, SSN " +
"from USERS where UNAME=" + user); // Noncompliant; parameter concatenated directly into query
ResultSet rs3 = pstmt.executeQuery();
//...
}
public User getUserHibernate(org.hibernate.Session session, String userInput) {
org.hibernate.Query query = session.createQuery( // Compliant
"FROM students where fname = " + userInput); // Noncompliant; parameter binding should be used instead
// ...
}
Compliant Solution
public User getUser(Connection con, String user) throws SQLException {
Statement stmt1 = null;
PreparedStatement pstmt = null;
String query = "select FNAME, LNAME, SSN " +
"from USERS where UNAME=?"
try {
stmt1 = con.createStatement();
ResultSet rs1 = stmt1.executeQuery("GETDATE()");
pstmt = con.prepareStatement(query);
pstmt.setString(1, user); // Compliant; PreparedStatements escape their inputs.
ResultSet rs2 = pstmt.executeQuery();
//...
}
}
public User getUserHibernate(org.hibernate.Session session, String userInput) {
org.hibernate.Query query = session.createQuery("FROM students where fname = ?");
query = query.setParameter(0,userInput); // Parameter binding escapes all input
// ...
原始版本代码
String sqlSear = "";
if(status == 1){
sqlSear = "select C_ID, C_ZIP from DB_SJB.T_SJB where N_ZT = ? and N_RECEIVEID = " + this.receiveId + " order by D_DCSJ";
}else{
sqlSear = "select C_ID, C_ZIP from DB_SJB.T_SJB where N_ZT = ? and N_RECEIVEID = " + this.receiveId + " order by D_DCSJ limit "+ count;
}
queryPs = conn.prepareStatement(sqlSear);
queryPs.setInt(1, status);
rs = queryPs.executeQuery();
结合SQ给出的示例代码我们猜测:因为count是外部传递的,他可能认为这个值不可控,有可能造成注入漏洞,所以提示错误。
修正版本代码 1
String sqlSear = "";
if(status == 1){
sqlSear = "select C_ID, C_ZIP from DB_SJB.T_SJB where N_ZT = ? and N_RECEIVEID = " + this.receiveId + " order by D_DCSJ";
}else{
sqlSear = "select C_ID, C_ZIP from DB_SJB.T_SJB where N_ZT = ? and N_RECEIVEID = " + this.receiveId + " order by D_DCSJ limit ?";
}
queryPs = conn.prepareStatement(sqlSear);
queryPs.setInt(1, status);
// 代码质量检查规则不允许外部参数直接与执行语句相加,使用占位符
if(status != 1){
queryPs.setInt(2, count);
}
rs = queryPs.executeQuery();
重新校验后发现错误依旧,只是去掉了count变量名 '""is provided externally to the method and not sanitized before use'
那么继续分析,思路依旧是注入漏洞。只剩下 this.receiveId 没有使用占位符了,有可能他导致的?
修正版本代码 2
String sqlSear = null;
if(status == 1){
sqlSear = "select C_ID, C_ZIP from DB_SJB.T_SJB where N_ZT = ? and N_RECEIVEID = ? order by D_DCSJ";
}else{
sqlSear = "select C_ID, C_ZIP from DB_SJB.T_SJB where N_ZT = ? and N_RECEIVEID = ? order by D_DCSJ limit ?";
}
queryPs = conn.prepareStatement(sqlSear);
queryPs.setInt(1, status);
queryPs.setInt(2, this.receiveId);
// 代码质量检查规则不允许外部参数直接与执行语句相加,使用占位符
if(status != 1){
queryPs.setInt(3, count);
}
rs = queryPs.executeQuery();
再次校验,错误提示没有任何改变,始终指向 queryPs = conn.prepareStatement(sqlSear) 这一行
这就不科学了呀,示例里面也是使用占位符规避这个问题的呀!代码中现在已经全部使用占位符了,你还要怎样?
但问题依然需要解决呀,那就求助谷歌大神吧!
网上针对这个问题的方案跟SQ自己的示例都差不多,都是改用占位符解决问题,但我的还是没解决啊(摔)!!!
在下面这个网址的代码示例中定义了四个字符串变量query,query2,query3,s(着重关注这四个,其他的略过)
对比后发现query定义时直接使用+字符串类型的参数,这样有注入漏洞的危险我们都知道,果然下面使用该变量时,后面都使用了Noncompliant关键字标注,代表不合格
但是使用query2和query3的地方都没有提示不合格。为什么呢?query2虽然有拼接,但是都是固定的值,不会存在注入漏洞;query3直接是没有任何变动的字符串,所以也不会出现注入漏洞。但是我们的修正代码版本2也不会有注入漏洞呀!!!(╯‵□′)╯︵┻━┻
修正代码版本2中的代码与query2和query3的区别是什么呢?最后发现示例中的query2和query3都是在定义时给了一个不为空的值,而我们的代码中给了一个null,所以猜测可能是初始值不能为空(空值或null),而他不知道你在初始化到执行conn.prepareStatement(sqlSear)之间对这个变量做了什么操作(或者他判断逻辑有误),执行时他的结果是什么(本质是因为他得到的变量结果和实际得到的结果不一致),导致后面的规则校验没有通过导致。
参考解决方案地址 https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/test/files/checks/SQLInjection.java
PS:可以看到后面的变量s是直接定义的变量,没有给任何默认值,这样他也是认为合法。
修正版本代码 3
private static final String sqlSear = "select C_ID, C_ZIP from DB_SJB.T_SJB where N_ZT = ? and N_RECEIVEID = ? order by D_DCSJ ?"; //定义为类常量
问题解决!!!
可能本人level不够,感觉这是规则校验不完全合理,修正代码2从逻辑上来说我认为是合法的。请大神们指点!!!
PS:脚本中两个参数都是int类型的,所以不存在注入漏洞,如果这个规则是为了防止注入漏洞的话,不修改任何代码都算逻辑上的合法!如果从代码规范化上来说,修正版本2也应该是最终版本,不应该继续提示错误才对!!!
规则源码地址 https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/SQLInjectionCheck.javahttp://www.atetric.com/atetric/javadoc/org.codehaus.sonar-plugins.java/java-checks/2.7/src-html/org/sonar/java/checks/SQLInjectionCheck.html