何为注入攻击?让我们先看一个小例子,我们编写一段程序,根据用户指定的name值返回记录。
String sql = " select * from test where name=' " + name + " ' " ;
state = conn.createStatement();
rs = state.executeQuery(sql);
// 显示查询结果
while (rs.next()) {
System.out.println(rs.getInt(1) + " " + rs.getString(2));
}
这里我们直接使用用户传递过来的name变量拼接了一条sql语句进行查询。在String name = "lingirl2"的时候程序会返回正确的结果。
上面的例子里,我们乐观的认为用户输入的都是正常的字符串,没有考虑到恶意攻击的情况,如果用户输入了这样一段内容:
String name = "xxx' or '1'='";
经过拼接得到的sql就变成了这样。
select * from test where name='xxx' or '1'='1'
好啦,这会搜索出所有满足name='xxx'或者满足'1'='1'条件的记录,结果变成搜索test库中所有的记录了。
当String name = "xxx' or '1'='";的时候,查询结果如下:
所谓sql注入攻击,是因为程序没有对用户输入进行校验,造成用户可以在输入中包含恶意代码篡改程序功能。上面的例子仅仅是造成数据泄密,更严重的用户还可能窃取最高管理权限,删除数据库中所有的数据。
为了解决这个问题,我们可以使用PreparedStatement,修改后的代码如下。
import java.sql. * ;
public class Select3 {
public static void main(String[] args) throws Exception {
String name = "xxx' or '1'='1";
Connection conn = DbUtils.getConnection();
PreparedStatement state = null;
ResultSet rs = null;
try {
String sql = "select * from test where name=?";
state = conn.prepareStatement(sql);
state.setString(1, name);
rs = state.executeQuery();
// 显示查询结果
while (rs.next()) {
System.out.println(rs.getInt(1) + " " + rs.getString(2));
}
} catch(Exception ex) {
ex.printStackTrace();
} finally {
if (rs != null) {
rs.close();
}
if (state != null) {
state.close();
}
DbUtils.close(conn);
}
}
}
通过如下几步将Statement改为PreparedStatement。
-
sql语句修改为
select * from test where name=?
注意这个问号(?)就是我们需要传递参数的地方。
-
使用prepareStatement获得PreparedStatement变量。
state = conn.prepareStatement(sql);
与createStatement不同,创建的PreparedStatement的同时要传入sql语句,让PreparedStatement对sql语句进行预处理,以备后用。
-
传入name参数。
state.setString(1, name);
这里的参数“1”代表使用name替代sql中的第一个问号“?”,name中有什么特殊字符,PreparedStatement会帮助咱们自动转换。
-
最后执行查询,rs = state.executeQuery();因为创建PreparedStatement的时候就已经对sql进行了处理,这里直接执行查询就能得到结果。
看一下使用PreparedStatement查询String name = "xxx' or '1'='";的情况。
实际上,自己手工拼接sql非常容易出错,即便不担心注入攻击也应该尽量使用PreparedStatement,至少可以减小写错sql的机会。
最后放上一个使用PreparedStatement进行插入的例子,可以自己写一个不使用PreparedStatement的例子对比一下。
import java.sql. * ;
public class Insert {
public static void main(String[] args) throws Exception {
int id = 1;
String name = "lingirl";
Connection conn = DbUtils.getConnection();
PreparedStatement state = null;
try {
String sql = "insert into test(id,name) values(?,?)";
state = conn.prepareStatement(sql);
state.setInt(1, id);
state.setString(2, name);
state.executeUpdate();
} catch(Exception ex) {
ex.printStackTrace();
} finally {
if (state != null) {
state.close();
}
DbUtils.close(conn);
}
}
}