網絡安全自學篇-滲透測試(隨筆二)

JDBC對SQL注射的防禦

SQL注射爲何會產生,對於我來說,則會將其總結爲一句話:“被動態拼接執行的SQL語句中包含了不可信任的數據。”
什麼是動態拼接?看看下面這條SQL語句:

select * from "+param_table+" where name='"+param_name+"'";

看到語句中的‘+’號了麼,這意味着param_table和param_name並不是寫死在語句中的,而我可以對其進行傳參從而達到我的某些目的。
那麼假如我有student表:
在這裏插入圖片描述
teacher表:
在這裏插入圖片描述
我想從中查詢hacker的信息
那麼將有如下代碼:

String param_table = "student";
String param_name = "hacker";
Statement stmt = conn.createStatement();
ResultSet rs =  stmt.executeQuery("select * from "+param_table+" where name='"+param_name+"'");
while(rs.next()) {
     out.println(rs.getString(1)+"/"+
                 rs.getString(2)+"/"+
                 rs.getString(3));
           		 }

於是構成了這樣一條語句:

select * from student where name=’hacker’;

這樣就可以查詢到hacker的信息:
在這裏插入圖片描述
但是如果我將hacker修改爲hacker’ or 1=1#:
String param_name = “hacker’ or 1=1#”;
則student表中所有數據被dump出來:
在這裏插入圖片描述
接着也可以將student修改爲student union select * from teacher,於是連同teacher表的數據也被dump出來:
在這裏插入圖片描述
那該如何防護?這是重點,我以前挖SQL注入的時候,僅僅是給廠商提供了這樣的建議,但對於廠商來說可能只是極其模糊的概念:
在這裏插入圖片描述現在我寫下實例,以便同時也加深自己對SQL注入的理解。
1.預編譯:
這裏用到PreparedStatement類進行預編譯,那麼將有如下代碼:

String param_table = "student";
String param_name = "hacker";
String stmt = "select * from ? where name= ?";
PreparedStatement ps = conn.prepareStatement(stmt);
ps.setString(1,param_table);
ps.setString(2,param_name);
ResultSet rs =  ps.executeQuery();
while(rs.next()) {
      out.println(rs.getString(1)+"/"+
                  rs.getString(2)+"/"+
                  rs.getString(3));

接着運行卻出現了錯誤:

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''student' where name= 'hacker'' at line 1

最後經過調試發現param_table不能被綁定,並且發現字段名也不能被綁定,那麼可能會用拼接的方式進行預編譯再查詢,代碼如下:

String param_table = "student";
String param_name = "hacker";
PreparedStatement ps = conn.prepareStatement("select * from "+param_table+" where name=?");
ps.setString(1,param_name);
ResultSet rs =  ps.executeQuery();
while(rs.next()) {
      out.println(rs.getString(1)+"/"+
                  rs.getString(2)+"/"+
                  rs.getString(3));
            }

但是param_table=student這裏依舊產生了注入,如果修改爲:

String param_table = "student union select * from teacher";

則:
在這裏插入圖片描述
這個注入比較奇葩點,where子句被拼接到查詢teacher表的語句上:
在這裏插入圖片描述
那麼我只能把student寫死在語句中:

String param_name = "hacker";
String stmt = "select * from student where name=?";
PreparedStatement ps = conn.prepareStatement(stmt);
ps.setString(1,param_name);
ResultSet rs =  ps.executeQuery();
while(rs.next()) {
      out.println(rs.getString(1)+"/"+
                  rs.getString(2)+"/"+
                  rs.getString(3));
            }

此時再將param_name修改爲hacker’ or 1=1#:
則會將hacker’ or 1=1#當做表名來查詢,查不到這個表,當然無回顯了:
在這裏插入圖片描述
2.存儲過程:
有這樣一個對student表操作的存儲過程:

create procedure `getstudent`(in aname varchar(20),out uname varchar(20),out uage int(11),out usex varchar(10))
begin
select * from student where name=aname into uname,uage,usex;
end;

那麼我們可以用CallableStatement類來防止注入,代碼如下:

String param_name = "hacker’ or 1=1#";
CallableStatement cs = conn.prepareCall("{call getstudent(?,?,?,?)}");
cs.setString(1,param_name);
cs.registerOutParameter(2,Types.VARCHAR);
cs.registerOutParameter(3,Types.INTEGER);
cs.registerOutParameter(4,Types.VARCHAR);
cs.executeQuery();
out.println(cs.getString(2)+"/"+
			cs.getInt(3)+"/"+
			cs.getString(4));

可以看到SQL注入的語句已經不再起作用:
在這裏插入圖片描述
3.白名單驗證:
前面的預編譯和存儲過程不能對錶名進行操作,那麼這裏用白名單對錶名進行過濾,代碼如下:

String param_table = "student union select * from teacher";
String param_name = "hacker";
String stmt = "";
if(param_table.equals("student")) {
		stmt = "select * from student where name=?";
			}
else if(param_table.equals("teacher")) {
		stmt = "select * from teacher where name=?";
			}
else {
		out.println("table name error!");
			}
PreparedStatement ps = conn.prepareStatement(stmt);
ps.setString(1,param_name);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
      out.println(rs.getString(1)+"/"+
				  rs.getString(2)+"/"+
				  rs.getString(3));
			}

則會報錯:
在這裏插入圖片描述4.對輸入進行編碼
這裏我使用十六進制對輸入進行編碼,方法的聲明及定義代碼如下:

public static String bytestoHex(byte[] byteArr) {
			if(byteArr == null || byteArr.length < 1) return "";
			StringBuilder sb = new StringBuilder();
			for(byte t : byteArr) {
				if((t & 0xF0) == 0) sb.append("0");
				sb.append(Integer.toHexString(t & 0xFF));
			}
			return sb.toString().toUpperCase();
		}

使用方法byte2HexStr對輸入param_name進行編碼,代碼:

String param_name = "hacker' or 1=1#";
		Statement stmt = conn.createStatement();
		String hex_param_name = bytestoHex(param_name.getBytes());
		out.println("編碼後的param_name爲:"+bytestoHex(param_name.getBytes()));
		ResultSet rs = stmt.executeQuery("select * from student where hex(name)='"+hex_param_name+"'");
		while(rs.next()) {
			out.println(rs.getString(1)+"/"+
						rs.getString(2)+"/"+
						rs.getString(3));
		}

由於hacker’ or 1=1#被編碼爲6861636B657227206F7220313D3123並被作爲表名進行查詢,因此不會dump出其他信息:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章