当前位置:office办公软件学习-Excel教程-PPT教程-Word教程 > 电脑达人 > 电脑安全 > 安全知识 > 什么是SQL注入攻击?如何防范?

什么是SQL注入攻击?如何防范?

时间:2018-12-14 08:32来源:office办公达人 作者:office办公达人整理 阅读:
【导读】:SQL注入攻击是黑客对数据库进行攻击的常用手段之一。用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入。

什么是SQL注入攻击?如何防范?

什么是SQL注入攻击

 

SQL注入攻击是黑客对数据库进行攻击的常用手段之一。随着B/S模式应用开发的发展,使用这种模式编写应用程序的程序员也越来越多。但是由于程序员的水平及经验也参差不齐,相当大一部分程序员在编写代码的时候,没有对用户输入数据的合法性进行判断,使应用程序存在安全隐患。用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入。

 

SQL注入攻击属于数据库安全攻击手段之一,可以通过数据库安全防护技术实现有效防护,数据库安全防护技术包括:数据库漏扫、数据库加密、数据库防火墙、数据脱敏、数据库安全审计系统。

 

SQL注入攻击会导致的数据库安全风险包括:刷库、拖库、撞库。

 

SQL注入攻击原理

 

一个Web应用程序,除非其特别简单,一般情况下,都是需要用到数据库的。而数据库有数据库自身的特点和安全问题。其中最为有名、最为广泛的攻击是SQL注入。而除了SQL注入,不同的数据库也还有其他安全问题需要解决。

数据库大都是支持SQL语言的(如果你想学习SQL查询语言,请访问我们的SQL语言教程),这就意味着,SQL语言本身是有预留关键字的,比如常用的selectupdatedelete等。但是程序语言往往没有把这些列为保留字。那么,问题就来了,如果一个别有用心的人,故意输入一个SQL查询语句,而我们没有执行相关验证,会发生什么?轻者程序错误,代码泄露,重者直接暴库,甚至危及服务器整体安全。这就是SQL注入攻击。

可见,SQL注入本质上是一种用户输入式攻击,是程序没有对用户输入进行充分验证留下的漏洞。

SQL注入方式

 

SQL注入是从正常的WWW端口访问,而且表面看起来跟一般的Web页面访问没什么区别,所以市面的防火墙都不会对SQL注入发出警报,如果管理员没查看ⅡS日志的习惯,可能被入侵很长时间都不会发觉。但是,SQL注入的手法相当灵活,在注入的时候会碰到很多意外的情况,需要构造巧妙的SQL语句,从而成功获取想要的数据。

 

SQL注入总体思路

 

·发现SQL注入位置;

·判断后台数据库类型;

·确定XP_CMDSHELL可执行情况

·发现WEB虚拟目录

·上传ASP木马;

·得到管理员权限;

 

应对办法

 

从安全技术手段上来说,可以通过数据库防火墙实现对SQL注入攻击的防范,因为SQL注入攻击往往是通过应用程序来进攻,可以使用虚拟补丁技术实现对注入攻击的SQL特征识别,实现实时攻击阻断。

 

攻击示例

这里,我们假设有一个页面,接收html传输来的用户名和密码,其控件名分别为txtUserIDtxtPassword,这些用户输入的用户名和密码被加到程序语句的SQL查询语句中,用来被验证用户身份。老式的ASP代码如下所示:

set rs=server.createobject("adodb.recordset")sql="select count(UserID) from users where UserID
='"&request.form("txtUserID")&"' and Password='"&request.form("txtPassword")&"'"rs.open sql,conn,1,1

VB.net代码如下:

Dim query AsString="select count(UserID) from users where UserID
='"&txtUserID.Text&"' and Password='"&txtPassword.Text&"'"

C#的代码如下:

string query ="select count(UserID) from users where UserID
='"&txtUserID.Text&"' and Password='"&txtPassword.Text&"'"

php代码如下:

$query = mysql_query("select count(UserID) from users where UserID
='".$_POST["txtUserID"]."' and Password='".$_POST["txtPassword.Text"]."'");

jsp代码如下:

String query="SELECT count(UserID) FROM users where UserID
='"+request.getParameter("txtUserID")+"' and Password
='"+request.getParameter("txtPassword")+"'";

我们的示例代码是很危险的!因为它们都没有经过审核验证用户的输入就将其加入到SQL查询语句中。当然,如果此时执行的验证完全只考虑XSS等其他类型的用户输入型攻击,危险仍然是存在的。让我们试图想象一下,攻击者输入了一个用户名:“'or 1=1 --”(不含双引号),那么整条SQL查询语句就会变成下面的样子:

SELECT * FROM users whereUserID=''or1=1--andPassword=''

也就是说,单引号'先是结束掉了UserID的查询,然后跟一个or语句,而1=1永远都会是true,所以错误便会出现,数据库里users 表所有的记录都将被加入程序计数,如果这时以是否取到了记录作为用户名密码是否有效的标准,那么攻击者就将会被授权。

还有更暴力的,直接会把你的表乃至整个数据库整个删除,后果极其严重:

SELECT * FROM users whereUserID=''or1=1; DROP DATABASE (dbname)--andPassword=''

如何防范SQL注入攻击

SQL注入能够成功的根本在于,程序没有对用户输入进行有效的验证。那么解决思路就很简单了,要么彻底验证用户的输入再将其并入SQL语句;要么将用户输入作为参数传入SQL语句,而不是简单的字符串拼接。

我们先来说第一种,将用户的输入进行彻底验证再将其并入SQL语句,是一个非常高效、简便的方法,当然,它不是绝对安全的,因为如果你验证的不够彻底,就会给攻击者留下可乘之机。不过因为其简便性,也是很多开发者选择的防范方法。那么,需要怎么验证呢?我们可以这样验证:验证输入是否含单引号(')、双引号(")、连接符(-)、分号(;)、括号(())等特殊字符(都是英文状态下的),如果用户的输入中,含有这些字符,那么一律验证不通过。这里列出一个特殊字符列表,供大家参考:

;|%|\|>|<|--|^|(|)|+|$|'|*|\"

你也可以使用正则表达式来验证,如只接受汉字、数字和英文字符:

[A-Za-z0-9_-\u4e00-\u9fa5]+

然后,我们可以考虑,将常见的SQL操作关键字,如selectupdateinsertandordropalter等等一律干掉,当然,这个适用于用户不太可能会输入这些字符的情况下,具体的关键词可以参考我们的SQL语言教程。

我们再来看第二种,将用户输入作为参数传给处理程序,这种方法相较第一种方法更加安全,因为你如果忘记过滤某个特殊字符也可能带来危险,但这种方法稍显麻烦。PHP.netjsp都提供参数化查询的方式,它们都把用户输入作为单纯的字符串处理。

1ASP.net

.net中,SQL存储过程可以接受多种类型的参数,参数化SQL查询使用 @作为前缀,C#代码如下例所示:

string sql="select count(email) from users where emai
l= @Username and password = @Password";
//使用其他地方定义的连接字符串SqlConnection connection =newSqlConnection(connectionString);
//
这里txtName txtPass runat="server" Web控件SqlCommand command
=newSqlCommand(sql,connection);command.Parameters.Add
("@Username",txtName.Text);command.Parameters.Add
("@Password",txtPass.Text);connection.Open();
int count=(int)command.ExecuteScalar();connection.Close();

VB.net代码如下所示:

Dim sql AsString="select count(EmailName) from users where email
= @Username and password = @Password"'
使用其他地方定义的连接字符串Dim connection As New SqlConnection
(connectionString)Dim command As New SqlCommand(sql,connection)
'
这里txtName txtPass runat="server"Web控件command.Parameter.Add
("@Username",txtUser.Text)command.Parameter.Add
("@Password",txtPass.Text)connection.Open()Dim count AsIntegercount=
Ctype(command.ExecuteScalar(),Integer)connection.Close()

2PHP

PHP通过使用预编译语句(prepared statements)和参数化查询(parameterized queries)。这些sql语句从参数,分开的发送到数据库服务端,进行解析。

有两种方式去完成这个,一是使用PDO对象(适用任何数据库驱动);第二是使用MySqli。使用PDO对象的代码如下:

$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
$stmt->execute(array('name'=> $name));foreach($stmt as $row){// do something with $row}

当使用PDO去连接Mysql数据库时,真正的预处理默认并没有开启。这时就应该关闭模拟的预处理语句:

$dbConnection =new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8','user','pass');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

1行的setAttribute()函数是必须的,作用是关闭模拟预处理,并使用真正的预处理语句。这将保证语句和值在被交到Mysql服务器上没有被解析。而第2行的setAttribute()函数不是必要的,但是推荐你加上去。这样,脚本在遇到致命错误(Fatal Error)时不会停止运行,并且我们可以去捕获PDOException异常。此外,你可以在构造函数里设置字符集(charset),但旧版本的PHP<5.3.6)会忽略在DSN中设置的字符集参数。

第二种,使用MySqli的代码如下:

$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name);$stmt->execute();$result = $stmt->get_result();
while($row = $result->fetch_assoc()){// do something with $row}

3JSP

JSP中使用PreparedStatement类中的诸多setXxxx方法来实现。PreparedStatement接口继承Statement,并与之在两方面有所不同:一、PreparedStatement 实例包含已编译的 SQL 语句。这就是使语句准备好。包含于 PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数。IN参数的值在 SQL 语句创建时未被指定。相反的,该语句为每个 IN 参数保留一个问号()作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX 方法来提供。二、由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。

作为 Statement 的子类,PreparedStatement 继承了 Statement 的所有功能。另外它还添加了一整套方法,用于设置发送给数据库以取代 IN 参数占位符的值。同时,三种方法 executeexecuteQuery executeUpdate 已被更改以使之不再需要参数。这些方法的 Statement 形式(接受 SQL 语句参数的形式)不应该用于 PreparedStatement 对象。

以下的代码段(其中 con Connection 对象)创建包含带两个 IN 参数占位符的 SQL 语句的 PreparedStatement 对象:

PreparedStatement pstmt =
con.prepareStatement("UPDATE table4 SET m = ? WHERE x = ?");

pstmt 对象包含语句 "UPDATE table4 SET m = ? WHERE x = ?",它已发送给DBMS,并为执行作好了准备。

在执行 PreparedStatement 对象之前,必须设置每个 ? 参数的值。这可通过调用 setXXX 方法来完成,其中 XXX 是与该参数相应的类型。例如,如果参数具有Java 类型 long,则使用的方法就是 setLongsetXXX 方法的第一个参数是要设置的参数的序数位置,第二个参数是设置给该参数的值。例如,以下代码将第一个参数设为 123456789,第二个参数设为 100000000

pstmt.setLong(1,123456789);pstmt.setLong(2,100000000);

一旦设置了给定语句的参数值,就可用它多次执行该语句,直到调用clearParameters 方法清除它为止。在连接的缺省模式下(启用自动提交),当语句完成时将自动提交或还原该语句。

如果基本数据库和驱动程序在语句提交之后仍保持这些语句的打开状态,则同一个 PreparedStatement 可执行多次。如果这一点不成立,那么试图通过使用PreparedStatement 对象代替 Statement 对象来提高性能是没有意义的。

利用 pstmt(前面创建的 PreparedStatement 对象),以下代码例示了如何设置两个参数占位符的值并执行 pstmt 10 次。如上所述,为做到这一点,数据库不能关闭 pstmt。在该示例中,第一个参数被设置为 "Hi"并保持为常数。在 for 循环中,每次都将第二个参数设置为不同的值:从 0 开始,到 9 结束。

pstmt.setString(1,"Hi");for(int i =0; i <10; i++){ pstmt.setInt(2, i); int rowCount = pstmt.executeUpdate();}

setXXX 方法中的 XXX Java 类型。它是一种隐含的 JDBC 类型(一般 SQL 类型),因为驱动程序将把 Java 类型映射为相应的 JDBC 类型(遵循该 JDBCGuide§8.6.2 “映射 Java JDBC 类型表中所指定的映射),并将该 JDBC 类型发送给数据库。例如,以下代码段将 PreparedStatement 对象 pstmt 的第二个参数设置为 44Java 类型为 short

pstmt.setShort(2,44);

驱动程序将 44 作为 JDBC SMALLINT 发送给数据库,它是 Java short 类型的标准映射。

程序员的责任是确保将每个 IN 参数的 Java 类型映射为与数据库所需的 JDBC 数据类型兼容的 JDBC 类型。不妨考虑数据库需要 JDBC SMALLINT 的情况。如果使用方法 setByte ,则驱动程序将 JDBC TINYINT 发送给数据库。这是可行的,因为许多数据库可从一种相关的类型转换为另一种类型,并且通常 TINYINT 可用于SMALLINT 适用的任何地方。

预处理语句对象PreparedStatement,使用PreparedStatement进行添加数据,更新数据,删除数据和查询数据。

添加数据的例子(注意一定要注意大小写):

<%@page language="java" contentType="text/html;charset=gb2312"%><%@pageimport="java.sql.*" %>
获得第二条记录开始的三条记录<%String url ="jdbc:mysql://localhost:3306/javaweb";
//
连接数据库的url地址String user ="root";//登录数据库的用户名String password ="zhangda890126;;";
//
登录数据库的用户名的密码Connection conn =null;//链接对象PreparedStatement pstmt =null;//语句对象//ResultSet rs = null;
//
结果集对象try{Class.forName("com.mysql.jdbc.Driver");//加载JDBC驱动程序 conn =DriverManager.getConnection(url,user,password);
//
连接数据库}catch(ClassNotFoundException e){out.println("找不到驱动类");
//
抛出异常时,提示信息}catch(SQLException e){out.println("链接MySQL数据库失败");
//
处理SQLException异常}try{String adduser ="INSERT INTO user (userid,username,password) VALUES(null,?,?)";
//
添加一条用户信息 pstmt = conn.<span style="color:#e53333;"><b>prepareStatement</b></span>(adduser);
//
创建预处理语句对象PreparedStatement//设置参数 pstmt.setString(1,"YAO"); pstmt.setString(2,"yao");
//
执行语句 pstmt.executeUpdate();}catch(SQLException e){out.println("添加用户信息失败");}try{if(pstmt !=null){ pstmt.close();
conn =null;}if(conn !=null){ conn.close(); conn =null;}}catch(Exception e){out.println("
数据库关闭失败");} %>

分享到
更多
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
栏目列表
文章点击榜
推荐内容
最新文章
关于我们 | 联系我们 | 友情链接 | 版权声明 | 网站地图 | 帮助

网站为公益性网站,部分内容来源网络,如无意中侵犯了您的版权,请来信告知,我们会在第一时间处理
CopyRight© 2013-2018,www.officedoyen.com 版权所有   技术支持:泉州网站建设  闽ICP备14010062号