扫描 GraphQL API 以查找漏洞
自2020年6月以来,Acunetix支持日益流行的API查询语言– GraphQL。在本文中,想逐步向您展示如何扫描使用GraphQL定义的API。为此,您将首先创建一个故意易受攻击的API及其GraphQL定义,然后使用Acunetix对其进行扫描,消除使用Acunetix发现的严重漏洞,并确认已消除了这些漏洞。
阶段1:设置测试环境
为了能够进行此练习,在测试之前必须准备一个测试环境。在本练习中,我们将Windows操作系统与开源软件一起使用。
- 使用PHP和MySQL将Wamp64安装为本地Web服务器
- 将Windows路径环境变量设置为指向您的PHP和MySQL可执行文件:
- 运行Windows可执行文件systempropertiesadvanced.exe
- 转到高级选项卡
- 单击环境变量
- 将您的php.exe文件夹和mysql.exe文件夹添加到路径列表;要添加的典型值是:
- c:\ wamp64 \ bin \ php \ php7.3.21
- c:\ wamp64 \ bin \ mysql \ mysql5.7.31 \ bin
- 安装适用于PHP的Composer依赖性管理器
阶段2:建立简单的GraphQL API
要使用Acunetix扫描GraphQL API,您将构建一个简单的,易受攻击的API。要构建API,您需要执行以下步骤:
- 在Web服务器上创建数据库以存储数据
- 创建一个Web服务根文件夹并安装一些依赖库
- 创建一个GraphQL模式文件
- 创建一个GraphQL模式加载器文件
- 创建索引文件以处理GraphQL请求
- 创建一个解析器文件
步骤1:在Web服务器上创建数据库
要在Wamp64 Web服务器上设置数据库,请执行以下操作
打开命令提示符
运行mysql -u root
在MySQL根目录提示符下运行以下命令:
mysql> create user 'graphuser'@'localhost' identified by 'graphuserpass'; mysql> create database graphusersdb; mysql> GRANT ALL PRIVILEGES ON graphusersdb.* TO 'graphuser'@'localhost'; mysql> use graphusersdb; mysql> CREATE TABLE `users` (`id` int(11) NOT NULL AUTO_INCREMENT,`fname` varchar(255) NOT NULL, `lname` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `notes` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `email` (`email`)); mysql> INSERT INTO users (fname, lname, email) VALUES ('John', 'Smith', 'john@example.com'); mysql> INSERT INTO users (fname, lname, email) VALUES ('Jane', 'Doe', 'jane@example.com');
步骤2:创建Web服务根文件夹
要创建Web服务根文件夹,请执行以下操作:
打开命令提示符
运行以下命令为您的项目创建文件夹,包括PHP库依赖项:
C:\>mkdir C:\wamp64\www\graphusers C:\>cd C:\wamp64\www\graphusers C:\wamp64\www\graphusers>composer require leocavalcante/siler C:\wamp64\www\graphusers>composer require overblog/dataloader-php C:\wamp64\www\graphusers>composer require webonyx/graphql-php
步骤3:创建一个GraphQL模式文件
创建具有以下内容的C:\ wamp64 \ www \ graphusers \ schema.graphql文件:
type Query {
getUser(email: String): [User]
}
type Mutation {
addUser(fname: String, lname: String, email: String, notes: String): [User]
deluser(email: String): [User]
}
type User {
id: Int
fname: String
lname: String
email: String
notes: String
}
步骤4:创建GraphQL模式加载器文件
创建具有以下内容的C:\ wamp64 \ www \ graphusers \ schema.php文件:
<?php
use Siler\Graphql;
$typeDefs = file_get_contents(__DIR__.'/schema.graphql');
$resolvers = include __DIR__.'/resolvers.php';
return Graphql\schema($typeDefs, $resolvers);
步骤5:创建索引文件以处理GraphQL请求
创建具有以下内容的C:\ wamp64 \ www \ graphusers \ index.php文件:
<?php
require_once 'vendor/autoload.php';
use Siler\Graphql;
use GraphQL\GraphQL as WGraphql;
use Siler\Http\Request;
use Siler\Http\Response;
use Overblog\DataLoader\DataLoader;
use Overblog\DataLoader\Promise\Adapter\Webonyx\GraphQL\SyncPromiseAdapter;
use Overblog\PromiseAdapter\Adapter\WebonyxGraphQLSyncPromiseAdapter;
try {
$MyDB = new PDO("mysql:host=localhost;dbname=graphusersdb;charset=utf8mb4", "graphuser", "graphuserpass");
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
function select_sql($query, $sql_args) {
global $MyDB;
$stmt = $MyDB->prepare($query);
$stmt->execute($sql_args);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $rows;
}
function insert_sql($query, $sql_args) {
global $MyDB;
$stmt = $MyDB->prepare($query);
return $stmt->execute($sql_args); // true if successful; false if not successful
}
function delete_sql($query, $sql_args) {
global $MyDB;
$stmt = $MyDB->prepare($query);
return $stmt->execute($sql_args); // true if successful; false if not successful
}
$graphQLSyncPromiseAdapter = new SyncPromiseAdapter();
$promiseAdapter = new WebonyxGraphQLSyncPromiseAdapter($graphQLSyncPromiseAdapter);
WGraphQL::setPromiseAdapter($graphQLSyncPromiseAdapter);
$context = [
'select_sql' => function ($query, $sql_args) { return select_sql($query, $sql_args); },
'insert_sql' => function ($query, $sql_args) { return insert_sql($query, $sql_args); },
'delete_sql' => function ($query, $sql_args) { return delete_sql($query, $sql_args); },
];
if (Request\method_is('post')) {
$schema = include __DIR__.'/schema.php';
Graphql\init($schema, null, $context);
}
步骤6:创建解析器文件
创建具有以下内容的C:\ wamp64 \ www \ graphusers \ resolvers.php文件:
<?php
return [
'Query' => [
getUser' => function($root, $args, $context) {
return $context['select_sql']("SELECT id, fname, lname, email, notes FROM users WHERE email=:email", ['email'=>$args['email']]);
}
],
'Mutation' => [
'addUser' => function($root, $args, $context) {
$dummy = $context['insert_sql']("INSERT INTO users (fname, lname, email, notes) VALUES ('" . $args['fname'] . "', '" . $args['lname'] . "', '" . $args['email'] . "', '" . $args['notes'] . "')", []);
if ($dummy) {
return $context['select_sql']("SELECT id, fname, lname, email, notes FROM users WHERE email=:email", ['email'=>$args['email']]);
}
return null;
},
'delUser' => function($root, $args, $context) {
$delrecord = $context['select_sql']("SELECT id, fname, lname, email, notes FROM users WHERE email=:email", ['email'=>$args['email']]);
$dummy = $context['delete_sql']("DELETE FROM users WHERE email=:email", ['email'=>$args['email']]);
if ($dummy) {
return $delrecord;
}
return null;
}
]
];
阶段3:扫描您的GraphQL API
在此示例中,Web服务定义位于以下URL:http://localhost/graphusers/schema.graphql。要使用Acunetix扫描Web服务:
使用以下URL创建一个新目标:http:// localhost / graphusers /
将修改后的模式文件导入到目标:
从以下URL下载模式文件:http://localhost/graphusers/schema.graphql
在文件顶部插入一行以指定GraphQL端点;该文件应如下所示:
graphql_endpoint="/graphusers/"; type Query { getUser(email: String): [User] } type Mutation { addUser(fname: String, lname: String, email: String, notes: String): [User] deluser(email: String): [User] } type User { id: Int fname: String lname: String email: String notes: String }
找到目标配置的“导入文件”部分,然后导入修改后的架构文件
将PHP AcuSensor部署到您的Web服务
启动您的Web服务的完整扫描,然后等待其完成
阶段4:识别GraphQL API中的漏洞
检查要扫描的漏洞列表。在本练习中,我们将重点介绍SQL注入漏洞,因为它们都有相同的根本原因。
Acunetix显示了攻击详细信息— GraphQL API调用已向变量注入了延迟命令,并且Acunetix能够确认响应确实延迟了指定的秒数。这证实了该API调用容易受到SQL注入的攻击,从而使恶意黑客可以制作其他请求以可能检索大量数据。
阶段5:解决漏洞
快速浏览**resolvers.php类文件中的addUser变异函数可以揭示根本原因。该查询是使用字符串连接构建的:
$dummy = $context 'insert_sql' VALUES ('" . $args['fname'] . "', '" . $args['lname'] . "', '" . $args['email'] . "', '" . $args['notes'] . "')", []);
]
该的$ args [“FNAME”]变量和其他变量被简单地连接起来无需任何验证查询字符串。我们可以使用参数化查询来调整代码。resolvers.php文件中新调整的行如下所示:
$dummy = $context'insert_sql' VALUES (:fname, :lname, :email, :notes)", ['fname'=>$args['fname'], 'lname'=>$args['lname'], 'email'=>$args['email'], 'notes'=>$args['notes']]);
阶段6:重新扫描以确认分辨率
转到扫描漏洞列表,然后选择刚刚解决的SQL注入漏洞。
单击“重新测试”按钮-这将创建一个新扫描,以再次测试所选漏洞。结果将表明您已经成功解决了这些漏洞。
