結果
填坑失敗,並沒有看懂是如何檢測sql攻擊的。
只能說的是: 建議都使用參數化傳遞sql語句參數。(所以,用hibernate、mybatis等框架的真不用太擔心sql攻擊問題。)
前言
本文主要是為了填坑:【spring】jdbcTemplate之sql參數注入
在上文中的demo是:
正確sql:
select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c where c.child_id= '1'
攻擊sql:
select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c where c.child_id= '1';delete from child where child_id='1';--'
執行拋出的異常:
java.sql.SQLException: ORA-00911: 無效字符 at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:439) at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:395) at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:802) at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:436) at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:186) at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:521) at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:205) at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:861) at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1145) at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1267) at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3449) at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3493) at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1203) at com.vergilyn.test.sh.dao.JdbcTemplateDao.injectionAttack(JdbcTemplateDao.java:49) at com.lyn.Junit.TestJdbc.testInjectionAttack(TestJdbc.java:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
最后給出的錯誤結論:oracle驅動jar中處理對sql注入攻擊進行了處理。
為什么會得出錯誤結論:
1、肯定是技術不過關、分析不仔細。
2、沒有嚴格去驗證,單純的測試了jdbc、jdbcTemplate、Hibernate就得出了此結論。
異常原因:
根據異常提示,我去看了下ojdbc5.jar中的源碼。 即核心是oracle/jdbc/driver/T4CMAREngine.class中的unmarshalUB2()、buffer2Value(byte var)。
主要是在buffer2Value()方法返回long的值是911,然后ojdbc在后面根據此值拋出以上異常。
表示實在看不懂buffer2Value()及其內部含義,但簡單理解就是在攻擊sql中出現了”;”分號,導致拋出此異常。
一、驗證非參數話會存在sql注入攻擊(jdbcTemplate測試)
@Test @Rollback() public void testSqlInjectionAttack(){ //結論: jdbcTemplate不用參數化會存在sql攻擊問題。 String id = "1"; //正確參數 // id = "1' or 1=1 --"; //sql攻擊參數 Object rs = jdbcDao.sqlInjectionAttack(id); System.out.println(JSON.toJSONString(rs)); }
public Object sqlInjectionAttack(String id){ String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c"; sql += " where c.child_id= '"+id+"'"; return this.jdbcTemplate.query(sql,new BeanPropertyRowMapper<Child>(Child.class)); }
說明:1、正確的是返回一個Child對象。攻擊sql則返回List<Child>對象。
2、用的方法是query(…)。如果是queryForXXX(…),可能會拋異常:org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 10
二、驗證參數化查詢防止sql注入攻擊
@Test @Rollback() public void solveSqlInjectionAttack(){ //參數化sql防止sql注入攻擊 String id = "1"; //正確參數 // id = "1' or 1=1 --"; //sql攻擊參數 Object rs = jdbcTemplateDao.solveSqlInjectionAttack(id); System.out.println(JSON.toJSONString(rs)); }
public Object solveSqlInjectionAttack(String id){ String sql = "select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c"; sql += " where c.child_id= ?"; return this.jdbcTemplate.query(sql,new Object[]{id},new BeanPropertyRowMapper<Child>(Child.class)); }
正確sql結果:
攻擊sql結果:

信息: Rolled back transaction for test context [DefaultTestContext@1cfddcd testClass = JdbcTemplateTest, testInstance = com.vergilyn.persistence.test.jdbcTemplate.JdbcTemplateTest@1ead226, testMethod = solveSqlInjectionAttack@JdbcTemplateTest, testException = org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c where c.child_id= ?]; ORA-01722: 無效數字 ; nested exception is java.sql.SQLException: ORA-01722: 無效數字 , mergedContextConfiguration = [MergedContextConfiguration@18add92 testClass = JdbcTemplateTest, locations = '{classpath:jdbcTemplate/jdbcTemplate-context.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]. 十二月 31, 2016 12:42:03 上午 org.springframework.context.support.AbstractApplicationContext doClose 信息: Closing org.springframework.context.support.GenericApplicationContext@1f12030: startup date [Sat Dec 31 00:41:44 CST 2016]; root of context hierarchy org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [select c.child_id childId,c.child_name,c.child_age childAge,c.parent_id parentId from child c where c.child_id= ?]; ORA-01722: 無效數字 ; nested exception is java.sql.SQLException: ORA-01722: 無效數字 at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:243) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:660) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:695) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:727) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:737) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:787) at com.vergilyn.persistence.jdbcTemplate.JdbcTemplateDao.solveSqlInjectionAttack(JdbcTemplateDao.java:42) at com.vergilyn.persistence.test.jdbcTemplate.JdbcTemplateTest.solveSqlInjectionAttack(JdbcTemplateTest.java:30) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84) Caused by: java.sql.SQLException: ORA-01722: 無效數字 at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:439) at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:395) at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:802) at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:436) at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:186) at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:521) at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:205) at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:861) at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1145) at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1267) at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3449) at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3493) at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1203) at org.apache.commons.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:96) at org.apache.commons.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:96) at org.apache.commons.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:96) at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:703) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:644) ... 34 more
以上結果是因為,數據庫中的CHILD_ID的類型是NUMBER,傳入的參數卻是String。
當我把CHILD_ID的類型修改為:varchar2時,sql可正常執行。但sql查詢結果是null。因為,數據庫記錄中並沒有一個CHILD_ID = ”1' or 1=1 --“
三、部分源碼 (填坑失敗,后面的代碼邏輯實在難看懂。)
/** * Query using a prepared statement, allowing for a PreparedStatementCreator * and a PreparedStatementSetter. Most other query methods use this method, * but application code will always work with either a creator or a setter. * @param psc Callback handler that can create a PreparedStatement given a * Connection * @param pss object that knows how to set values on the prepared statement. * If this is null, the SQL will be assumed to contain no bind parameters. * @param rse object that will extract results. * @return an arbitrary result object, as returned by the ResultSetExtractor * @throws DataAccessException if there is any problem */ public <T> T query( PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(rse, "ResultSetExtractor must not be null"); logger.debug("Executing prepared SQL query"); return execute(psc, new PreparedStatementCallback<T>() { //line:695 @Override public T doInPreparedStatement(PreparedStatement ps) throws SQLException { ResultSet rs = null; try { if (pss != null) { pss.setValues(ps); } rs = ps.executeQuery(); // 拋出異常方法 - 02
ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); } finally { JdbcUtils.closeResultSet(rs); if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } } }); }
//------------------------------------------------------------------------- // Methods dealing with prepared statements //------------------------------------------------------------------------- @Override public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null"); Assert.notNull(action, "Callback object must not be null"); if (logger.isDebugEnabled()) { String sql = getSql(psc); logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); } Connection con = DataSourceUtils.getConnection(getDataSource()); PreparedStatement ps = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } ps = psc.createPreparedStatement(conToUse); applyStatementSettings(ps); PreparedStatement psToUse = ps; if (this.nativeJdbcExtractor != null) { psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps); } T result = action.doInPreparedStatement(psToUse); // 拋出異常方法 - 01 handleWarnings(ps); return result; } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } String sql = getSql(psc); psc = null; JdbcUtils.closeStatement(ps); ps = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex); //line: 660 } finally { if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, getDataSource()); } }
其中拋異常的方法是: at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:660)
方法中的>>>> T result = action.doInPreparedStatement(psToUse);
此處的action.doInPreparedStatement(psToUse)實際調用的就是上面 - 匿名內部類中定義的。
// 拋出異常 - 02 代碼 public ResultSet executeQuery() throws SQLException { this.checkOpen(); try { return DelegatingResultSet.wrapResultSet(this, ((PreparedStatement)this._stmt).executeQuery()); } catch (SQLException var2) { this.handleException(var2); throw new AssertionError(); } }
出現異常方法:((PreparedStatement)this._stmt).executeQuery()。 this._stmt = OraclePreparedStatementWrapper.class
// OraclePreparedStatementWrapper.class public ResultSet executeQuery() throws SQLException { return this.preparedStatement.executeQuery(); //this.preparedStatement = T4CPreparedStatement.class }
class T4CPreparedStatement extends OraclePreparedStatement;
// OraclePreparedStatement.class
public ResultSet executeQuery() throws SQLException { PhysicalConnection var1 = this.connection; synchronized(this.connection) { this.executionType = 1; this.executeInternal(); // 異常方法 - 03 if(this.userRsetType == 1) { this.currentResultSet = new OracleResultSetImpl(this.connection, this); return this.currentResultSet; } else { if(this.scrollRset == null) { this.currentResultSet = new OracleResultSetImpl(this.connection, this); this.scrollRset = this.currentResultSet; } return this.scrollRset; } } } int executeInternal() throws SQLException { this.noMoreUpdateCounts = false; this.ensureOpen(); if(this.currentRank > 0 && this.m_batchStyle == 2) { SQLException var3 = DatabaseError.createSqlException(this.getConnectionDuringExceptionHandling(), 81, "batch must be either executed or cleared"); var3.fillInStackTrace(); throw var3; } else { boolean var1 = this.userRsetType == 1; this.prepareForNewResults(true, false); this.processCompletedBindRow(this.sqlKind == 0?1:this.batch, false); if(!var1 && !this.scrollRsetTypeSolved) { return this.doScrollPstmtExecuteUpdate() + this.prematureBatchCount; } else { this.doExecuteWithTimeout(); // 異常方法 - 04 boolean var2 = this.prematureBatchCount != 0 && this.validRows > 0; if(!var1) { this.currentResultSet = new OracleResultSetImpl(this.connection, this); this.scrollRset = ResultSetUtil.createScrollResultSet(this, this.currentResultSet, this.realRsetType); if(!this.connection.accumulateBatchResult) { var2 = false; } } if(var2) { this.validRows += this.prematureBatchCount; this.prematureBatchCount = 0; } return this.validRows; } } }
void doExecuteWithTimeout() throws SQLException { try { this.cleanOldTempLobs(); this.connection.registerHeartbeat(); this.rowsProcessed = 0; SQLException var1; if(this.sqlKind == 0) { if(this.connection.j2ee13Compliant && this.executionType == 2) { var1 = DatabaseError.createSqlException(this.getConnectionDuringExceptionHandling(), 129); var1.fillInStackTrace(); throw var1; } this.connection.needLine(); if(!this.isOpen) { this.connection.open(this); this.isOpen = true; } if(this.queryTimeout != 0) { try { this.connection.getTimeout().setTimeout((long)(this.queryTimeout * 1000), this); this.executeMaybeDescribe(); } finally { this.connection.getTimeout().cancelTimeout(); } } else { this.executeMaybeDescribe(); // 異常方法 - 05 } this.checkValidRowsStatus(); if(this.serverCursor) { this.adjustGotLastBatch(); } } else { if(this.connection.j2ee13Compliant && this.sqlKind != 1 && this.sqlKind != 4 && this.executionType == 1) { var1 = DatabaseError.createSqlException(this.getConnectionDuringExceptionHandling(), 128); var1.fillInStackTrace(); throw var1; } ++this.currentRank; if(this.currentRank >= this.batch) { try { this.connection.needLine(); if(!this.isOpen) { this.connection.open(this); this.isOpen = true; } if(this.queryTimeout != 0) { this.connection.getTimeout().setTimeout((long)(this.queryTimeout * 1000), this); } this.isExecuting = true; this.executeForRows(false); } catch (SQLException var14) { this.needToParse = true; if(this.batch > 1) { this.clearBatch(); int[] var2; int var3; if(this.numberOfExecutedElementsInBatch != -1 && this.numberOfExecutedElementsInBatch < this.batch) { var2 = new int[this.numberOfExecutedElementsInBatch]; for(var3 = 0; var3 < var2.length; ++var3) { var2[var3] = -2; } } else { var2 = new int[this.batch]; for(var3 = 0; var3 < var2.length; ++var3) { var2[var3] = -3; } } BatchUpdateException var17 = DatabaseError.createBatchUpdateException(var14, var2.length, var2); var17.fillInStackTrace(); throw var17; } this.resetCurrentRowBinders(); throw var14; } finally { if(this.queryTimeout != 0) { this.connection.getTimeout().cancelTimeout(); } this.currentRank = 0; this.isExecuting = false; this.checkValidRowsStatus(); } } } } catch (SQLException var16) { this.resetOnExceptionDuringExecute(); throw var16; } this.connection.registerHeartbeat(); }
void executeMaybeDescribe() throws SQLException { int i = 1; if (this.rowPrefetchChanged) { if ((this.streamList == null) && (this.rowPrefetch != this.definesBatchSize)) { this.needToPrepareDefineBuffer = true; } this.rowPrefetchChanged = false; } if (!this.needToPrepareDefineBuffer) { if (this.accessors == null) { this.needToPrepareDefineBuffer = true; } else if (this.columnsDefinedByUser) { this.needToPrepareDefineBuffer = (!checkAccessorsUsable()); } } boolean bool = false; try { this.isExecuting = true; if (this.needToPrepareDefineBuffer) { if (!this.columnsDefinedByUser) { executeForDescribe();// 異常方法 – 06 實際是調用T4CPreparedStatement.class中的實現方法 bool = true; if (this.aFetchWasDoneDuringDescribe) { i = 0; } } if (this.needToPrepareDefineBuffer) { prepareAccessors(); } } int j = this.accessors.length; for (int k = this.numberOfDefinePositions; k < j; k++) { Accessor localAccessor = this.accessors[k]; if (localAccessor != null) localAccessor.rowSpaceIndicator = null; } if (i != 0) { executeForRows(bool); } } catch (SQLException localSQLException) { this.needToParse = true; throw localSQLException; } finally { this.isExecuting = false; } }
// T4CPreparedStatement.class void executeForDescribe() throws SQLException { this.t4Connection.assertLoggedOn("oracle.jdbc.driver.T4CPreparedStatement.execute_for_describe"); this.cleanOldTempLobs(); try { if(this.t4Connection.useFetchSizeWithLongColumn) { this.doOall8(true, true, true, true, false); } else { this.doOall8(true, true, false, true, this.definedColumnType != null);// 異常 - 07 } } catch (SQLException var8) { throw var8; // ORA-01722: 無效數字
} catch (IOException var9) {
((T4CConnection)this.connection).handleIOException(var9);
SQLException var2 = DatabaseError.createSqlException(this.getConnectionDuringExceptionHandling(), var9);
var2.fillInStackTrace();
throw var2;
} finally {
this.rowsProcessed = this.t4Connection.all8.rowsProcessed;
this.validRows = this.t4Connection.all8.getNumRows();
}
this.needToParse = false;
this.implicitDefineForLobPrefetchDone = false;
this.aFetchWasDoneDuringDescribe = false;
if(this.t4Connection.all8.aFetchWasDone) {
this.aFetchWasDoneDuringDescribe = true;
this.rowPrefetchInLastFetch = this.rowPrefetch;
}
for(int var1 = 0; var1 < this.numberOfDefinePositions; ++var1) {
this.accessors[var1].initMetadata();
}
this.needToPrepareDefineBuffer = false;
}