此博客仅用于记录个人学习进度,学识浅薄,若有错误观点欢迎评论区指出。欢迎各位前来交流。(部分材料来源网络,若有侵权,立即删除)
本人博客所有文章纯属学习之用,不涉及商业利益。不合适引用,自当删除!
若被用于非法行为,与我本人无关
[SEED-Lab]-SQL注入攻击
实验环境
- seed-ubuntu 20.04
PDF文件
实验步骤
环境配置
Labsetup
DNS定向
-
sudo Docker-compose build
-
等待一会
- 安装完成
- 启动
sudo docker-compose up
- 访问页面会发现进入到apache默认页面
- 回去仔细看发现,启动apache的时候有一个报错
- 然后这边去查看apache的配置文件
- 注意,这里是docker里的apache,而不是本地的apache,原本以为是本地的apache的问题,弄了半天,在雨指导的指导下,终于发现问题了
- 然后打开Labsetup,进入image_www,然后在conf后缀的配置文件中进行修改
- 修改成这样
- 然后重新docker-compose bulid 以及 docker-compose up
- 再次在火狐浏览器中访问http://www.SEEDLabSQLInjection.com
Lab Tasks
- SEEDLabSQLInjection.com.这个web应用程序是一个简单的员工管理应用程序。员工可以通过此Web应用程序查看和更新他们在数据库中的个人信息。此Web应用程序中主要有两个角色:管理员是特权角色,可以管理每个员工的个人资料信息;员工是正常角色,可以查看或更新自己的个人资料信息。所有员工信息如下表所示。
task01: Get Familiar with SQL Statements
题目
- 此任务的目的是通过使用所提供的数据库来熟悉SQL命令。我们创建了一个名为用户的数据库,其中包含一个名为凭证的表;该表存储个人信息(如开斋节、密码、工资、ssn等)。每个员工的数量。在此任务中,您需要使用数据库以熟悉SQL查询。
- MySQL是一个开源的关系数据库管理系统。我们已经在SEEDUbuntuVM图像中设置了MySQL。用户名是根目录,密码请参见数据库。请使用以下命令登录到MySQL控制台:
mysql -u root -pseedubuntu
- 登录后,您可以创建新的数据库或加载现有的数据库。由于我们已经为您创建了用户数据库,因此您只需使用以下命令加载此现有数据库:
mysql> use Users;
运行上述命令后,需要使用SQL命令打印员工Alice的所有配置文件信息。
mysql> show tables;
实现
- 尝试多次在虚拟机和docker中启动mysql,均失败,在Y4指导的指导下,将数据库文件导出至其他已搭建mysql环境进行实验。
- 这边建的数据库名称是 1
- 成功进入
mysql> use 1;
- 表示可以进行增删改查
mysql> show tables;
SELECT * FROM credential WHERE Name='Alice'
- 拿下
task 02:SQL Injection Attack on SELECT Statement
- 我们将使用来自www的登录页面。针对此任务的SEEDLabSQLInjection.com。登录页面如图1所示。它要求用户提供一个用户名和一个密码。web应用程序根据这两条数据对用户进行身份验证,因此只有知道自己密码的员工才被允许登录。作为攻击者,您的工作是在不知道任何员工的凭据的情况下登录到Web应用程序。为了帮助您开始此任务,我们将解释如何在web应用程序中实现身份验证。PHP代码位于/var/www/SQLphep目录中的不安全home.php,用于进行用户身份验证。下面的代码段显示了用户的身份验证方式。
$input_uname = $_GET[’username’];
$input_pwd = $_GET[’Password’];
$hashed_pwd = sha1($input_pwd);
...
$sql = "SELECT id, name, eid, salary, birth, ssn, address, email,
nickname, Password
FROM credential
WHERE name= ’$input_uname’ and Password=’$hashed_pwd’";
$result = $conn -> query($sql);
// The following is Pseudo Code
if(id != NULL) {
if(name==’admin’) {
return All employees information;
} else if (name !=NULL){
return employee information;
}
} else {
Authentication Fails;
}
- 以上SQL语句从凭证表中选择个人员工信息,如身份证明、姓名、工资、ssn等。SQL语句使用两个变量输入不名和哈列pwd,输入名包含用户在登录页面的用户名字段中输入的字符串,而哈希pwd包含用户键入的密码的sha1哈列。程序检查是否有记录与提供的用户名和密码匹配;如果有匹配,用户身份验证,并获得相应的员工信息。如果没有匹配项,则身份验证将失败。
task 2.1
题目
- SQL注入攻击。您的任务是从登录页面以管理员身份登录到web应用程序,以便您可以看到所有员工的信息。我们假设您知道管理员的帐户名,这是管理员,但您不知道密码。您需要决定键入用户名和密码字段才能成功进行攻击。
操作
- 在网页端对网站进行SQL注入攻击。已知用户名admin,但是并不知道其密码。
- 关于sql注入尝试最基础的单引号注入
- 以及#
admin'#
- 然后成功进入
task2.2
题目
- 来自命令行的SQL注入攻击。你的任务是重复任务2.1,但你需要在不使用网页的情况下完成。您可以使用命令行工具,如curl,它可以发送HTTP请求。值得一提的是,如果您想在HTTP请求中包含多个参数,则需要将URL和参数放在一对单引号之间;否则,用于分离参数(如&)的特殊字符将由shell程序解释,从而改变命令的含义。下面的示例展示了如何向我们的Web应用程序发送HTTPGET请求,并附加了两个参数(用户名和密码):
’www.SeedLabSQLInjection.com/index.php?username=alice&Password=111’
- 如果需要在用户名或密码字段中包含特殊字符,则需要正确地对它们进行编码,或者它们可以更改请求的含义。如果要在这些字段中包含单个报价,则应该使用%27;如果要包含空白,则应使用%20。在此任务中,您确实需要在使用curl发送请求时处理HTTP编码。
操作
- 将符号进行转译,以及注释符添加
http://www.seedlabsqlinjection.com/unsafe_home.php?username=admin%27%23
- 访问
Task 2.3: Append a new SQL statemen
题目
- 在上述两种攻击中,我们只能从数据库中窃取信息;如果我们能在登录页面中使用相同的漏洞来修改数据库会更好。一种想法是使用SQL注入攻击将一个SQL语句转换为两个,其中第二个是更新或删除语句。在SQL中,分号(;)用于分离两个SQL语句。请描述如何使用登录页面让服务器运行两个SQL语句。尝试通过攻击从数据库中删除记录,并描述您的观察结果。
操作
Admin';update credential set salary=0 where Name="Boby";#
- 尝试将boby的工资置零
- 但是不行
- 查找原因发现,这种攻击对mysql无效,因为PHP中mysqli扩展的query()函数禁止执行多条语句。
- 除非修改unsafe_home.php中使用的函数
- 把query修改为multi_query:
- 注意不止一处
- 重启docker
- 再次尝试,删除Samy
Admin';delete from credential where Name="Samy";#
- 发现还是不行
task 03
- 如果更新语句发生了SQL注入漏洞,则损坏将更严重,因为攻击者可以使用该漏洞来修改数据库。在我们的员工管理应用程序中,有一个编辑配置文件页面,允许员工更新他们的个人资料信息,包括昵称、电子邮件、地址、电话号码和密码。要进入此页面,员工需要首先登录。当员工通过“编辑配置文件”页面更新其信息时,将执行以下SQL更新查询。在不安全的编辑backend.php文件中实现的PHP代码用于更新员工的配置文件信息。PHP文件位于/var/www/SQLIn注入目录中。
$hashed_pwd = sha1($input_pwd);
$sql = "UPDATE credential SET
nickname=’$input_nickname’,
email=’$input_email’,
address=’$input_address’,
Password=’$hashed_pwd’,
PhoneNumber=’$input_phonenumber’
WHERE ID=$id;";
$conn->query($sql);
Task 3.1: Modify your own salary
题目
- 如“编辑个人资料”页面所示,员工只能更新他们的昵称、电子邮件、地址、电话号码和密码;他们无权更改工资。假设你(爱丽丝)是一个心怀不满的员工,而你的老板博比今年并没有增加你的薪水。您希望通过利用编辑配置文件页面中的SQL注入漏洞来增加自己的工资。请演示你如何才能实现这一点。我们假设你知道工资被存储在一个叫做“工资”的栏中。
操作
- 随便选取一个注入点
110',Salary='30001
- 修改成功
Task 3.2: Modify other people’ salary
题目
- 在增加了自己的薪水后,你决定惩罚你的老板boby。你想把他的薪水降到1美元。请演示你如何才能实现这一点。
操作
- 使用where语句匹配
110', Salary=0 where ID=2#
Task 3.3: Modify other people’ password
题目
- 在改变了Boby的薪水后,你仍然很不满,所以你想把Boby的密码改成你知道的东西,然后你可以登录他的账户,造成进一步的损害。请演示你如何才能实现这一点。您需要证明您可以使用新密码成功地登录到Boby的帐户。有一件事值得这里提到的是,数据库存储密码的散列值,而不是明文密码字符串。您可以再次查看不安全的编辑backend.php代码,以查看密码是如何存储的。它使用SHA1哈希函数来生成密码的哈希值。
- 为了确保您的注入字符串不包含任何语法错误,您可以在启动对我们的Web应用程序的真正攻击之前,在MySQL控制台上测试您的注入字符串
操作
- 在网站上使用的是SHA1加密后的结果,所以如果需要修改密码,应该输入加密后的结果;
- 比如密码为zacker
- 对应为dd79cc996328f4b68d23a351a411fedb6706dbc0
113',Password='dd79cc996328f4b68d23a351a411fedb6706dbc0' WHERE ID=2;#
Task 4: Countermeasure — Prepared Statement
task 4.1
题目
- SQL注入漏洞的基本问题是未能分离出代码和数据。在构造SQL语句时,程序(例如PHP程序)知道哪些部分是数据,哪些部分是代码。不幸的是,当SQL语句被发送到数据库时,边界已经消失;SQL解释器看到的边界可能与开发人员设置的原始边界不同。要解决这个问题,必须确保服务器端代码和数据库中的边界视图是一致的。最安全的方法是使用已准备好的语句。
- 要了解准备好的语句如何防止SQL注入,我们需要了解当SQL服务器接收到查询时会发生什么。查询执行方式的高级工作流如图3所示。在编译步骤中,查询首先经过解析和规范化阶段,在该阶段,查询将根据语法和语义进行检查。下一个阶段是编译阶段,其中的关键字(例如,选择、FROM、更新等)。被转换为一种机器可以理解的格式。基本上,在这个阶段,查询会被解释。在查询优化阶段,考虑不同计划的数量来执行查询,从中选择最佳的优化计划。所选计划存储在缓存中,因此每当下一个查询进入时,它将根据缓存中的内容进行检查;如果它已经存在于缓存中,则将跳过解析、编译和查询优化阶段。然后将编译后的查询传递到实际执行的执行阶段。
- 准备好的语句会在编译后和执行步骤之前进入图片中。一个准备好的语句将经过编译步骤,并被转换为一个具有数据空占位符的预编译查询。要运行此预编译的查询,需要提供数据,但这些数据不会通过编译步骤;相反,它们被直接插入到预编译的查询中,并被发送到执行引擎。因此,即使数据内部有SQL代码,如果不经过编译步骤,该代码也将被简单地视为数据的一部分,没有任何特殊的含义。这就是准备好语句防止SQL注入攻击的方法。下面是一个如何用PHP编写一个准备好的语句的示例。我们将在下面的示例中使用一个可选择的状态。我们将展示如何使用已准备好的语句来重写容易受到SQL注入攻击的代码。
$sql = "SELECT name, local, gender
FROM USER_TABLE
WHERE id = $id AND password =’$pwd’ ";
$result = $conn->query($sql))
- 上述代码很容易受到SQL注入的攻击。它可以重写如下
$stmt = $conn->prepare("SELECT name, local, gender
FROM USER_TABLE
WHERE id = ? and password = ? ");
// Bind parameters to the query
$stmt->bind_param("is", $id, $pwd);
$stmt->execute();
$stmt->bind_result($bind_name, $bind_local, $bind_gender);
$stmt->fetch();
- 使用准备好的语句机制,我们将向数据库发送SQL语句的过程分为两个步骤。第一步是只发送代码部分,即没有实际数据的SQL语句。这是需要准备的步骤。正如我们从上面的代码片段中所看到的,实际的数据会被问号(?)所取代。在这一步骤之后,我们使用绑定参数()将数据发送到数据库。数据库将只将此步骤中发送的所有内容视为数据,而不再是代码。它将数据绑定到准备好的语句的相应问号上。在绑定参数()方法中,第一个参数is表示参数的类型:i表示id中的数据具有整数类型,s表示$pwd中的数据具有字符串类型。对于此任务,请使用已准备好的语句机制来修复您在之前的任务中利用的SQL注入漏洞。然后,检查您是否仍然可以利用该漏洞。
操作
- 主要的操作是对数据库查询语句进行修改
// create a connection
$conn = getDB();
// Sql query to authenticate the user
$sql = $conn->prepare("SELECT id, name, eid, salary, birth, ssn, phoneNumber, address, email,nickname,Password
FROM credential
WHERE name= ? and Password= ?");
$sql->bind_param("ss", $input_uname, $hashed_pwd);
$sql->execute();
$sql->bind_result($id, $name, $eid, $salary, $birth, $ssn, $phoneNumber, $address, $email, $nickname, $pwd);
$sql->fetch();
$sql->close();
if($id!=""){
// If id exists that means user exists and is successfully authenticated
drawLayout($id,$name,$eid,$salary,$birth,$ssn,$pwd,$nickname,$email,$address,$phoneNumber);
}else{
// User authentication failed
echo "</div>";
echo "</nav>";
echo "<div class='container text-center'>";
echo "<div class='alert alert-danger'>";
echo "The account information your provide does not exist.";
echo "<br>";
echo "</div>";
echo "<a href='index.html'>Go back</a>";
echo "</div>";
return;
}
- 换用了预处理SQL方式,先用 prepare() 处理SQL模板,再用bind_param() 绑定数据,最后获取结果。
- 首先更改unsafe_home.php
- 改完代码后把文件改成safe_home.php
- 以及对应index.html中的链接改成safe_home.php
- 在更改后重新docker-compose build
- 重启后登录alice
- 发现进入的文件是更改后的safe_home.php
- 说明改动成功
- 然后测试2.1中最简单的单引号加注释符绕过
- 发现无法绕过
- 说明防御成功
task 4.2
- 将 unsafe_edit_frontend.php 中的unsafe 都改成 safe,最后将这个文件名中的的unsafe 也改成 safe
将unsafe_edit_backend.php 中的unsafe 都改成 safe。
- 处理修改的数据库查询的逻辑位于 safe_edit_backend.php 中,修改后的如下:
- 保存之后老规矩build up
1', salary = ’50000
- 可以看到我们输入的nickname被当成了nickname, 而不是salary作为一个字段。最后攻击失败,salary修改失败,只有nickname被修改了。
- 所以防御成功