SQL注入原理演示

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38234015/article/details/89480793

浅谈SQL注入

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

上面是百度百科对SQL注入的介绍,通过上面的介绍可以知道,所谓的SQL注入就是输入恶意SQL命令从数据库获取本不应该被获取到的数据或者信息。那么,什么样的命令能够达到这样的目的呢?

在正式开始介绍SQL注入原理之前,需要先介绍两个在SQL注入中被广泛使用的命令符,那就是“ or 1 = 1” 和 “ # ”。" or 1 = 1 "使得筛选条件永真,从而使其失去意义以达到操作所有的目的,“ # ”是SQL中的注释起始符,使得其后面的所有命令被注释。

SQL注入原理以及效果演示

我的数据库test中有一个student表,表中存储的数据如下:

 如果程序中不采取防范措施,可以定义这样一个方法:

public ResultSet select(String sno) {
	String sql = "select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '" + sno + "' limit 1";
	System.out.println("the statement:" + sql);
	try {
		if(connection == null) {
			connection = getConnection();
		}
		statement = connection.createStatement();
		resultSet = statement.executeQuery(sql);
	}catch(SQLException ex) {
	System.out.println(ex.getMessage());
		}
	return resultSet;
}

这个方法的作用是通过传入的sno参数,从数据库表当中获取某个指定学生的信息。

如果用户输入正常,是如下效果:

public static void main(String[] args) {
	DataBaseUtil util = new DataBaseUtil();
	ResultSet set1 = util.select("001");
	try {
		while (set1.next()) {
			System.out.print(set1.getString("Sno") + "\t");
			System.out.print(set1.getString("Sname") + "\t");
			System.out.print(set1.getString("Sage") + "\t");
			System.out.print(set1.getString("Ssex") + "\t");
			System.out.print(set1.getString("Sdept") + "\t");
			System.out.println();
		}
	} catch (SQLException e) {
		// TODO Auto-generated catch block
	    e.printStackTrace();
	}
}
the statement:select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '001' limit 1
001	张华	23	男	软件工程专业

但是,如果用户输入的信息是经过特殊设计的,结果可能是这样的:

public static void main(String[] args) {
	DataBaseUtil util = new DataBaseUtil();
	ResultSet set1 = util.select("000' or 1=1 #");
	try {
		while (set1.next()) {
			System.out.print(set1.getString("Sno") + "\t");
			System.out.print(set1.getString("Sname") + "\t");
			System.out.print(set1.getString("Sage") + "\t");
			System.out.print(set1.getString("Ssex") + "\t");
			System.out.print(set1.getString("Sdept") + "\t");
			System.out.println();
		}
	} catch (SQLException e) {
		// TODO Auto-generated catch block
	    e.printStackTrace();
	}
}
the statement:select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '000' or 1=1 #' limit 1

001	张华	23	男	软件工程专业	
002	李华	18	男	大数据科学与技术	
003	耿耿	18	女	数字媒体与艺术	
004	余淮	18	男	大数据科学与技术	

是的,没错,数据库表中的所有信息全部被查询出来了,尽管表中没有学号是000的学生,最后不仅是没有查询结果,反而所有信息全部被显示了。

原因:

用户输入的指令是   000' or 1=1 #   ,而这条指令没有经过任何处理,被直接拼接成为一条SQL语句,使得数据库执行的语句成为 select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '000' or 1=1 #' limit 1

原本根据学号用于筛选的where语句因为和 or 1 = 1 拼接,成为一个永真条件,在筛选中,永真条件相当于没有,所以语句相当于 select Sno,Sname,Ssex,Sage,Sdept from student   。那么,之前语句中用来将用户输入信息包装成字符类型的引号呢?用于限定只能查询一条数据的 limit 1 呢? 不要忘记文章起始时说过的 # 标记。 #' limit 1 ,这仅仅时一条注释。

防范措施

既然有的用户比较调皮,容易输入一些不符合期望的内容,那么,程序设计的时候,可以在接收用户输入的文本框做输入限制。

但是,有的文本框的输入内容并不是能够指定的类型,或者有的用户调皮到可以采用其他方式向后台提交一些什么稀奇古怪的数据。这个时候可以使用PrepareStatement而不是Statement执行SQL命令。

Statement一般只用于执行静态SQL语句,PrepareStatement用于执行预编译的动态SQL命令。

public ResultSet selectUsePre(String sno) {
	String sql = "select Sno,Sname,Ssex,Sage,Sdept from student where Sno = ?";
	try {
		if(connection == null) {
			connection = getConnection();
		}
		preStatement = connection.prepareStatement(sql);
		preStatement.setString(1, sno);
		System.out.println(preStatement);
		resultSet = preStatement.executeQuery();
	}catch(SQLException ex) {
		System.out.println(ex.getMessage());
	}
	return resultSet;
}
public static void main(String[] args) {
	DataBaseUtil util = new DataBaseUtil();
	ResultSet set2 = util.selectUsePre("000' or 1=1 #");
	try {
		while (set2.next()) {
			System.out.print(set2.getString("Sno") + "\t");
			System.out.print(set2.getString("Sname") + "\t");
			System.out.print(set2.getString("Sage") + "\t");
			System.out.print(set2.getString("Ssex") + "\t");
			System.out.print(set2.getString("Sdept") + "\t");
		    System.out.println();
		}
	} catch (SQLException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}
com.mysql.cj.jdbc.ClientPreparedStatement: select Sno,Sname,Ssex,Sage,Sdept from student where Sno = '000\' or 1=1 #'

PrepareStatement:

使用PrepareStatement,会将输入的参数进行转义,从一定程度上达到防止SQL注入的目的。

总结

程序设计中,对于动态SQL语句,不要使用字符串拼接的方式形成SQL命令并执行。如果条件允许,应该首先对用户输入进行判断并使用预编译。当然,上面仅仅是一个小小的案例演示,实际的SQL注入形式多种多样而且破坏性往往很强。对SQL注入的预防也应该是全方位的,不能仅仅依靠输入判断或者预编译。

猜你喜欢

转载自blog.csdn.net/qq_38234015/article/details/89480793