Вступление

Всем привет! На связи Владимир Исабеков, руководитель группы статического тестирования безопасности приложений в Swordfish Security. В предыдущей статье мы рассказывали о Stateful REST API-фаззинге с применением инструмента RESTler. Сегодня мы поговорим о продвинутых возможностях RESTler-а и покажем, как настроить фаззер на примере более сложного приложения. Этот материал мы подготовили вместе с Артемом Мурадяном @TOKYOBOY0701, инженером по безопасности.

Выбираем приложение для фаззинга

В качестве подопытного возьмем приложение Vampi – это заранее уязвимый API, созданный с помощью Flask и включающий в себя уязвимости из OWASP Top 10 API. После регистрации в Vampi пользователь может авторизоваться в системе и опубликовать книгу, используя токен, полученный при входе. Для размещения необходимо ввести название и секрет книги. Только ее владельцу разрешено просматривать этот секрет.

Проводим фаззинг

Итак, мы определились с приложением и готовы перейти непосредственно к фаззингу. Рассмотрим его основные этапы и покажем возможности инструмента RESTler.

1. Компиляция грамматики

Начнем с компиляции грамматики из спецификации API. Для этого скомпилируем спецификацию Vampi:

C:\restler_bin\restler\Restler.exe compile --api_spec C:\Vampi\openapi3.yml

Подробнее о том, как компилируется грамматика, можно узнать в нашей первой статье.

2. Настройка аутентификации

Так как в приложении присутствует аутентификация, нам необходимо, чтобы фаззер мог ее проходить. Существует несколько способов сделать это:

Способ 1. Текстовый файл

Первый и самый простой способ – это чтение текстового файла с токеном, который будет применяться для аутентификации. Чтобы фаззер использовал файл с токеном, нужно указать его в специальном конфигурационном файле Engine_settings (при последующем запуске фаззера путь к файлу указывается в параметре --settings):

"authentication": {
    "token": {
      "location": "/путь/до/токена.txt"
    }
}

Способ 2. Скрипт

Это программа, которая возвращает токен. Скрипт можно указать в том же конфигурационном файле Engine_settings:

"authentication": {
    "token": {
      "token_refresh_cmd": "python acquire_token.py",
      "token_refresh_interval": 200
    }
}

Еще его можно разместить в параметрах командной строки при запуске фаззера

--token_refresh_command "python C:\restler-fuzzer-main\acquire_token.py" --token_refresh_interval 200

Способ 3. Модуль
Это подключаемый модуль, который реализует аутентификацию. RESTler импортирует его и вызывает функцию для получения токенов. Как и в предыдущих способах, модуль указывается в конфигурационном файле Engine_settings:

"authentication": {
    "token": {
      "module": {
        "file": "/путь/до/acquire_token.py",
        "function": "acquire_token_data", #(функция по умолчанию «acquire_token»)
        "data": {
            "client_id": "client_id"
        }
      },
      "token_refresh_interval": 200
    }
}

В отличие от скрипта, в модуль можно передать дополнительные параметры, необходимые для аутентификации. Делается это для того, чтобы в будущем не было нужды изменять сам скрипт. Вне зависимости от способа, формат токена одинаковый: в первой строке метаданные, которые записываются в журнал, в следующих – заголовок и сам токен:

{u'app1': {<метаданные для отладки >}, u'app2':{}}
ApiTokenTag: 9A
ApiTokenTag: ZQ

Для выбранного приложения мы решили реализовать второй способ, так как в нем не требуется менять пользователей во время работы фаззера, но при этом нужно постоянно обновлять токен. Использовались две учетные записи – обычного пользователя и админа (для покрытия фаззингом админских ручек). Наш скрипт для аутентификации выглядит так:

import requests
import json
import os
from dotenv import load_dotenv

def acquire_token(username, password):
    url = "http://127.0.0.1:5000/users/v1/login"
    payload = json.dumps({
        "username": username,
        "password": password
    })
    headers = {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }
    response = requests.request("POST", url, headers=headers, data=payload)
    res = response.json()
   # print(res)
    stf = f'Authorization: Bearer {res["auth_token"]}'
    return stf
metadata = "{u'first_token': {u'username':u'admin'}, u'second_token': {u'username':'name1'}}"
load_dotenv()
lgn1 = os.getenv("login1")
pswd1 = os.getenv("password1")
lgn2 = os.getenv("login2")
pswd2 = os.getenv("password2")
print(metadata)
print(acquire_token(lgn1,pass1))
print(acquire_token(lgn2,pswd2))

После успешной компиляции нужно проверить, сколько API-методов покрывается тестами RESTler-a.

3. Тестовое покрытие

Запускаем фаззер в режиме Test (выполнение всех запросов в спецификации OpenAPI хотя бы один раз и получение в ответ кода состояния «200»). Затем выполняем команду:

C:\restler_bin\restler\restler.exe test --grammar_file C:\Vampi\Compile\grammar.py --dictionary_file C:\Vampi\Compile\dict.json --settings C:\restler-fuzzer-main\Compile\engine_settings.json --token_refresh_interval 2 --token_refresh_command "python C:\restler-fuzzer-main\acquire_token.py" --no_ssl

Видим результаты:

От трех интерфейсов RESTler не смог получить код состояния «200». От одного из них инструмент получил код состояния «500». По умолчанию фаззер считает все ответы с кодом «500» багами и сохраняет их в файл main_driver_500_{}.json. Мы разберем результаты как раз из ответа Internal server error.

4. Анализ логов

В логах (..\Test\RestlerResults\experiment{}\bug_buckets\main_driver_500_1.json) мы можем увидеть, какой эндпоинт вызвал ошибку (/books/v1) и traceback приложения. Также благодаря им можно выяснить причину проблемы. Ошибка произошла из-за того, что сначала был вызван метод удаления пользователя, а после – метод, который выводил книги всех юзеров (/books/v1).
Интересно, что этот баг не значится в списке уязвимостей от разработчика Vampi и сохраняется даже при переводе приложения в «неуязвимый режим», при котором остальные уязвимости не срабатывают.

5. Повышаем покрытие API

Теперь нужно повысить покрытие нашего API тестами фаззера. Чтобы разобраться в проблемах, необходимо изучить файл C:\Vampi\Test\coverage_failures_to_investigate.txt, в котором
находятся все неудачные запросы.

Запрос Get /users/v1/{username} пытается получить пользователя, которого нет в базе, а Get /books/v1/{book_title} – книгу, которая там тоже отсутствует. Эту проблему легко исправить, изменив примеры соответствующих запросов в спецификации API.

Третий же запрос — Get /books/v1. Поскольку уязвимость на данном эндпоинте уже была найдена, можно удалить его из спецификации, чтобы не получать лишние срабатывания. Также мы приняли решение убрать метод /createdb, так как он приводит базу данных приложения в исходное состояние, а это может помешать правильной работе фаззера при формировании цепочек вызовов (может, например, удалиться какой-нибудь объект, созданный ранее в ходе цепочки вызовов API). После этих действий получаем полное покрытие оставшихся API-методов:

6. Фаззинг

Теперь, когда корректно сформирована грамматика и все API методы покрыты тестами, можно приступить к фаззингу. Запускаем фаззер в режиме fuzz командой

C:\restler_bin\restler\restler.exe fuzz --grammar_file C:\Vampi\Compile\grammar.py --dictionary_file C:\Vampi\Compile\dict.json --settings C:\restler-fuzzer-main\Compile\engine_settings.json --token_refresh_interval 2 --token_refresh_command "python C:\restler-fuzzer-main\acquire_token.py" --no_ssl

Через несколько секунд работы фаззера приложение зависает, в логах срабатывает чекер InvalidValueChecker_timeout:

Как мы видим, при запросе к эндпоинту /users/v1/{username}/email, в параметр почты которого помещен большой массив данных, произошло зависание сервиса. Это случилось из-за того, что регулярное выражение для обработки почты написано неверно. Появилась возможность атаки RegexDOS, при которой приложение тратит очень много времени на обработку зловредной входящей строки с большим количеством альтернативных путей для регулярного выражения. Это может привести к отказу в обслуживании для других пользователей. Данную уязвимость можно классифицировать как API4:2023 Unrestricted Resource Consumption.

Чтобы продолжить фаззинг, мы изменили грамматику (grammar.py) таким образом, чтобы поле почты больше не изменялось в процессе.

В новой итерации фаззинга появилось несколько ошибок. Сработал чекер InvalidValueChecker. Он вставляет в параметры запроса различные недопустимые значения. В этом случае можно использовать кастомный словарь недопустимых значений или генератор. Подробнее о настройках чекеров можно почитать тут.

При запросе к эндпоинту /users/v1/{username}, в котором в параметр имени пользователя была подставлена сгенерированная строка, произошла ошибка сервера. Раскрытие этой информации можно классифицировать как API8:2023 - Security Misconfiguration. Как мы видим, в traceback присутствует сам запрос в базу данных, что говорит о возможном наличии SQL-инъекции.

Еще одна внутренняя ошибка сервера была получена при запросе к /books/v1.

Здесь была попытка создания книги, которая уже есть в базе. Это привело к ошибке и появлению traceback.

Фаззер может отлавливать ответы не только с кодом «500», но и с любыми другими. Для этого нужно указать в файле Engine_settings параметр custom_bug_codes со списком кодов состояния, которые будут помечены как ошибки, либо custom_non_bug_codes с перечнем кодов, которые не будут помечены как ошибки (а все остальные будут). В данном случае мы изменили конфигурационный файл так, чтобы RESTler отлавливал коды «200» для эндпоинта /users/v1/{username}/password. Также добавили параметр include_requests, что позволяет отфильтровать грамматику и посылать запросы только к указанным методам.

Таким образом можно проверить возможность несанкционированной смены пароля другими пользователями. В ходе последующего фаззинга в файле с багами main_driver_20x_1.json был обнаружен успешный запрос на смену пароля пользователю name2 (скрипт авторизации был изменен, чтобы фаззер в данном случае мог авторизоваться только от лица обычного пользователя name1)

7. Результаты

В ходе фаззинга были найдены 4 подтвержденных бага, 3 из которых приводят к уязвимостям. Мы также просканировали приложение с помощью DAST сканера OWASP ZAP. Результаты можно увидеть в таблице ниже.

Уязвимости

OWASP API top 10

Эндпоинты

RESTler

ZAP

SQL Injection

API8:2019 Injection

/users/v1/{username}

+

+

Unauthorized Password Change

API2:2023 Broken Authentication

/users/v1/{username}/password

+

Broken Object Level Authorization

API1:2023 Broken Object Level Authorization

/books/v1/{book_title}

Mass Assignment

API3:2023 Broken Object Property Level Authorization

/users/v1/register

Excessive Data Exposure

API3:2023 Broken Object Property Level Authorization

/users/v1/_debug; /books/v1

+

User and Password Enumeration

API2:2023 Broken Authentication

/users/v1/login

RegexDOS (Denial of Service)

API4:2023 Unrestricted Resource Consumption

/users/v1/{username}/email

+

Lack of Resources & Rate Limiting

API2:2023 Broken Authentication

/users/v1/login

Заключение

Сегодня мы продемонстрировали возможности и эффективность фаззера RESTler. Этот инструмент находит баги, которые могут приводить к серьезным уязвимостям API, например Excessive Data Exposure, Injection, Unrestricted Resource Consumption и Broken User Authentication. В будущих статьях цикла мы расскажем вам о различных режимах фаззинга, об аннотациях, а также о проблемах, возникающих при подготовке и проведении фаззинг-тестирования API, и способах их решения. Оставайтесь с нами!

Комментарии (0)