目前为止,绝大多数的web应用程序都会使用后端的数据库来存储数据。为了使web动态化显示,web程序必须与数据库实时交互。当用户发送http请求时,web应用程序后端向数据库发起响应,这些查询可以包括https请求的信息或者其他相关信息。
当用户提供信息用于构建对数据库的查询时,恶意用户可以欺骗该查询并用于其原始意图之外的用途。
SQL注入是只针对Mysql等关系型数据库的攻击。
Web 应用程序中可能存在多种类型的注入漏洞,例如 HTTP 注入、代码注入和命令注入。然而,最常见的例子是 SQL 注入。当恶意用户尝试传递更改 Web 应用程序发送到数据库的最终 SQL 查询的输入时,就会发生 SQL 注入,从而使用户能够直接对数据库执行其他非预期的 SQL 查询。
首先攻击者必须在预期用户的输入限制之外注入代码,因此他不会作为简单的用户输入执行。最基本的情况下,这是通过注入单引号或者双引号来摆脱用户的输入限制并将数据直接注入SQL查询来完成的。
一旦攻击者可以注入,他们就必须寻找一种方法来执行不同的SQL查询。这可以使用SQL代码组成一个执行预期SQL查询和新SQL查询的工作来完成。
通过安全编码我们,用户输入清理和验证来减少SQL注入攻击的可能性。
在了解SQL注入之前,我们需要更多了解数据库和结构化查询语言,那些数据库将执行必要的查询。 Web应用程序利用后端数据库来存储与Web应用程序相关的各种内容和信息。这可以是核心 Web 应用程序资产(例如图像和文件)、内容(例如帖子和更新)或用户数据(例如用户名和密码)
有许多不同类型的数据库,每种类型都适合特定的用途。传统上,应用程序使用基于文件的数据库,随着大小的增加,速度非常慢。这导致了Database Management Systems
( DBMS
)的采用。
数据库管理系统 (DBMS) 帮助创建、定义、托管和管理数据库。随着时间的推移,设计了各种类型的 DBMS,例如基于文件的 DBMS、关系 DBMS (RDBMS)、NoSQL、基于图形的存储和键/值存储。
与 DBMS 交互的方式有多种,例如命令行工具、图形界面,甚至 API(应用程序编程接口)。 DBMS 用于各种银行、金融和教育部门来记录大量数据。 DBMS 的一些基本功能包括:
Feature 特征 | Description 描述 |
---|---|
Concurrency | 现实世界的应用程序可能有多个用户同时与其交互。 DBMS 确保这些并发交互成功,而不会损坏或丢失任何数据。 |
Consistency | 由于存在如此多的并发交互,DBMS 需要确保整个数据库中的数据保持一致且有效。 |
Security | DBMS 通过用户身份验证和权限提供细粒度的安全控制。这将防止未经授权的查看或编辑敏感数据。 |
Reliability | 备份数据库并在数据丢失或泄露时将其回滚到之前的状态很容易。 |
Structured Query Language | SQL 通过支持各种操作的直观语法简化了用户与数据库的交互。 |
Tier I
通常由客户端应用程序组成,例如网站或 GUI 程序。这些应用程序包含高级交互,例如用户登录或评论。这些交互产生的数据通过 API 调用或其他请求传递到Tier II
。
第二层是中间件,它解释这些事件并将它们放入 DBMS 所需的形式。最后,应用程序层根据 DBMS 的类型使用特定的库和驱动程序与其进行交互。 DBMS 接收来自第二层的查询并执行请求的操作。这些操作可能包括插入、检索、删除或更新数据。处理后,如果查询无效,DBMS 将返回任何请求的数据或错误代码。
可以将应用程序服务器和 DBMS 托管在同一主机上。然而,具有支持许多用户的大量数据的数据库通常是单独托管的,以提高性能和可扩展性。
分为关系型和非关系型数据库
关系数据库中的表与键相关联,这些键在需要查看特定数据时提供快速数据库摘要或对特定行或列的访问。这些表也称为实体,它们都是相互关联的。例如,客户信息表可以为每个客户提供一个特定的 ID,该 ID 可以指示我们需要了解的有关该客户的所有信息,例如地址、姓名和联系信息。此外,产品描述表可以为每个产品分配特定的ID。存储所有订单的表只需要记录这些 ID 及其数量。这些表格中的任何变化都会影响所有表格,但影响是可预测的、系统性的。
然而,在处理集成数据库时,需要一个概念来使用其键(称为“键”)将一个表链接到另一个表。 relational database management system
( RDBMS
)。许多最初使用不同概念的公司正在转向 RDBMS 概念,因为这个概念易于学习、使用和理解。最初,这个概念仅被大公司使用。然而,现在许多类型的数据库都实现了 RDBMS 概念,例如 Microsoft Access、MySQL、SQL Server、Oracle、PostgreSQL 等。
非关系数据库(也称为NoSQL
数据库)不使用表、行和列或主键、关系或模式。相反,NoSQL 数据库根据存储的数据类型使用各种存储模型来存储数据。由于缺乏定义的数据库结构,NoSQL 数据库具有非常高的可扩展性和灵活性。因此,当处理定义和结构不是很好的数据集时,NoSQL 数据库将是存储此类数据的最佳选择。 NoSQL 数据库有四种常见的存储模型:
上述每个模型都有不同的数据存储方式。例如, Key-Value
模型通常以 JSON 或 XML 形式存储数据,每对都有一个键,并将其所有数据存储为其值:
上面的示例可以使用 JSON 表示为:
json{
"100001": {
"date": "01-01-2021",
"content": "Welcome to this web application."
},
"100002": {
"date": "02-01-2021",
"content": "This is the first post on this web app."
},
"100003": {
"date": "02-01-2021",
"content": "Reminder: Tomorrow is the ..."
}
}
它看起来类似于Python
或PHP
等语言中的字典项(即{'key':'value'}
),其中key
通常是字符串, value
可以是字符串、字典或任何类对象。
比如MongoDB。
[!NOTE]
同样的,对于非关系型数据库(NoSQL)我们也有对应的NoSQL注入方式。
不同的RDBMS之间的SQL语法不尽相同。但是都需要遵循一定的标准(ISO 标准)。对此,SQL可以执行以下操作:
在命令行上操作MySQL数据库交互的基本标标志:
-u username
-p password(一般不写)
为了防止被记录在history中
-h 指定远程主机host
-P 端口
sqlcreate database users;
MySQL 期望命令行查询以分号结束。上面的示例创建了一个名为users
新数据库。我们可以使用SHOW DATABASES查看数据库列表,并且可以使用USE
语句切换到users
数据库。
[!NOTE]
注意:SQL语句不区分大小写,这意味这“USE users;”和“use users;”的执行效果是一样的。但是:对于数据库名称来说,我们不能使用“use USERS;”和“use users;”来达到相同的效果,因为对于数据库名称来说,是区分大小写的。
DBMS(数据库管理系统)都是以table的形式存储数据。表由水平行和列组成。行和列的交集称为单元格。每个表示使用一组固定的列创建的,其中每一列都属于固定的数据类型。
数据类型定义列要保存的值类型。常见示例包括数字
、字符串
、日期
、时间和``二进制数据
。也可能有特定于 DBMS 的数据类型。可以在此处找到 MySQL 中数据类型的完整列表。例如,让我们使用 CREATE TABLE SQL 查询创建一个名为 logins
的表来存储用户数据
sqlCREATE TABLE logins (
id INT,
username VARCHAR(100),
password VARCHAR(100),
date_of_joining DATETIME
);
正如我们所看到的,CREATE TABLE
查询首先指定表名,然后(在括号内)我们按名称和数据类型指定每一列,所有列都用逗号分隔。在 name 和 type 之后,我们可以指定特定的属性,这将在后面讨论。
上面的 SQL 查询创建了一个名为 logins
的表,其中包含四列。第一列 id
是一个整数。以下两列 username
和 password
设置为每列 100 个字符的字符串。任何超过此时间的输入都会导致错误。类型为 DATETIME
的 date_of_joining
列存储添加条目的日期。
我们可以使用show table来获取当前数据库中的table列表。
此外describe关键字还可以列出器字段和数据类型。
sqlmysql> DESCRIBE logins;
+-----------------+--------------+
| Field | Type |
+-----------------+--------------+
| id | int |
| username | varchar(100) |
| password | varchar(100) |
| date_of_joining | date |
+-----------------+--------------+
4 rows in set (0.00 sec)
在上文中,我们通过create table查询创建了一张表,并为其添加了许多属性。例如:我们可以使用auto_increment将id设为自动递增,每次将新项目添加到表中时,该关键字都会自动将id递增1:
sqlid INT NOT NULL AUTO_INCREMENT,
NOT NULL
约束确保特定列永远不会留空,“即必填字段”。我们还可以使用 UNIQUE
约束来确保插入的项目始终是唯一的。例如,如果我们将它与 username
列一起使用,我们可以确保没有两个用户会具有相同的用户名:
sql username VARCHAR(100) UNIQUE NOT NULL,
另一个重要的关键字是 DEFAULT
关键字,用于指定默认值。例如,在 date_of_joining
列中,我们可以将默认值设置为 Now(),在 MySQL 中,它将返回当前日期和时间:
sqldate_of_joining DATETIME DEFAULT NOW(),
最重要的属性之一是primary key:我们可以使用他来唯一标识表中的每一条记录,引用关系数据库的表中记录的所有数据,如上一节前面所讨论的那样。我们可以将id列设为该表的primary key(主键)
final cmd:
sqlCREATE TABLE logins (
id INT NOT NULL AUTO_INCREMENT,
username VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
date_of_joining DATETIME DEFAULT NOW(),
PRIMARY KEY (id)
);
insert主要用于我那个表中添加新记录。语法如下:
sqlINSERT INTO table_name VALUES (column1_value, column2_value, column3_value, ...);
shellINSERT INTO logins VALUES(1, 'admin', 'p@ssw0rd', '2020-07-02');
当然,如果我们其中的几列是有默认值,那我们可以指定列名插入数据:
sqlINSERT INTO table_name(column2, column3, ...) VALUES (column2_value, column3_value, ...);
[!NOTE]
注意:不能跳过带有NOT NULL约束的列
shellINSERT INTO logins(username, password) VALUES('administrator', 'adm1n_p@ss');
[!TIP]
对于密码之类的重要数据,最好不要通过明文传输,一般通过hash
除此之外,我们也可以通过“,”,来一次性添加多条数据。
我们使用select来检索表中的相关信息
e.g.:
sqlSELECT * FROM table_name;
通配符*可以表示所有列。from关键字用于表示可供选择的表。我们也可以用来查看当前表的特定列。
e.g.:
sqlSELECT column1, column2 FROM table_name;
我们可以使用drop去删除服务中的数据库或表。
like this:
shellmysql> DROP TABLE logins;
Query OK, 0 rows affected (0.01 sec)
mysql> SHOW TABLES;
Empty set (0.00 sec)
我们可以使用alter改变表和任意一块区域的名字或者往已存在的表中删除或添加新的列。
下面的例子:通过add方法往logins表中添加newColumn列
mysqlmysql> ALTER TABLE logins ADD newColumn INT;
重命名列,使用“RENAME COLUMN”:
sqlmysql> ALTER TABLE logins RENAME COLUMN newColumn TO newerColumn;
我们也可以使用modify改变一列的数据类型:
sqlmysql> ALTER TABLE logins MODIFY newerColumn DATE;
删除列,“DROP”:
sqlmysql> ALTER TABLE logins DROP newerColumn;
当Alter被用来改变表的属性,update可以用来更新表的特定记录。
e.g.:
sqlUPDATE table_name SET column1=newvalue1, column2=newvalue2, ... WHERE <condition>;
我们指定表名,每一列和每一条记录以及更新记录的状态。例子:
sqlUPDATE logins SET password = 'change_password' WHERE id > 1;
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> SELECT * FROM logins;
+----+---------------+-----------------+---------------------+
| id | username | password | date_of_joining |
+----+---------------+-----------------+---------------------+
| 1 | admin | p@ssw0rd | 2020-07-02 00:00:00 |
| 2 | administrator | change_password | 2020-07-02 11:30:50 |
| 3 | john | change_password | 2020-07-02 11:47:16 |
| 4 | tom | change_password | 2020-07-02 11:47:16 |
+----+---------------+-----------------+---------------------+
4 rows in set (0.00 sec)
我们可以使用order by对特定列进行排序;
such as:
shell-sessionmysql> SELECT * FROM logins ORDER BY password; +----+---------------+------------+---------------------+ | id | username | password | date_of_joining | +----+---------------+------------+---------------------+ | 2 | administrator | adm1n_p@ss | 2020-07-02 11:30:50 | | 3 | john | john123! | 2020-07-02 11:47:16 | | 1 | admin | p@ssw0rd | 2020-07-02 00:00:00 | | 4 | tom | tom123! | 2020-07-02 11:47:16 | +----+---------------+------------+---------------------+
默认情况下,排序升序,当然我们也可以通过asc或者desc来对结果手动指定升/降序。
for example:
shell-sessionSELECT * FROM logins ORDER BY password DESC;
同样的,我们也可以指定对多列指定升降序排列。
forexample
shell-sessionSELECT * FROM logins ORDER BY password DESC, id ASC;
在某些情况下,我们查询返回了相当数量的记录,这时候我们就可以通过LIMIT这些结果来筛选我们想要的
shell-sessionSELECT * FROM logins LIMIT 2;
如果我们想用偏移量来限制结果,我们需要在计数之前添加偏移量:
shell-sessionSELECT * FROM logins LIMIT 1, 2;(offset == 1)
为了筛选特定的数据,我们可以在select语句后使用where子句来微调我们的select。
sqlSELECT * FROM table_name WHERE <condition>;
比如我们要查询id > 1的记录:
shell-sessionSELECT * FROM logins WHERE id > 1;
同样的name 和 password等列也可以通过制定相应的字符来筛选:
另一个有用的子句LIKE,能够让我们查询时匹配含有特定字符的记录:
shell-sessionSELECT * FROM logins WHERE username LIKE 'admin%'; +----+---------------+------------+---------------------+ | id | username | password | date_of_joining | +----+---------------+------------+---------------------+ | 1 | admin | p@ssw0rd | 2020-07-02 00:00:00 | | 4 | administrator | adm1n_p@ss | 2020-07-02 15:19:02 |
%作为通配符匹配含有admin字符的所有records。
%通常用作匹配0或多个字符,如果想要匹配某一固定数量的字符使用‘_’
for example:
shell-sessionSELECT * FROM logins WHERE username like '___'; +----+----------+----------+---------------------+ | id | username | password | date_of_joining | +----+----------+----------+---------------------+ | 3 | tom | tom123! | 2020-07-02 15:18:56 | +----+----------+----------+---------------------+
匹配username为三个字符的record。
最常用的运算符not and or;
and运算符连接两个条件并并根据评估返回true或false
condition1 AND condition2
在Mysql中任何的非零值都被认为时true,并且true返回1.
与and不同的是,or只要求我们的条件中有一个为true即判定为真。
not运算符用于切换true与false两个值。
上述的not、and、or都可以被表示为符号形式:&&(and)、||(or)和!(not)。
因为SQL支持多种运算,有且不限于 %(求余) + - * / => <= not and or 等等。
优先级如下:
/
), Multiplication (*
), and Modulus (%
)+
) and subtraction (-
)=
, >
, <
, <=
, >=
, !=
, LIKE
)!
)&&
)||
)sqlSELECT * FROM logins WHERE username != 'tom' AND id > 3 - 2;
当然我们首先必须确定我们的目标网站是否使用Mysql并且该后端服务是否启用。
一旦服务启动,网站就会通过mysql进行数据的存储或者检索。
下面的例子使用php网页端应用:
php$conn = new mysqli("localhost", "root", "password", "users");
$query = "select * from logins";
$result = $conn->query($query);
上述的php语句中query的输出将会被存储到result中,我们可以将它打印到页面上。
下面这行语句将会把所有结果返回输出到新的一行中。
phpwhile($row = $result->fetch_assoc() ){
echo $row["name"]."<br>";
}
web应用程序在检索数据时通常直接使用用户的输入。就比如用户使用搜索功能,直接将输入数据传输到web应用程序中,使用该输入在数据库中搜索。
如果我们使用用户输入进行SQL查询,这会导致编码不安全,可能会导致SQL注入漏洞。
在上面的实例中,我们接受用户输入并直接将其传递给SQL查询语句,而无需进行清理。
[!NOTE]
清理:指删除用户输入中的任何特殊字符。
当应用程序错误的将用户输入错误的解释为实际代码而不是字符串,更改代码流并执行它时,就会发生注入。这样我们就可以通过注入特殊字符(如" ' ")来转义用户输入边界
,然后来编写要执行的代码来实现,除非用户输入经过处理,否则很可能会执行注入的代码。
当用户输入到SQL查询字符串中并未正确清理或筛选时,会发生SQL注入。前面的例子显示了如何在SQL查询中使用用户查询斌且没有任何形式的输入清理。
php$searchInput = $_POST['findUser'];
$query = "select * from logins where username like '%$searchInput'";
$result = $conn->query($query);
在一般情况下,我们选择输入searchInput完成查询,并返回预期结果。我们键入的任何输入都会进入上述的SQL查询。
对于**'%$searchInput'"**,无论我们输入什么,比如admin,那么最后的查询语句会变成
sqlselect * from logins where username like '%admin'"
对上述语句的sql注入利用方式,大致就像上面显示,例如:我们输入drop users;,他将会显示 %drop users;也就是查询含有drop users字符的字段。但是由于没有清理,我们可以在在输入前面添加一个'来结束用户输入。 1';drop users;,这种情况下,实际的查询语句就是
php'select * from logins where username like 1';drop users;"
[!IMPORTANT]
请注意我们如何在 “1” 之后添加单引号 ('),以便转义 ('%$searchInput') 中用户输入的边界。
因此,执行的最终 SQL 查询将如下所示:
sqlselect * from logins where username like '%1'; DROP TABLE users;'
从上述语法高亮中,我们可以看到,我们可以转义原始的查询边界,并执行新的注入查询。
前面的SQL注入示例将返回一个报错:
phpError: near line 1: near "'": syntax error
这是因为最后一个尾随字符我们有一个未关闭的引号,这会在执行sql查询的时候出现语法错误。
sqlselect * from logins where username like '%1'; DROP TABLE users;'
那么如果我们注入成功,我们就需要确保sql语法没有错误,那么我们如何保证成功注入呢?
一个答案是使用注释,另一种是通过传入多个单引号使查询语法成功。
.
307+
本文作者:Hyrink
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!