Notes on the way to security: JDBC's defense against SQL injection

Posted by cgf2000 on Sun, 27 Oct 2019 03:06:19 +0100

0x00 Preface

This article records my way of learning, directly into the body.

0x01 text

Why does SQL injection occur? For me, it will be summed up as one sentence: "the SQL statement executed by dynamic splicing contains untrusted data."
What is dynamic splicing? Look at the following SQL statement:

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

See the '+' sign in the statement, which means that param table and param name are not written in the statement, but I can pass parameters to achieve some of my purposes.
So if I have a student table:

teacher table:

I want to find out about hacker
Then there will be the following code:

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));
           		 }

So a statement is formed:

select * from student where name='hacker';

In this way, you can query the information of hacker:

But if I change hacker to hacker's or 1=1 × 3:
String param_name = "hacker' or 1=1#";
Then all the data in the student table is dump ed:

Then you can change student to student union select * from teacher, and the data in the teacher table will also be dump:

How to protect? This is the key point. When I used to dig SQL injection, I just gave the manufacturer such advice, but it may be a very vague concept for the manufacturer:
Now I write down the instance, so as to deepen my understanding of SQL injection at the same time.
1. Precompile:
Here, the PreparedStatement class is used for precompiling, and the following code will be available:

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));

Then there was an error:

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

Finally, after debugging, it is found that param_table cannot be bound, and field names cannot be bound, so precompilation and query may be performed by splicing. The code is as follows:

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));
            }

But param_table=student still generates injection here. If it is modified to:

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

Then:

This injection is more exotic. The where clause is spliced into the statement querying the teacher table:

Then I can only write the student in the statement:

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));
            }

In this case, change param#name to hacker's or 1=1ා:
Then we will query hacker 'or 1=1 × as the table name. We can't find this table, of course, there is no echo:

2. Stored procedure:
There is a stored procedure that operates on the student table:

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;

Then we can use the CallableStatement class to prevent injection. The code is as follows:

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));

You can see that SQL injected statements no longer work:

3. White list verification:
The previous precompiled and stored procedures can't operate on the table name. Here, the white list is used to filter the table name. The code is as follows:

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));
			}

Error will be reported:
4. Code the input
Here I use hexadecimal to code the input. The declaration and definition code of the method are as follows:

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();
		}

Use the method byte2HexStr to code the input param_name, code:

String param_name = "hacker' or 1=1#";
		Statement stmt = conn.createStatement();
		String hex_param_name = bytestoHex(param_name.getBytes());
		out.println("Coded param_name For:"+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));
		}

Because hacker's or 1=1 ා is encoded as 6861636B657227206F7220313D3123 and queried as a table name, no other information will be dump:

0x02 conclusion

The safety road is very long. It's important to stick to it!

Topics: SQL MySQL Stored Procedure JDBC