再看ibatis Order By注入問題

壹頁書發表於2017-03-22
接上文
http://blog.itpub.net/29254281/viewspace-1318239/

領導讓開發同學鼓搗一個可配置化的後臺.
又回到了原來的問題
如果要靈活,很多引數要從前端頁面傳過來,有SQL隱碼攻擊的風險.
如果引數化SQL,又很難做到靈活..

先看一個注入的例子:

  1. import java.sql.Connection;  
  2. import java.sql.DriverManager;  
  3. import java.sql.ResultSet;  
  4. import java.sql.SQLException;  
  5. import java.sql.Statement;  
  6.   
  7. public class Test {  
  8.     public static void main(String[] args) throws SQLException {  
  9.         String para="/index.html' union all select * from probe -- ";  
  10.         Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mvbox""xx""xx");  
  11.           
  12.         Statement ps=conn.createStatement();  
  13.   
  14.         ResultSet rs = ps.executeQuery("select * from probe where path='"+para+"'");  
  15.         while (rs.next()) {  
  16.             System.out.println(rs.getString("host")+":"+rs.getString("path"));  
  17.         }  
  18.         rs.close();  
  19.         ps.close();  
  20.         conn.close();  
  21.     }  

如果要避免這種風險,可以選擇引數化
  1. import java.sql.Connection;  
  2. import java.sql.DriverManager;  
  3. import java.sql.PreparedStatement;  
  4. import java.sql.ResultSet;  
  5. import java.sql.SQLException;  
  6.   
  7. public class Test {  
  8.     public static void main(String[] args) throws SQLException {  
  9.         String para="/index.html' union all select * from probe -- ";  
  10.         Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mvbox""xx""xx");  
  11.         PreparedStatement ps=conn.prepareStatement("select * from probe where path=?");  
  12.         ps.setString(1, para);  
  13.         ResultSet rs=ps.executeQuery();  
  14.         while (rs.next()) {  
  15.             System.out.println(rs.getString("host")+":"+rs.getString("path"));  
  16.         }  
  17.         rs.close();  
  18.         ps.close();  
  19.         conn.close();  
  20.     }  

為何引數化可以防止注入?
作為MySQL JDBC驅動來說(5.1.31),其實就是對敏感字元做了轉義.

觀察 com.mysql.jdbc.PreparedStatement 的 setString方法

可以看到有如下的替換過程

  1.                String parameterAsString = x;  
  2.             boolean needsQuoted = true;  
  3.               
  4.             if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {  
  5.                 needsQuoted = false// saves an allocation later  
  6.                   
  7.                 StringBuffer buf = new StringBuffer((int) (x.length() * 1.1));  
  8.                   
  9.                 buf.append('\'');  
  10.       
  11.                 //  
  12.                 // Note: buf.append(char) is _faster_ than  
  13.                 // appending in blocks, because the block  
  14.                 // append requires a System.arraycopy()....  
  15.                 // go figure...  
  16.                 //  
  17.       
  18.                 for (int i = 0; i < stringLength; ++i) {  
  19.                     char c = x.charAt(i);  
  20.       
  21.                     switch (c) {  
  22.                     case 0/* Must be escaped for 'mysql' */  
  23.                         buf.append('\\');  
  24.                         buf.append('0');  
  25.       
  26.                         break;  
  27.       
  28.                     case '\n'/* Must be escaped for logs */  
  29.                         buf.append('\\');  
  30.                         buf.append('n');  
  31.       
  32.                         break;  
  33.       
  34.                     case '\r':  
  35.                         buf.append('\\');  
  36.                         buf.append('r');  
  37.       
  38.                         break;  
  39.       
  40.                     case '\\':  
  41.                         buf.append('\\');  
  42.                         buf.append('\\');  
  43.       
  44.                         break;  
  45.       
  46.                     case '\'':  
  47.                         buf.append('\\');  
  48.                         buf.append('\'');  
  49.       
  50.                         break;  
  51.       
  52.                     case '"'/* Better safe than sorry */  
  53.                         if (this.usingAnsiMode) {  
  54.                             buf.append('\\');  
  55.                         }  
  56.       
  57.                         buf.append('"');  
  58.       
  59.                         break;  
  60.       
  61.                     case '\032'/* This gives problems on Win32 */  
  62.                         buf.append('\\');  
  63.                         buf.append('Z');  
  64.       
  65.                         break;  
  66.   
  67.                     case '\u00a5':  
  68.                     case '\u20a9':  
  69.                         // escape characters interpreted as backslash by mysql  
  70.                         if(charsetEncoder != null) {  
  71.                             CharBuffer cbuf = CharBuffer.allocate(1);  
  72.                             ByteBuffer bbuf = ByteBuffer.allocate(1);   
  73.                             cbuf.put(c);  
  74.                             cbuf.position(0);  
  75.                             charsetEncoder.encode(cbuf, bbuf, true);  
  76.                             if(bbuf.get(0) == '\\') {  
  77.                                 buf.append('\\');  
  78.                             }  
  79.                         }  
  80.                         // fall through  
  81.   
  82.                     default:  
  83.                         buf.append(c);  
  84.                     }  
  85.                 }  
  86.       
  87.                 buf.append('\'');  
  88.       
  89.                 parameterAsString = buf.toString();  
  90.             } 


是不是可以使用 iBatis 的 $ 方式 增加靈活性,而在引數進入iBatis之前,手工進行一下敏感字元替換,而防止SQL隱碼攻擊呢?

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29254281/viewspace-2135866/,如需轉載,請註明出處,否則將追究法律責任。

相關文章