
Библиотека Python для Turbo Intruder, которая добавляет поддержку позиций полезной нагрузки и типы атак Sniper/Clusterbomb/Pitchfork с генерацией тестов на основе тегов для HTTP-фаззинга.

Haptyc — это библиотека на Python, созданная для добавления поддержки позиций полезной нагрузки и типов атак Sniper/Clusterbomb/Batteringram/Pitchfork в Turbo Intruder. Haptyc достаточно хорошо справляется с этими задачами, а также предлагает более простой способ описания тестовых последовательностей в целом. Хотя эта библиотека предназначена для Turbo Intruder, у неё нет жёстких зависимостей от него, и её можно использовать везде, где требуется генерация тестов в контексте Python. К сожалению, на данный момент, поскольку Haptyc создавался для интерпретатора Jython, он поддерживает только Python 2.7 (однако будущие изменения исправят это).
Теги Haptyc — это теги, с помощью которых тестировщик может аннотировать исходную входную полезную нагрузку. Можно использовать несколько тегов, чтобы окружить ключевые фрагменты данных в HTTP-запросе, превратив их в позиционные нагрузки. При генерации тестов Haptyc разберёт все теги в исходной нагрузке и сгенерирует тесты в соответствии с функциями, связанными с именами тегов. Когда Haptyc вычисляет тег Haptyc, он выполняет связанную с ним функцию (называемую Haptyc Transform) для тестовой нагрузки, помещая результат на место соответствующего тега в запросе. Каждая функция тега получает аргументы data и state. Аргумент data может содержать внутренние данные тега или другую последовательность тестовой нагрузки. Аргумент state — это объект состояния, связанный с тегом, в котором можно хранить состояние между итерациями теста. Рассмотрим пример.
Исходная нагрузка:
GET /animal/[+GuessAnimal]dog[+end] HTTP/1.1
Класс Haptyc и Haptyc Transform:
from haptyc import *
original = "GET /animal/[+GuessAnimal]dog[+end] HTTP/1.1"
class TestLogic(Transform):
@ApplyList(["snake","cat","owl","lion"])
def test_GuessAnimal(self, data, state):
return data + "?original=" + self.inner() + "&attempt=" + str(state.iter)
TestFactory = TestLogic(original)
for test in TestFactory:
print(test)
Сгенерированные тесты:
GET /animal/snake?original=dog&attempt=0 HTTP/1.1
GET /animal/cat?original=dog&attempt=1 HTTP/1.1
GET /animal/owl?original=dog&attempt=2 HTTP/1.1
GET /animal/lion?original=dog&attempt=3 HTTP/1.1
В примере выше показано, как можно просто выразить тесты с помощью Haptyc. Сначала импортируется библиотека Haptyc. Затем определяются исходные данные с аннотациями тегов Haptyc (GuessAnimal). Далее объявляется класс TestLogic, наследующий Transform. Внутри этого класса каждый метод, начинающийся с test_, регистрируется как тег Haptyc для вычисления в исходной нагрузке. Мы используем декоратор логики для применения логики состояния к этому преобразованию Haptyc. В данном случае мы используем декоратор @ApplyList(list), чтобы указать Haptyc генерировать тест для каждого элемента указанного списка и передавать этот элемент в преобразование Haptyc в качестве аргумента data. Внутри преобразования мы возвращаем мутированную версию data для вставки обратно на место тега. В данном случае мутация представляет собой элемент списка как data, объединённый с данными, окружёнными тегом (dog), и затем объединённый со значением iter из объекта state. Наконец, оставшаяся часть кода Python показывает создание объекта TestFactory и генерацию всех тестов в цикле for. Это пример стандартной атаки типа Sniper, нацеленной на одну позицию нагрузки. Далее рассмотрим другие типы атак.
Исходная нагрузка:
GET /animal?type=[%type]dog[%end]&name=[%name]fido[%end] HTTP/1.1
Класс Haptyc и Haptyc Transform:
from haptyc import *
original = "GET /animal?type=[%type]dog[%end]&name=[%name]fido[%end] HTTP/1.1"
class TestLogic(Transform):
@ApplyList("snake","cat","owl","lion")
def test_type(self, data, state):
return data
@ApplyList("Frank", "Lisa", "Jin", "Tooth")
def test_name(self, data, state):
return data
TestFactory = TestLogic(original)
for test in TestFactory:
print(test)
Сгенерированные тесты:
GET /animal?type=snake&name=Frank HTTP/1.1
GET /animal?type=snake&name=Lisa HTTP/1.1
GET /animal?type=snake&name=Jin HTTP/1.1
GET /animal?type=snake&name=Tooth HTTP/1.1
GET /animal?type=cat&name=Frank HTTP/1.1
GET /animal?type=cat&name=Lisa HTTP/1.1
GET /animal?type=cat&name=Jin HTTP/1.1
GET /animal?type=cat&name=Tooth HTTP/1.1
GET /animal?type=owl&name=Frank HTTP/1.1
GET /animal?type=owl&name=Lisa HTTP/1.1
GET /animal?type=owl&name=Jin HTTP/1.1
GET /animal?type=owl&name=Tooth HTTP/1.1
GET /animal?type=lion&name=Frank HTTP/1.1
GET /animal?type=lion&name=Lisa HTTP/1.1
GET /animal?type=lion&name=Jin HTTP/1.1
GET /animal?type=lion&name=Tooth HTTP/1.1
Пример 1 показал, как вычислять преобразования в стиле Sniper, используя знак '+' в теге [+tag][+end]. Пример 2 показывает, как можно использовать 2 преобразования/позиции для проведения атаки в стиле Clusterbomb. Как видите, мы используем два отдельных тега преобразования: [%type][%end] и [%name][%end]. Знак '%' сообщает Haptyc, что эти преобразования должны вычисляться в стиле Clusterbomb: для каждой нагрузки из первого преобразования создаётся тест с нагрузкой из второго преобразования. Количество тестов равно количеству тестов каждого участвующего преобразования, умноженному друг на друга.
Используя тот же самый код Python, можно переключить стиль атаки с Clusterbomb на Pitchfork, заменив '%' на '#'. Атаки в стиле Pitchfork размещают позиционные нагрузки параллельно. Количество тестов равно наименьшему количеству тестов среди всех участвующих преобразований.
Исходная нагрузка:
GET /animal?type=[#type]dog[#end]&name=[#name]fido[#end] HTTP/1.1
Сгенерированные тесты:
GET /animal?type=snake&name=Frank HTTP/1.1
GET /animal?type=cat&name=Lisa HTTP/1.1
GET /animal?type=owl&name=Jin HTTP/1.1
GET /animal?type=lion&name=Tooth HTTP/1.1
Исходная нагрузка:
GET /animal?type=dog&id=[+idor]0[+end]&process=[@randbool]False[@end] HTTP/1.1
Класс Haptyc и Haptyc Transform:
from haptyc import *
import random
original = "GET /animal?type=dog&id=[+idor]0[+end]&process=[@randbool]False[@end] HTTP/1.1"
class TestLogic(Transform):
@ApplyIteration(10)
def test_idor(self, data, state):
return str(state.iter)
def per_randbool(self, data):
return random.choice(["True", "False"])
TestFactory = TestLogic(original)
for test in TestFactory:
print(test)
Сгенерированные тесты:
GET /animal?type=dog&id=0&process=False HTTP/1.1
GET /animal?type=dog&id=1&process=True HTTP/1.1
GET /animal?type=dog&id=2&process=False HTTP/1.1
GET /animal?type=dog&id=3&process=True HTTP/1.1
GET /animal?type=dog&id=4&process=False HTTP/1.1
GET /animal?type=dog&id=5&process=True HTTP/1.1
GET /animal?type=dog&id=6&process=False HTTP/1.1
GET /animal?type=dog&id=7&process=True HTTP/1.1
GET /animal?type=dog&id=8&process=False HTTP/1.1
GET /animal?type=dog&id=9&process=False HTTP/1.1
Постоянные преобразования обозначаются знаком '@', и их функции всегда начинаются с per_, потому что эти преобразования не итеративны, они не создают тесты и не хранят состояние. Это просто наивные преобразования, которые можно применить в любом месте нагрузки для преобразования без сохранения состояния, не влияя на преобразования с состоянием. Поскольку они не задают тесты, одними постоянными преобразованиями тесты не сгенерировать — они предназначены для смешивания с итеративными преобразованиями. В приведённом выше примере у нас есть преобразование в стиле Sniper на 10 тестов, помещающее увеличивающийся идентификатор. А также постоянное преобразование, помещающее случайное логическое значение в свою позицию.
Могут быть случаи, когда перед началом последовательности тестов тестировщик хочет выполнить некоторую обработку/инициализацию. Для поддержки этого Haptyc перед запуском преобразования для генерации теста выполняет все участвующие преобразования на этапе инициализации. Этот шаг инициализации можно использовать для выполнения любой необходимой инициализации и сохранения её в объекте state. Для этого тестировщик может использовать state.init как логическое значение, чтобы определить, выполняется ли инициализация. Любые возвращаемые данные на этапе инициализации будут проигнорированы.
Исходная нагрузка:
GET /animal?data=[+b64mutate]SGVsbG8gSGFja2VyIQ==[+end] HTTP/1.1
Класс Haptyc и Haptyc Transform:
from haptyc import *
import base64
original = "GET /animal?data=[+b64mutate]SGVsbG8gSGFja2VyIQ==[+end] HTTP/1.1"
class TestLogic(Transform):
@ApplyIteration(10)
def test_b64mutate(self, data, state):
if state.init:
state.decoded = base64.b64decode(data)
return
return base64.b64encode(random_insert(state.decoded, ["'"]))
TestFactory = TestLogic(original)
for test in TestFactory:
print(test)
Сгенерированные тесты:
GET /animal?data=SGVsbG8gSCdhY2tlciE= HTTP/1.1
GET /animal?data=SGVsbG8gSGFja2VyISc= HTTP/1.1
GET /animal?data=SGVsbG8gSGFjaydlciE= HTTP/1.1
GET /animal?data=SGVsbG8gSGEnY2tlciE= HTTP/1.1
GET /animal?data=SCdlbGxvIEhhY2tlciE= HTTP/1.1
GET /animal?data=SGVsbG8gSGFjaydlciE= HTTP/1.1
GET /animal?data=SGVsbG8gSGFja2VyISc= HTTP/1.1
GET /animal?data=SCdlbGxvIEhhY2tlciE= HTTP/1.1
GET /animal?data=SGVsbG8gSGFjJ2tlciE= HTTP/1.1
GET /animal?data=SGVsbG8gSGFjJ2tlciE= HTTP/1.1
В примере выше тест использует state.init для декодирования base64 внутренней нагрузки только один раз в начале последовательности тестов и сохраняет результат в state.decoded. Затем при всех обычных выполнениях генерации тестов state.decoded используется как декодированные внутренние данные для обработки. Такой шаблон полезен для повышения производительности преобразования, так как декодирование происходит только один раз в начале (вместо декодирования одной и той же нагрузки при генерации каждого теста).
[+tag]inner[+end] — итеративное преобразование в стиле Sniper[%tag]inner[%end] — итеративное преобразование в стиле Clusterbomb[#tag]inner[#end] — итеративное преобразование в стиле Batteringram/Pitchfork[@tag]inner[@end] — постоянное преобразование без сохранения состояния| Имя | Аргументы | Описание |
|---|---|---|
| @CloneTransform(srcname, destname) | srcname = строка с именем метода преобразования, копируемого из; destname = строка с именем несуществующего метода преобразования, в который копируется | CloneTransform используется для копирования реализации одного преобразования в другое пространство имён без необходимости копировать/вставлять код. Это полезно в атаках '%' и '#', когда нужно повторно использовать одну и ту же реализацию преобразования в нескольких позициях |
| Имя | Описание |
|---|---|
| self.inner() | Извлекает внутреннюю нагрузку тега |
| Имя | Описание |
|---|---|
| state.iter | Текущее количество итераций преобразования (начиная с 0) |
| state.init | Логическое значение, указывающее, находимся ли на этапе инициализации |
| Имя | Описание |
|---|---|
| radamsa(data) | Эта функция запускает radamsa на входных данных и возвращает результат (требуется установка radamsa) |
| index_insert(data, list, index) | Эта функция вставляет нагрузку из списка в переданные данные по указанному индексу |
| random_insert(data, list) | Эта функция вставляет нагрузку из списка в переданные данные в случайный индекс |
Есть 2 способа установки Haptyc
turbo-intruder-all_w_haptyc.jarНезависимо от выбранного способа, эти релизы не включают radamsa, и если вам нужна поддержка radamsa, необходимо установить его из этого репозитория: (Опционально) Установите radamsa через https://gitlab.com/akihe/radamsa
turbo-intruder-all_w_haptyc.jar в папке releaseAddSelect File ... и выберите turbo-intruder-all_w_haptyc.jar./install.sh <абсолютный путь к папке с turbo-intruder-all.jar>| Имя | Аргументы | Входные data | Описание |
|---|
| @ApplyIteration(n) | n = количество итераций | внутреннее значение тега haptyc | Логика для генерации N тестов с внутренним значением в качестве data |
| @ApplyRange(b,e,s=1) | b = начальное значение, e = максимальное значение, s = шаг | сгенерированное значение диапазона | Логика для генерации теста для каждого значения с шагом, причём значение передаётся как data |
| @ApplyList(L) | L = список Python | элемент списка | Логика для генерации теста для каждого значения из списка, передаваемого как data |
| @ApplyFilelist(path) | path = путь в файловой системе | элемент списка | Логика для генерации теста для каждого значения из файлового списка, передаваемого как data |
| @ApplyPayloads(name) | name = имя встроенного списка | элемент списка | Логика для генерации теста для каждого значения из встроенного списка, передаваемого как data |
| self.stop() |
| Немедленно останавливает генерацию тестов для данного преобразования |
| self.me() | Возвращает имя текущего контекста преобразования |
| self.set_label(label) | Устанавливает метку для текущего теста |
| self.get_label(label) | Получает метку для текущего теста |