扫描 REST API 中的漏洞
许多复杂的Web应用程序都是使用REST API构建的。与整体Web应用程序和网站一样,Acunetix可以帮助您确保所有REST API的安全性。在本文中,您将学习如何使用OpenAPI,Swagger或WADL定义来发现和修复REST API中的漏洞:
- 构建一个简单的REST API
- 创建不同规格的API定义文件:
- OpenAPI 3.0
- Swagger 2.0
- WADL
- 扫描API
- 识别漏洞
- 缓解和/或解决漏洞
- 重新扫描API以确认解析度
步骤1:构建简单的REST API
第一步是构建一个可以扫描的简单REST API。您将构建一个故意易受攻击的REST API,以便以后可以查看Acunetix如何发现该漏洞。
为了能够构建简单的REST API,您需要一个本地Web服务器以及一个附带的数据库服务器。在此示例中,我们在本地主机(也可通过192.168.0.11访问)上将本地WampServer(wamp64)与MariaDB数据库服务器一起使用。您也可以使用任何其他Web服务器和其他数据库类型,例如MySQL。如果这样做,则只需相应地修改示例中的路径。
要构建REST API,请执行以下步骤:
- 在Web服务器上创建数据库以存储数据
- 创建一个C:\ wamp64 \ www \ example \ includes \ config.php文件来存储连接到数据库所需的参数
- 创建一个C:\ wamp64 \ www \ example \ core \ initialize.php文件,其中将包含初始化API所需的命令
- 为用户类创建一个C:\ wamp64 \ www \ example \ core \ user.php文件,该文件定义了我们将提供的API函数;在此示例中,我们将提供一个名为read_by_id的 API函数
- 为API函数read_by_id创建一个C:\ wamp64 \ www \ example \ api \ read_by_id.php文件
- 创建一个C:\ wamp64 \ www \ example \ client \ client.php文件,该文件将向用户显示输入表单,并使用API检索请求的信息;这将是您的Web应用程序用户界面
在您的Web服务器上创建数据库
从数据库服务器根提示符下运行以下命令:
MariaDB [(none)]> CREATE USER 'restuser'@'localhost' IDENTIFIED BY 'restuserpass';
MariaDB [(none)]> CREATE DATABASE restdb;
MariaDB [(none)]> GRANT ALL PRIVILEGES ON restdb.* TO 'restuser'@'localhost';
MariaDB [(none)]> USE restdb;
MariaDB [restdb]> CREATE TABLE `users` (`id` int(11) NOT NULL AUTO_INCREMENT,`fname` varchar(30) DEFAULT NULL, `lname` varchar(30) DEFAULT NULL, `email` varchar(30) DEFAULT NULL, PRIMARY KEY (`id`) );
MariaDB [restdb]> INSERT INTO users (fname, lname, email) VALUES ('John', 'Smith', 'john@example.com');
MariaDB [restdb]> INSERT INTO users (fname, lname, email) VALUES ('Jane', 'Doe', 'jane@example.com');
生成配置文件
创建C:\ wamp64 \ www \ example \ includes \ config.php文件,如下所示:
<?php
$db_host = 'localhost';
$db_name = 'restdb';
$db_user = 'restuser';
$db_pass = 'restuserpass';
$db = new PDO('mysql:host='.$db_host.';dbname='.$db_name.';charset=utf8',$db_user,$db_pass);
?>
生成初始化文件
创建一个C:\ wamp64 \ www \ example \ core \ initialize.php文件,如下所示:
<?php
defined('SITE_ROOT') ? null : define('SITE_ROOT', 'C:\wamp64\www\example');
require_once(SITE_ROOT . '\includes\config.php');
require_once(SITE_ROOT . '\core\user.php');
?>
生成用户类文件
创建C:\ wamp64 \ www \ example \ core \ user.php文件,如下所示:
<?php
class User{
private $conn, $table = 'users';
public $id, $fname, $lname, $email;
public function __construct($db) { $this->conn = $db; }
public function read_by_id() {
$query = 'SELECT id, fname, lname, email FROM ' . $this->table . ' WHERE id = ' . $this->id;
$query_count = 'SELECT count(*) FROM ' . $this->table . ' WHERE id = ' . $this->id;
$statement_count = $this->conn->prepare($query_count);
$statement_count->execute();
$statement = $this->conn->prepare($query);
$statement->execute();
$row = $statement->fetch(PDO::FETCH_ASSOC);
$statement_row_count = $statement_count->fetchColumn();
if ($statement_row_count==0) {
http_response_code(404);
} else {
$this->id = $row['id'];
$this->fname = $row['fname'];
$this->lname = $row['lname'];
$this->email = $row['email'];
}
}
}
?>
构建read_by_id API函数
创建C:\ wamp64 \ www \ example \ api \ read_by_id.php文件,如下所示:
<?php
header('Content-Type: application/json');
include_once('../core/initialize.php');
$user = new User($db);
$user->id = isset($_GET['id']) ? $_GET['id'] : die();
$user->read_by_id();
$uarray = array('id'=>$user->id,'fname'=>$user->fname,'lname'=>$user->lname,'email'=>$user->email);
print_r(json_encode($uarray));
?>
构建Web应用程序用户界面
创建C:\ wamp64 \ www \ example \ client \ client.php文件,如下所示:
<?php
if (isset($_GET['id']) && $_GET['id']!="") {
$id = $_GET['id'];
$url = "http://192.168.0.11/example/api/read_by_id.php?id=".$id;
$client = curl_init($url);
curl_setopt($client,CURLOPT_RETURNTRANSFER,true);
$response = curl_exec($client);
$result = json_decode($response);
echo "<table style='border: 1px solid black;'>";
echo "<tr><td>Order ID:</td><td>$result->id</td></tr>";
echo "<tr><td>First Name:</td><td>$result->fname</td></tr>";
echo "<tr><td>Last Name:</td><td>$result->lname</td></tr>";
echo "<tr><td>Email Address:</td><td>$result->email</td></tr>";
echo "</table><br><br><hr><br><br>";
}
?>
<form action="" method="GET">
<label>Enter User ID:</label><br />
<input type="text" name="id" placeholder="Enter User ID" required/>
<br /><br />
<button type="submit" name="submit">Submit</button>
</form>
步骤2.创建API定义文件
OpenAPI 3.0规范
创建C:\ wamp64 \ www \ example \ api \ api_example_OpenAPI3.yaml文件,如下所示:
openapi: '3.0.0'
info:
title: UsersExample
version: '1.0'
servers:
- url: http://192.168.0.11/example/api
paths:
/read_by_id.php:
get:
summary: Single user identified by id
parameters:
- name: id
in: query
description: id to identify user
schema:
type: integer
responses:
'200':
description: Successfully returned a single user
content:
application/json:
schema:
$ref: '#/components/schemas/user'
'404':
description: No user with specified id was found
components:
schemas:
user:
type: object
properties:
id:
type: integer
fname:
type: string
lname:
type: string
email:
type: string
Swagger 2.0规范
创建C:\ wamp64 \ www \ example \ api \ api_example_Swagger2.yaml文件,如下所示:
swagger: '2.0'
info:
version: '1.0'
title: UsersExample
contact: {}
host: 192.168.0.11
basePath: /example/api
schemes:
- http
consumes:
- application/json
produces:
- application/json
paths:
/read_by_id.php:
get:
description: Single user identified by id
summary: Single user identified by id
operationId: Singleuseridentifiedbyid
deprecated: false
produces:
- application/json
parameters:
- name: id
in: query
required: false
type: integer
format: int32
description: id to identify user
responses:
200:
description: Successfully returned a single user
schema:
$ref: '#/definitions/user'
headers: {}
404:
description: No user with specified id was found
schema: {}
definitions:
user:
title: user
type: object
properties:
id:
type: integer
format: int32
fname:
type: string
lname:
type: string
email:
type: string
tags: []
WADL规范
创建C:\ wamp64 \ www \ example \ api \ api_example.wadl文件,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:schemas="http://192.168.0.11/example/api/schemas" xmlns="http://wadl.dev.java.net/2009/02">
<doc title="UsersExample" xml:lang="en" />
<grammars>
<xs:schema xmlns:tns="http://192.168.0.11/example/api/schemas" targetNamespace="http://192.168.0.11/example/api/schemas" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="user" type="schemas:user" />
<xs:element name="Singleuseridentifiedbyid_Response" type="schemas:Singleuseridentifiedbyid_Response" />
<xs:complexType name="user">
<xs:sequence>
<xs:element minOccurs="0" name="id" type="xs:integer" />
<xs:element minOccurs="0" name="fname" type="xs:string" />
<xs:element minOccurs="0" name="lname" type="xs:string" />
<xs:element minOccurs="0" name="email" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Singleuseridentifiedbyid_Response">
<xs:sequence>
<xs:element minOccurs="1" name="response" type="schemas:user">
<xs:annotation>
<xs:documentation>Successfully returned a single user</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
</grammars>
<resources base="http://192.168.0.11/example/api">
<resource id="_read_by_id.php" path="/read_by_id.php">
<method id="Singleuseridentifiedbyid" name="GET">
<doc title="Single user identified by id" xml:lang="en">Single user identified by id</doc>
<request>
<param name="id" style="query" type="xsd:integer">
<doc title="id" xml:lang="en">id to identify user</doc>
</param>
</request>
<response status="200">
<doc title="200" xml:lang="en">Successfully returned a single user</doc>
<representation element="schemas:Singleuseridentifiedbyid_Response" mediaType="application/json" />
</response>
<response status="404">
<doc title="404" xml:lang="en">No user with specified id was found</doc>
</response>
</method>
</resource>
</resources>
</application>
步骤3.扫描您的API
在此示例中,我们的API在此处定义:
- https://192.168.0.11/example/api/api_example_OpenAPI3.yaml(OpenAPI 3.0规范)
- https://192.168.0.11/example/api/api_example_Swagger2.yaml(Swagger 2.0规范)
- https://192.168.0.11/example/api/api_example.wadl(WADL规范)
要使用Acunetix扫描API,请执行以下操作:
- 使用上面列出的任何规范URL创建一个新目标;请注意,每个URL描述相同的API,因此将暴露相同的漏洞;我们提供了三种不同的方式,以便您证明Acunetix支持所有常见的REST API定义标准
- 将PHP AcuSensor部署到您的API
- 针对您的API 启动全面扫描,然后等待其完成
步骤4.找出您API中的漏洞
检查目标的漏洞列表。
在此练习中,我们将重点介绍SQL注入漏洞。
在“ 攻击详细信息”部分,Acunetix显示输入字段已成功填充潜在的恶意内容。这意味着未正确验证输入字段中插入的数据。Acunetix还提供了利用证明:它告诉您后端代码使用的数据库名称(API用户不应访问此类信息)。
步骤5.解决漏洞
快速浏览一下该函数读取-by_id内部user.php的类文件可以揭示根本原因。使用字符串连接构建查询:
$query = 'SELECT id, fname, lname, email FROM ' . $this->table . ' WHERE id = ' . $this->id;
$query_count = 'SELECT count(*) FROM ' . $this->table . ' WHERE id = ' . $this->id;
$statement_count = $this->conn->prepare($query_count);
$statement_count->execute();
$statement = $this->conn->prepare($query);
$statement->execute();
在$这个- > ID变量被简单地串接到查询字符串,没有任何验证。我们需要通过参数化查询字符串来调整代码,以确保传递的所有参数均正确地转义并用引号封装,从而不允许进一步利用。新的代码段如下所示:
$query = 'SELECT id, fname, lname, email FROM ' . $this->table . ' WHERE id = ?';
$query_count = 'SELECT count(*) FROM ' . $this->table . ' WHERE id = ?';
$statement_count = $this->conn->prepare($query_count);
$statement_count->bindParam(1, $this->id);
$statement_count->execute();
$statement = $this->conn->prepare($query);
$statement->bindParam(1, $this->id);
$statement->execute();
步骤6.重新扫描以确认分辨率
转到扫描漏洞列表,然后选择您试图修复的漏洞。
现在,单击“ 重新测试”按钮-这将创建一个新的扫描,以再次测试所选的漏洞。结果将表明您已经成功解决了这些漏洞。
