2023年6月21日发(作者:)
如何防⽌SQL注⼊?使⽤参数化命令!转载⾃
与数据库交互的 Web 应⽤程序中最严重的风险之⼀:SQL 注⼊攻击。 SQL 注⼊是应⽤程序开发⼈员未预期的把 SQL 代码传⼊到应⽤程序的过程,它由于应⽤程序的糟糕设计⽽使攻击成为可能,并且只有那些直接使⽤⽤户提供的值构建SQL 语句的应⽤程序才会受影响。
问题在于命令时如何被执⾏的。SQL 语句通过字符串的构造技术动态创建,⽂本框的值被直接复制到字符串中,他可能是这样的:string sql = "SELECT * FROM Orders where CustomerID = '" + + "'"; 在这个⽰例中,⽤户可以篡改 SQL 语句,通常,攻击的第⼀个⽬标是得到错误信息。如果错误没有被恰当处理,底层的信息就会暴露给攻击者。现在若是在⽂本框中输⼊:ALFKI' OR '1'='1,再看看这条 SQL 语句,它现在是这样的:string sql = "SELECT * FROM Orders where CustomerID = 'ALFKI' OR '1'='1'"; 这样产⽣的后果是没有显⽰当前⽤户的特定信息,却向攻击者提供了全部资料,如果屏幕上显⽰的是敏感信息,如社会保险号,⽣⽇或者信⽤卡资料等,就会带来严重的问题!
还可以进⾏更复杂的攻击!例如攻击者可以利⽤两个连接号(--)注释掉 SQL 语句剩余部分,虽然这样的攻击只限于SQL Server,不过对于其他类型的数据库也有等效的办法。另外,攻击者还可以执⾏含有任意 SQL 语句的批处理命令,对于 SQL Server,攻击者只需加上分号(;),攻击者⽤这样的⽅式还可以删除其他表的内容。甚⾄调⽤SQL Server 的系统存储过程 xp_cmdshell 在命令⾏执⾏任意的程序。
下⾯是攻击者在⽂本框中输⼊的,他的⽬的是删除 Customers 表的全部⾏:ALFKI' ; DELETE * FROM Customers,得到的 SQL 语句是这样:"SELECT * FROM Orders where CustomerID = 'ALFKI';DELETE * FROM Customers"
如何防⽌ SQL 注⼊攻击?预防⼿段:使⽤ gth 属性防⽌⽤户输⼊过长的字符,这样减少了贴⼊⼤量的脚本的可能性使⽤ 验证控件锁定错误的数据限制错误信息给出的提⽰,捕获到异常时只显⽰⼀些通⽤的信息,⽽不是显⽰ e 属性中的信息,它会暴露出系统的攻击点更为重要的是,⼀定要⼩⼼去掉特殊字符,⽐如将单引号替换为两个单引号最好的解决⽅法是使⽤参数化的命令或者使⽤存储过程执⾏转义以防⽌ SQL 注⼊攻击使⽤参数化命令: 参数化命令是在 SQL ⽂本中使⽤占位符的命令,占位符表⽰需要动态替换的值,它们通过 Command 对象的 Parameters 集合来传送。 例如下⾯这条SQL语句:SELECT * FROM Customers where CustomerID = 'ALFKI' 可以写成这样:SELECT * FROM Customers where CustomerID = @CustID占位符随后单独提供并被⾃动编码
为每个参数创建⼀个 Parameter 对象,这些对象被加⼊到 ters 集合中。下⾯的⽰例重写前⾯的代码防⽌可能的 SQL 注⼊攻击:string connStr = tionStrings["Northwind"].ConnectionString;SqlConnection conn = new SqlConnection(connStr);
// 这⾥使⽤参数化的 SQL 语句string sql = "SELECT * FROM Customers where CustomerID = @CustID";SqlCommand cmd = new SqlCommand(sql, conn);
// 这⾥配置 ters 集合hValue("@CustID", );();SqlDataReader reader = eReader(); 若在修改后的页⾯上再次尝试 SQL 注⼊攻击,将得不到任何记录。因为没有客户 ID 值与 ⽂本框输⼊的 ALFKI ' OR '1' = '1' 相等的订单项,这正是我们期待的结果。
调⽤存储过程 参数化命令时调⽤完整功能存储过程的诸多命令中的⼀⼩部分。 存储过程当然是保存在数据库上的批次执⾏的⼀条或多条SQL语句。它们是良好的逻辑封装体,可以接收(输⼊参数)和返回(输出参数)数据。 存储过程有很多优点:更易于维护:例如,你可以优化存储过程中的命令⽽不必重新编译使⽤它的程序。可以更安全地使⽤数据库:例如,可以让执⾏ 程序的 Windows 账号可以执⾏数据库存储过程,但不能访问基表。可以提升性能:因为存储过程是多条语句的集合体,访问⼀次数据库可以做很多事情,如果数据库在其他计算机(不是web服务器)上,可以极⼤的减少执⾏复杂任务的总时间。
我们通过⼀个较为完整的⽰例来学习这⼀过程,向 Northwind 数据库添加⼀个存储过程:create procedure InsertEmployee@TitleOfCourtesy varchar(25),@LastName varchar(20),@FirstName varchar(10),@EmployeeID int outputas insert into Employees(TitleOfCourtesy,LastName,FirstName,HireDate) values(@TitleOfCourtesy,@LastName,@FirstName,GetDate());
Set @EmployeeID = @@identity 这个存储过程有3个输⼊参数,1个输出参数。顺便⼀提,如果不使⽤存储过程的输出参数功能,要从刚刚插⼊的记录中获得⾃动⽣成的标识会是⼀件很⿇烦的事。接着,我们创建⼀个程序来调⽤存储过程:protected void Page_Load(object sender, EventArgs e){ string connStr = tionStrings["Northwind"].ConnectionString; SqlConnection conn = new SqlConnection(connStr);
// 调⽤存储过程,必须指定 dType
SqlCommand cmd = new SqlCommand("InsertEmployee", conn); dType = Procedure;
// 向存储过程传递参数 // 需要精确指定【数据类型】和【参数的⼤⼩】以便和数据库中的细节相匹配 // 使⽤参数的 Value 属性进⾏赋值 (new SqlParameter("@TitleOfCourtesy",ar,25)); ters["@TitleOfCourtesy"].Value = Title; (new SqlParameter("@LastName", ar, 20)); ters["@LastName"].Value = LastName; (new SqlParameter("@FirstName", ar, 10)); ters["@FirstName"].Value = FirstName;
// 【输出参数】也使⽤相同的⽅式添加 // 但是必须指定它的 Direction 属性为 OutPut (new SqlParameter("@EmployeeID", , 4)); ters["@EmployeeID"].Direction = ;
// 执⾏数据库命令 using (conn) { (); int rtv = eNonQuery(); = ("Inserted {0} record(s)
", rtv);
// 获取存储过程的输出参数 int empID = (int)ters["@EmployeeID"].Value; += "New id: " + ng(); }}
Parameters 集合的⼀个⽅便的⽅法时 AddWithValue()。该⽅法接收参数名及其值但不包括数据类型的信息,⽽是根据提供的数据猜测数据类型。显然,这对输出参数⽆效,因为你压根不会为输出参数提供值。
2023年6月21日发(作者:)
如何防⽌SQL注⼊?使⽤参数化命令!转载⾃
与数据库交互的 Web 应⽤程序中最严重的风险之⼀:SQL 注⼊攻击。 SQL 注⼊是应⽤程序开发⼈员未预期的把 SQL 代码传⼊到应⽤程序的过程,它由于应⽤程序的糟糕设计⽽使攻击成为可能,并且只有那些直接使⽤⽤户提供的值构建SQL 语句的应⽤程序才会受影响。
问题在于命令时如何被执⾏的。SQL 语句通过字符串的构造技术动态创建,⽂本框的值被直接复制到字符串中,他可能是这样的:string sql = "SELECT * FROM Orders where CustomerID = '" + + "'"; 在这个⽰例中,⽤户可以篡改 SQL 语句,通常,攻击的第⼀个⽬标是得到错误信息。如果错误没有被恰当处理,底层的信息就会暴露给攻击者。现在若是在⽂本框中输⼊:ALFKI' OR '1'='1,再看看这条 SQL 语句,它现在是这样的:string sql = "SELECT * FROM Orders where CustomerID = 'ALFKI' OR '1'='1'"; 这样产⽣的后果是没有显⽰当前⽤户的特定信息,却向攻击者提供了全部资料,如果屏幕上显⽰的是敏感信息,如社会保险号,⽣⽇或者信⽤卡资料等,就会带来严重的问题!
还可以进⾏更复杂的攻击!例如攻击者可以利⽤两个连接号(--)注释掉 SQL 语句剩余部分,虽然这样的攻击只限于SQL Server,不过对于其他类型的数据库也有等效的办法。另外,攻击者还可以执⾏含有任意 SQL 语句的批处理命令,对于 SQL Server,攻击者只需加上分号(;),攻击者⽤这样的⽅式还可以删除其他表的内容。甚⾄调⽤SQL Server 的系统存储过程 xp_cmdshell 在命令⾏执⾏任意的程序。
下⾯是攻击者在⽂本框中输⼊的,他的⽬的是删除 Customers 表的全部⾏:ALFKI' ; DELETE * FROM Customers,得到的 SQL 语句是这样:"SELECT * FROM Orders where CustomerID = 'ALFKI';DELETE * FROM Customers"
如何防⽌ SQL 注⼊攻击?预防⼿段:使⽤ gth 属性防⽌⽤户输⼊过长的字符,这样减少了贴⼊⼤量的脚本的可能性使⽤ 验证控件锁定错误的数据限制错误信息给出的提⽰,捕获到异常时只显⽰⼀些通⽤的信息,⽽不是显⽰ e 属性中的信息,它会暴露出系统的攻击点更为重要的是,⼀定要⼩⼼去掉特殊字符,⽐如将单引号替换为两个单引号最好的解决⽅法是使⽤参数化的命令或者使⽤存储过程执⾏转义以防⽌ SQL 注⼊攻击使⽤参数化命令: 参数化命令是在 SQL ⽂本中使⽤占位符的命令,占位符表⽰需要动态替换的值,它们通过 Command 对象的 Parameters 集合来传送。 例如下⾯这条SQL语句:SELECT * FROM Customers where CustomerID = 'ALFKI' 可以写成这样:SELECT * FROM Customers where CustomerID = @CustID占位符随后单独提供并被⾃动编码
为每个参数创建⼀个 Parameter 对象,这些对象被加⼊到 ters 集合中。下⾯的⽰例重写前⾯的代码防⽌可能的 SQL 注⼊攻击:string connStr = tionStrings["Northwind"].ConnectionString;SqlConnection conn = new SqlConnection(connStr);
// 这⾥使⽤参数化的 SQL 语句string sql = "SELECT * FROM Customers where CustomerID = @CustID";SqlCommand cmd = new SqlCommand(sql, conn);
// 这⾥配置 ters 集合hValue("@CustID", );();SqlDataReader reader = eReader(); 若在修改后的页⾯上再次尝试 SQL 注⼊攻击,将得不到任何记录。因为没有客户 ID 值与 ⽂本框输⼊的 ALFKI ' OR '1' = '1' 相等的订单项,这正是我们期待的结果。
调⽤存储过程 参数化命令时调⽤完整功能存储过程的诸多命令中的⼀⼩部分。 存储过程当然是保存在数据库上的批次执⾏的⼀条或多条SQL语句。它们是良好的逻辑封装体,可以接收(输⼊参数)和返回(输出参数)数据。 存储过程有很多优点:更易于维护:例如,你可以优化存储过程中的命令⽽不必重新编译使⽤它的程序。可以更安全地使⽤数据库:例如,可以让执⾏ 程序的 Windows 账号可以执⾏数据库存储过程,但不能访问基表。可以提升性能:因为存储过程是多条语句的集合体,访问⼀次数据库可以做很多事情,如果数据库在其他计算机(不是web服务器)上,可以极⼤的减少执⾏复杂任务的总时间。
我们通过⼀个较为完整的⽰例来学习这⼀过程,向 Northwind 数据库添加⼀个存储过程:create procedure InsertEmployee@TitleOfCourtesy varchar(25),@LastName varchar(20),@FirstName varchar(10),@EmployeeID int outputas insert into Employees(TitleOfCourtesy,LastName,FirstName,HireDate) values(@TitleOfCourtesy,@LastName,@FirstName,GetDate());
Set @EmployeeID = @@identity 这个存储过程有3个输⼊参数,1个输出参数。顺便⼀提,如果不使⽤存储过程的输出参数功能,要从刚刚插⼊的记录中获得⾃动⽣成的标识会是⼀件很⿇烦的事。接着,我们创建⼀个程序来调⽤存储过程:protected void Page_Load(object sender, EventArgs e){ string connStr = tionStrings["Northwind"].ConnectionString; SqlConnection conn = new SqlConnection(connStr);
// 调⽤存储过程,必须指定 dType
SqlCommand cmd = new SqlCommand("InsertEmployee", conn); dType = Procedure;
// 向存储过程传递参数 // 需要精确指定【数据类型】和【参数的⼤⼩】以便和数据库中的细节相匹配 // 使⽤参数的 Value 属性进⾏赋值 (new SqlParameter("@TitleOfCourtesy",ar,25)); ters["@TitleOfCourtesy"].Value = Title; (new SqlParameter("@LastName", ar, 20)); ters["@LastName"].Value = LastName; (new SqlParameter("@FirstName", ar, 10)); ters["@FirstName"].Value = FirstName;
// 【输出参数】也使⽤相同的⽅式添加 // 但是必须指定它的 Direction 属性为 OutPut (new SqlParameter("@EmployeeID", , 4)); ters["@EmployeeID"].Direction = ;
// 执⾏数据库命令 using (conn) { (); int rtv = eNonQuery(); = ("Inserted {0} record(s)
", rtv);
// 获取存储过程的输出参数 int empID = (int)ters["@EmployeeID"].Value; += "New id: " + ng(); }}
Parameters 集合的⼀个⽅便的⽅法时 AddWithValue()。该⽅法接收参数名及其值但不包括数据类型的信息,⽽是根据提供的数据猜测数据类型。显然,这对输出参数⽆效,因为你压根不会为输出参数提供值。
发布评论