ASP.NET 防止 SQL 注入攻击 - 第一部分






4.56/5 (26投票s)
SQL 注入是 Web 应用程序中一种非常常见的攻击。本文解释了 SQL 注入是如何发生的以及如何防止它。
引言
安全是任何系统最重要的属性。提供安全的体验是赢得客户信任的关键原则之一。如今,几乎所有的网站都要求将用户的个人信息存储在服务器上,以便了解客户并提供更好的服务。组织有责任确保客户的数据是安全的,并且以安全的方式进行访问。
Web 应用程序的安全性对开发人员来说一直是个大问题,但提供安全的环境是赢得客户信任的关键原则之一。在这个 Web 应用程序时代,几乎所有的网站都是动态的,即数据库驱动的,并且会接受用户的海量数据。
当软件开发人员创建包含用户输入数据的动态数据库查询时,就会引入 SQL 注入漏洞。本文解释了如何在 ASP.NET 中防止 SQL 注入。
背景
SQL 注入攻击到底是什么?
SQL 注入是一种攻击,通过接受恶意、不安全、未经验证的用户输入,将非预期的 SQL 命令(语句)注入到数据库中。注入的 SQL 命令可以更改 SQL 语句并危及 Web 应用程序的安全性。如果您想详细了解 SQL 注入攻击,请访问以下链接:
利用 SQL 注入的方法
利用方法如下:
- 输入框
- 查询字符串 [GET]
如何利用?
在当今动态 Web 应用程序的世界中,获取用户输入并对其进行处理是必要的,因此我们需要编写各种 SQL 查询来根据用户输入处理数据。考虑以下查询。表 - `user_info`,列 - `userID`、`name`、`email`、`password`。
SELECT name, email FROM user_info WHERE userID = 1
我们可以将此查询分为两部分:
第一部分:查询部分 - `SELECT userID,email FROM user_info`
第二部分:输入部分 - `userID=1`
黑客通常对第一部分不感兴趣,他们只对如何将恶意查询插入到您的第二部分感兴趣。让我们看一个 SQL 注入如何被利用的例子。
Using the Code
- 假设我们有一个 `user_info` 表,其中包含一些数据。以下是脚本。
CREATE TABLE [dbo].[user_info]( [userID] [int] IDENTITY(1,1) NOT NULL, [name] [nvarchar](200) NULL, [email] [nvarchar](200) NULL, [password] [nvarchar](50) NULL, CONSTRAINT [PK_user_info] PRIMARY KEY CLUSTERED ( [userID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, _ IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO SET IDENTITY_INSERT [dbo].[user_info] ON INSERT [dbo].[user_info] ([userID], [name], [email], [password]) _ VALUES (1, N'Mayur Lohite', N'mayur@mayur.com', N'123456') INSERT [dbo].[user_info] ([userID], [name], [email], [password]) _ VALUES (2, N'John Doe', N'john@john.com', N'654321') INSERT [dbo].[user_info] ([userID], [name], [email], [password]) _ VALUES (3, N'Hacker', N'hack@hack.com', N'789123') SET IDENTITY_INSERT [dbo].[user_info] OFF
- 创建一个新的空 ASP.NET 网站项目。在其中添加以下两个页面:
- Default.aspx
- viewuser.aspx
- Default.aspx 的代码如下:
<%@ page language="C#" autoeventwireup="true" codefile="Default.aspx.cs" inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd "> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>SQL Injection Demo</title> </head> <body> <form id="form1" runat="server"> <div style="width: 50%; margin: 0 auto; text-align: center;"> <table> <tr> <td colspan="2"> <h2> SQL Injection Demo</h2> </td> </tr> <tr> <td> Search by userid <asp:textbox id="txtUserID" runat="server"> </asp:textbox> </td> <td> <asp:button id="btnSubmit" onclick="BtnSubmit_Click" runat="server" text="Search" /> </td> </tr> <tr> <asp:gridview id="gvUserInfo" width="100%" runat="server" datakeynames="userID" autogeneratecolumns="false"> <Columns> <asp:BoundField DataField="userID" HeaderText="userID" /> <asp:BoundField DataField="name" HeaderText="name" /> <asp:BoundField DataField="email" HeaderText="email" /> <asp:HyperLinkField DataNavigateUrlFields="userID" DataNavigateUrlFormatString="viewuser.aspx?userid={0}" Text="View User" HeaderText="action" /> </Columns> </asp:gridview> </tr> </table> </div> </form> </body> </html>
- Default.aspx.cs 的代码如下:
public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { DataSet dset = new DataSet(); SqlConnection conn = new SqlConnection (ConfigurationManager.ConnectionStrings["MyExpConnectionString"].ToString()); using (conn) { conn.Open(); SqlDataAdapter adapter = new SqlDataAdapter(); SqlCommand cmd = new SqlCommand("SELECT userID, name, email FROM user_info", conn); cmd.CommandType = CommandType.Text; adapter.SelectCommand = cmd; adapter.Fill(dset); gvUserInfo.DataSource = dset; gvUserInfo.DataBind(); } } } protected void BtnSubmit_Click(object sender, EventArgs e) { DataSet dset = new DataSet(); SqlConnection conn = new SqlConnection (ConfigurationManager.ConnectionStrings["MyExpConnectionString"].ToString()); using (conn) { conn.Open(); SqlDataAdapter adapter = new SqlDataAdapter(); string sqlQuery = string.Format("SELECT userID, name, email FROM user_info WHERE userID={0}", txtUserID.Text); SqlCommand cmd = new SqlCommand(sqlQuery, conn); cmd.CommandType = CommandType.Text; adapter.SelectCommand = cmd; adapter.Fill(dset); gvUserInfo.DataSource = dset; gvUserInfo.DataBind(); } } }
Default 页面的屏幕截图如下:
- viewuser.aspx 的代码如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="viewuser.aspx.cs" Inherits="viewuser" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>SQL Injection Demo</title> </head> <body> <form id="form1" runat="server"> <div style="width: 50%; margin: 0 auto; text-align: center;"> <table> <tr> <td colspan="2"> <h2> SQL Injection Demo</h2> </td> </tr> <tr> <td> <h3> Welcome <asp:Label ID="lblDetails" runat="server"></asp:Label> </h3> </td> </tr> </table> </div> </form> </body> </html>
- viewuser.aspx.cs 的代码如下:
public partial class viewuser : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (Request.QueryString["userid"] != null) { DataSet dset = new DataSet(); SqlConnection conn = new SqlConnection (ConfigurationManager.ConnectionStrings["MyExpConnectionString"].ToString()); using (conn) { conn.Open(); SqlDataAdapter adapter = new SqlDataAdapter(); string sqlQuery = string.Format ("SELECT name FROM user_info WHERE userID={0}", Request.QueryString["userid"]); SqlCommand cmd = new SqlCommand(sqlQuery, conn); cmd.CommandType = CommandType.Text; adapter.SelectCommand = cmd; adapter.Fill(dset); if (dset.Tables[0].Rows.Count > 0) { lblDetails.Text = dset.Tables[0].Rows[0]["name"].ToString(); ; } } } } }
Viewuser 页面的屏幕截图如下:
漏洞利用
方法 1:通过输入框。
A-1. 首先看 Default 页面,我们有一个 TextBox、一个 Button 和一个 GridView。在窗体加载时,所有数据都将显示在 GridView 中。我们有按 ID 搜索用户的功能。假设我在文本框中输入 1 并按按钮,它将显示与 userID = 1 相关的记录。
A-2. 现在,如果我们看一下上面 Default.aspx.cs 中的代码,有一个按钮点击事件,即:
protected void BtnSubmit_Click(object sender, EventArgs e)
查询被写成一个 `string`,并且用户输入被连接到它。
string sqlQuery = string.Format("SELECT userID, name, email FROM user_info WHERE userID={0}", txtUserID.Text);
A-3. 假设用户输入没有经过妥善验证,那么黑客或攻击者就可以将任何恶意查询连接到它。在这种情况下,我将使用 `UNION` 将另一个 `SELECT` 语句连接到 `txtUserID.Text`。
A-4. 我在文本框 (txtUserID
) 中输入了以下文本(不带引号):“1 UNION SELECT userID,email,password FROM user_info
”。
A-5. 现在完整的查询变成了:
string sqlQuery = SELECT userID, name, email FROM user_info _
WHERE userID=1 UNION SELECT userID,email,password FROM user_info
A-6. 如果我点击按钮,GridView 将显示两个 `SELECT QUERY` 的组合,并且用户的密码将被泄露。如果用于用户输入连接的查询没有进行任何输入验证,那么代码总是容易受到 SQL 注入攻击。
注意:我增加了 `textbox` 的大小以便更好地理解查询。
方法 2:查询字符串 [GET]
B-1. 现在请转到 default.aspx 并点击 `GridView` 上的 `viewuser` 链接。页面将重定向到 viewuser.aspx,并带有 `userid` 查询字符串参数。
B-2. 该页面通过用户名欢迎用户。用户名将通过查询字符串值中的 `userid` 找到。
B-3. 现在,如果我们看一下上面 viewuser.aspx.cs 中 `Form_Load` 事件的代码……
protected void Page_Load(object sender, EventArgs e)
查询被写成一个 `string`,并且查询字符串被连接到它。
string sqlQuery = string.Format("SELECT name _
FROM user_info WHERE userID={0}", Request.QueryString["userid"]);
B-4. 现在假设我将恶意 `Select` 查询附加到 `Request.QueryString["userid"]`,就像上面的方法一样,URL 变成了:
http://mayurlohite.com/viewsuer.aspx?userid=1 UNION SELECT password FROM user_info WHERE userID = 1
B-5. 如果我按 Enter 键,标签将显示与 `userID = 1` 相关的密码。
为什么会发生这种情况?
在上述两种方法中,查询都与用户输入连接,并且用户输入没有得到妥善验证。因此,攻击者利用这一点,并将恶意查询连接到它,攻击者就可以获取密码,安装后门。攻击者可以从 `sysobject` 操作整个数据库。
如何防止?
- 妥善验证用户输入
- 使用参数化 SQL 查询 (
sqlParameter
) 和存储过程
1. 验证用户输入
如果您的输入只接受 ID 或整数,请添加一些验证以仅接受数字。如果输入复杂,则使用正则表达式来识别正确的输入。
2. 参数化 SQL 查询和存储过程
参数化查询在运行 SQL 查询之前会正确替换参数。它完全消除了“脏”输入改变查询含义的可能性。使用参数化查询,除了通用注入外,您还可以处理所有数据类型,数字(int
和 float
)、字符串(包含引号)、日期和时间(当 .ToString()
未使用不变区域性调用且您的客户端移动到具有意外日期格式的计算机时,不会出现格式问题或本地化问题)。
我已经重写了上述代码,使其免受 SQL 注入的攻击。请看一看。
- ConnectionManager.cs 类的代码如下:
public class ConnectionManager { public static SqlConnection GetDatabaseConnection() { SqlConnection connection = new SqlConnection (Convert.ToString(ConfigurationManager.ConnectionStrings["MyExpConnectionString"])); connection.Open(); return connection; } }
- DataAccessLayer.cs 类的代码如下:
public class DataAccessLayer { public static DataSet DisplayAllUsers() { DataSet dSet = new DataSet(); using (SqlConnection connection = ConnectionManager.GetDatabaseConnection()) { try { SqlCommand command = new SqlCommand("spDisplayUserAll", connection); command.CommandType = CommandType.StoredProcedure; SqlDataAdapter adapter = new SqlDataAdapter(); adapter.SelectCommand = command; adapter.Fill(dSet); } catch (Exception ex) { throw; } return dSet; } } public static DataSet DisplayUserByID(int userID) { DataSet dSet = new DataSet(); using (SqlConnection connection = ConnectionManager.GetDatabaseConnection()) { try { SqlCommand command = new SqlCommand("spDisplayUserByID", connection); command.CommandType = CommandType.StoredProcedure; command.Parameters.Add("@userID", SqlDbType.Int).Value = userID; SqlDataAdapter adapter = new SqlDataAdapter(); adapter.SelectCommand = command; adapter.Fill(dSet); } catch (Exception ex) { throw; } return dSet; } } }
- Default.aspx 的代码如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>SQL Injection Demo</title> </head> <body> <form id="form1" runat="server"> <div style="width: 50%; margin: 0 auto; text-align: center;"> <table> <tr> <td colspan="2"> <h2> SQL Injection Demo</h2> </td> </tr> <tr> <td> Search by userid <asp:TextBox ID="txtUserID" runat="server"> </asp:TextBox> <<asp:RequiredFieldValidator ID="rfvUserID" ControlToValidate="txtUserID" Display="Dynamic" runat="server" ErrorMessage="Required"></asp:RequiredFieldValidator> <asp:RegularExpressionValidator ID="revUserID" runat="server" ErrorMessage="Numbers Only" ValidationExpression="[0-9]+" ControlToValidate="txtUserID" Display="Dynamic"></asp:RegularExpressionValidator> </td> <td> <asp:Button ID="btnSubmit" OnClick="BtnSubmit_Click" runat="server" Text="Search" /> </td> </tr> <tr> <asp:GridView ID="gvUserInfo" Width="100%" runat="server" DataKeyNames="userID" AutoGenerateColumns="false"> <Columns> <asp:BoundField DataField="userID" HeaderText="userID" /> <asp:BoundField DataField="name" HeaderText="name" /> <asp:BoundField DataField="email" HeaderText="email" /> <asp:HyperLinkField DataNavigateUrlFields="userID" DataNavigateUrlFormatString="viewuser.aspx?userid={0}" Text="View User" HeaderText="action" /> </Columns> </asp:GridView> </tr> </table> </div> </form> </body> </html>
- Default.aspx.cs 的代码如下:
public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { DataSet dset = DataAccessLayer.DisplayAllUsers(); if (dset.Tables[0].Rows.Count > 0) { gvUserInfo.DataSource = dset; gvUserInfo.DataBind(); } } } protected void BtnSubmit_Click(object sender, EventArgs e) { int userID = Convert.ToInt32(txtUserID.Text); DataSet dSet = DataAccessLayer.DisplayUserByID(userID); if (dSet.Tables[0].Rows.Count > 0) { gvUserInfo.DataSource = dSet; gvUserInfo.DataBind(); } } }
- viewuser.aspx 的代码如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="viewuser.aspx.cs" Inherits="viewuser" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>SQL Injection Demo</title> </head> <body> <form id="form1" runat="server"> <div style="width: 50%; margin: 0 auto; text-align: center;"> <table> <tr> <td colspan="2"> <h2> SQL Injection Demo</h2> </td> </tr> <tr> <td> <h3> Welcome <asp:Label ID="lblDetails" runat="server"></asp:Label> </h3> </td> </tr> </table> </div> </form> </body> </html>
- viewuser.aspx.cs 的代码如下:
public partial class viewuser : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (Request.QueryString["userid"] != null) { int userID = Convert.ToInt32(Request.QueryString["userID"]); DataSet dSet = DataAccessLayer.DisplayUserByID(userID); if (dSet.Tables[0].Rows.Count > 0) { lblDetails.Text = Convert.ToString(dSet.Tables[0].Rows[0]["name"]); } } } }
- 存储过程:
spDisplayUserAll
CREATE PROCEDURE spDisplayUserAll AS BEGIN SET NOCOUNT ON; SELECT userID, name, email FROM user_info END
- 存储过程:
spDisplayUserByID
CREATE PROCEDURE spDisplayUserByID @userID int = 0 AS BEGIN SET NOCOUNT ON; SELECT userID, name, email FROM user_info WHERE userID = @userID END
关注点
SQL 注入是 Web 应用程序中最常见的安全漏洞。没有进行验证的动态网页和不当的代码处理可能导致 SQLI,但通过了解正确的代码标准和技巧,我们可以成功地防止它。