掃描 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.重新掃描以確認分辨率
轉到掃描漏洞列表,然后選擇您試圖修復的漏洞。

現在,單擊“ 重新測試”按鈕-這將創建一個新的掃描,以再次測試所選的漏洞。結果將表明您已經成功解決了這些漏洞。