Модуль, запускаемый при помощи fuchsia-franzisrunner¶
Терминология¶
- Конечная точка — описание HTTP-запроса в расширенном формате HAR.
- Тег — сущность, описываемая меткой (формат — строка) и атрибутами (формат — объект в нотации JSON). Является результатом работы модуля.
- Отчет об уязвимости — тег, атрибуты которого соответствуют JSON-схеме.
Компонент fuchsia-franzisrunner¶
Компонент fuchsia-franzisrunner — это программный компонент, задачами которого являются запуск модулей сканера Fuchsia и запись тегов в базу данных. Запускаемые модули индифферентны к схеме базы данных, но для корректного взаимодействия обязаны соблюдать протокол общения с fuchsia-franzisrunner, описанный ниже.
Протокол взаимодействия fuchsia-franzisrunner и запускаемого модуля¶
Входные данные модуля¶
При запуске модуля компонент fuchsia-franzisrunner передает входные данные модулю посредством их записи в стандартный поток ввода stdin.
Допустимые входные данные:
- конечная точка в формате JSON;
- тег от любого другого модуля в формате JSON;
- URL-адрес ресурса в строковом формате.
Примечание
Компонент fuchsia-franzisrunner запускает один процесс модуля и передает ему на вход только одну конечную точку. Если необходимо передать N конечных точек (N > 0), то за время работы fuchsia-franzisrunner запустит N процессов модуля и каждому из них передаст по одной конечной точке.
Каждой сущности из списка выше соответствует режим работы, для выбора которого используется флаг -mode. Для получения выборки входных данных с более сложными условиями можно использовать фильтры с помощью флага -filter. Для определения получаемых тегов используется флаг -label (фильтрация по метке, с которой тег был записан в базу данных).
Значения по умолчанию для используемых флагов представлены в таблице ниже.
| Флаг | Значение по умолчанию |
|---|---|
-mode |
deps |
-filter |
none |
-label |
не задано |
Подробнее о режимах работы и используемых фильтрах в таблице ниже.
-mode) |
Фильтр -filter) |
Передаваемые модулю данные |
deps |
none |
Все найденные конечные точки в формате JSON. |
tagged |
Все найденные конечные точки в формате JSON, имеющие тег с меткой, соответствующей значению переданного флага -label |
|
associated-by-label |
Все найденные конечные точки в формате JSON, ассоциированные с веб-ресурсом, имеющий тег с меткой, соответствующей значению переданного флага -label |
|
pages |
none |
Все найденные URL-адреса веб-страниц в строковом представлении, прошедшие дополнительную фильтрацию по значению заголовка Content-Type (соответствует значению text/html или application/xhtml+xml) |
root |
Только корневой URL-адрес сканируемого приложения | |
all |
Все найденные URL-адреса веб-страниц в строковом представлении, без дополнительной фильтрацию по значению заголовка Content-Type |
|
tagged |
Все найденные URL-адреса веб-страниц в строковом представлении, имеющие тег с меткой, соответствующей значению переданного флага -label |
|
tags |
none |
Атрибуты тегов в формате JSON, имеющих метку, соответствующую значению переданного флага -label |
Примеры¶
Примечание
В рассматриваемых примерах ... заменяет набор доступных флагов, который опционален для конкретных примеров. first-scanner — значение флага -label.
-
Модуль предусматривает работу на всех найденных конечных точках:
Это эквивалентно примеру ниже благодаря использованию значений по умолчанию:
-
Модуль предусматривает работу только на корневом URL-адресе сканируемого приложения:
-
Модуль предусматривает работу на всех найденных тегах, полученных от другого модуля:
Это эквивалентно примеру ниже благодаря использованию значения по умолчанию:
-
Модуль предусматривает работу только на конечных точках, которые имеют тег от другого модуля:
-
Модуль предусматривает работу на всех найденных URL-адресах веб-страниц, с фильтрацией по
Content-Type:Это эквивалентно примеру ниже благодаря использованию значений по умолчанию:
Выходные данные модуля¶
Для корректной работы модуль должен писать в стандартный поток вывода stdout только результат своей работы. Все записи журнала и прочий вывод должны передаваться в стандартный поток вывода ошибок stderr. Записи журнала, записанные в stderr, сохраняются отдельно. Их можно получить с помощью команды fuchsiactl logs.
Модуль должен записывать результаты своей работы в формате NDJSON (поток JSON-объектов, разделенных переводом строки). Результатом работы модуля может быть:
- пустой вывод;
- один JSON-объект;
- несколько JSON-объектов разделенных переводом строки.
Для дальнейшей идентификации результатов работы модуля используются метки, получаемые с флагом -label.
- Если все результаты модуля можно воспринимать одинаково, метка задается при помощи флага
-scanner. - Если один модуль может порождать результаты работы, метки которых отличаются, то необходимо использовать флаг
-labelsFromScannerи специальный формат JSON-тега.
Флаг -scanner должен быть задан в обоих случаях.
Примеры¶
-
Запускаемый модуль пишет теги с одинаковыми метками
example-scanner.Запуск будет выглядеть следующим образом:
В стандартный поток вывода сканер может передать следующий набор тегов:
{"type": "IssueFound", "issue": "...", "url": "https://example.com/path1"} {"type": "IssueFound", "issue": "...", "url": "https://example.com/path2"}В результате работы модуля в базу данных запишется два тега с
label=example-scanner.-
В атрибуты первого тега попадет объект:
-
В атрибуты второго тега попадет объект:
-
-
Запускаемый модуль пишет теги с разными метками.
Запуск будет выглядеть следующим образом:
В стандартный поток вывода сканер может передать следующий набор тегов в специальном формате:
{"label": "example-scanner1", "attributes": {"type": "IssueFound", "issue": "...", "url": "https://example.com/path1"}} {"label": "example-scanner2", "attributes": {"type": "IssueFound", "issue": "...", "url": "https://example.com/path2"}}В результате работы модуля в базу данных запишется:
-
один тег с
label=example-scanner1, атрибутами которого будет являться объект:
-
один тег с
label=example-scanner2, атрибутами которого будет являться объект:
-
В случае, если результат работы модуля должен отображаться в разделе «Уязвимости» в панели управления, тогда в качестве метки тега должно быть задано имя модуля, а в качестве атрибутов тега передаваться JSON объект, соответствующий схеме. Если корректное отображение не обязательно (например, если модуль сохраняет какую-либо промежуточную информацию для следующего модуля) в качестве формата атрибутов тега может быть любой допустимый JSON-объект.
Флаги fuchsia-franzisrunner¶
Полный список флагов можно получить, выполнив команду fuchsia-franzisrunner -help. В данном разделе будут описаны основные флаги.
-
-mode— выбор формата входных данных для передачи модулю. На текущий момент поддерживаются следующие режимы:deps— в качестве входных данных модулю подаются все найденные конечные точки;-
tags— в качестве входных данных модулю подаются теги из базы данных, записанные туда другими модулями. При использовании данного режима работы необходимо также указывать флаг-labelи в нем указывать метку модуля, теги которого будут использоваться в качестве входных данных. Например, есть модуль, который записал тег вида{"key": "value"}с меткойtest-scanner. Если модуль должен работать с данными тегами в качестве входных данных, запускfuchsia-franzisrunnerбудет выглядеть следующим образом:
pages— в качестве входных данных модулю подаются URL-адреса найденных веб-страниц. Ресурсы фильтруются по значению заголовкаContent-Type. В данном режиме будут использованы только ресурсы,Content-Typeдля которых соответствуетtext/htmlилиapplication/xhtml+xml;
-
-filter— выбор дополнительных условий, регулирующих, какие входные данные будут переданы модулю. На текущий момент поддерживаются следующие режимы:none— без дополнительных условий;root— получить только корневой URL-адрес сканируемого приложения. Работает только при-mode=pages;all— получить все найденные URL-адреса веб-страниц без дополнительного ограничения по значению заголовкаContent-Type. Работает только при-mode=pages;tagged— получить все найденные конечные точки или URL-адреса страниц, имеющие тег с меткой, соответствующей значению переданного флага-label. Работает только при-mode=depsили-mode=pages;associated-by-label— получить все найденные конечные точки, ассоциированные с веб-ресурсом, имеющим тег с меткой, соответствующей значению переданного флага-label. Работает только при-mode=deps;
-scanner— имя запускаемого сканера, которое будет использовано в качестве метки при записи результатов в базу данных;-label— метка для выбора определенных условий (получить теги с определенной меткой; получить конечные точки, у которых есть тег с определенной меткой)-concurrency— количество процессов модуля, запущенных в один момент (например, при-concurrency 4одновременно будет запущено 4 процесса модуля с разными входными данными);-autocommit— сообщаетfuchsia-franzisrunner, что нужно закрыть транзакцию в базе данных после каждого записанного тега, а не в конце работы всех запущенных процессов модуля;-labelsFromScanner— сообщаетfuchsia-franzisrunner, что запускаемый модуль будет записывать свои результаты с разными метками;-domainList— список разрешенных доменов для сопоставления относительно политики, выбранной в-domainScope. Если-domainListпуст, то он будет автоматически задан из домена сканируемой цели.-pageDeduplication— дедупликация ресурсов, на которых запускается модуль (в режиме работыpages). Включена по умолчанию. Для отключения использовать флаг со значениемfalse.-
-depDeduplication— дедупликация конечных точек, на которых запускается модуль. Поддерживаемые значения:none— дедупликация выключена;default— дедупликация при помощи нормализации;extended— расширенная дедупликация;
-
-domainScope— описывает политики запуска на конечных точках, исходя из домена в найденном HTTP-запросе. Допустимые значения:any— запуск на любых конечных точках независимо от домена;same— домен полностью совпадает с одним из указанных в списке разрешенных в-domainList;subdomain— домен полностью совпадает с одним из указанных в списке разрешенных или является поддоменом какого-либо домена из списка разрешенных в-domainList;secondlevel/second-level— домен полностью совпадает с одним из указанных в списке разрешенных или домен второго уровня совпадает с одним из доменов из списка разрешенных в-domainList. Для поиска доменов второго уровня используется «Public Suffix List». Является значением по умолчанию;
-scanId— номер сканирования, с сущностями которого будет работатьfuchsia-franzisrunner. Без указания этого флага будут использоваться все сканирования, имеющиеся на момент запуска в базе данных. Заполняется при помощи шаблонизированного значения;-config— путь до конфигурационного файлаfuchsiad. Заполняется при помощи шаблонизированного значения.
Переменные окружения¶
Компонент fuchsia-franzisrunner передает модулям настройки при помощи переменных окружения:
FUCHSIA_PROXY— адрес HTTP-прокси в форматеip:port. Настоятельно рекомендуется использоватьFUCHSIA_PROXYв качестве прокси, даже если модуль не планируется запускать через прокси. Внутри сканера Fuchsia используется собственный прокси, журналирующий запросы и выполняющий авторизацию. Если при запуске сканирования была использована функция передачи Upstream Proxy, то при использованииFUCHSIA_PROXYзапросы будут маршрутизированы корректно.FUCHSIA_RPS_LIMIT— значение, огранивающее RPS, если данный параметр был задан в настройках сканирования. Внутренний прокси сканера Fuchsia занимается лимитированием запросов самостоятельно (исходя из этой настройки). Однако, если модуль выполняет специфические атаки (например, основанные на времени проверок), то рекомендуется самостоятельно настраивать ограничения RPS во избежание ложных срабатываний.
Конфигурационные файлы модулей¶
Для работы модуля в составе сканера Fuchsia необходимо, чтобы конфигурационный файл модуля удовлетворял определенным условиям.
Расположение конфигурационного файла¶
- Если
fuchsiadразвернута в системе с помощью deb-пакетов, конфигурационные файлы модулей должны находиться в директории/usr/lib/fuchsia/pipeline.d, если в директивеscan_pipelineконфигурационного файлаfuchsiadне сказано иное. - Если
fucshiadразвернута с помощью Docker Compose, файлы должны храниться в/etc/fuchsia/pipeline.d, если в директивеscan_pipelineконфигурационного файлаfuchsiadне сказано иное.
После того, как конфигурационный файл модуля попадёт в директорию с аналогичными файлами других модулей, для регистрации модуля в пайплайне fuchsiad необходимо выполнить команду fuchsiactl reload для обновления конфигурации. Убедиться, что модуль зарегистрирован в пайплайне, можно, выполнив команду fuchsiactl list_modules.
Имя конфигурационного файла¶
Файл должен быть назван в формате N-scanner-name.yml, где N — приоритет модуля, scanner-name — имя модуля. Приоритет определяется следующим образом: сначала идут модули, выявляющие поверхность атаки (приоритет до 30 включительно), далее идут модули сигнатурного анализа (приоритет до 60 включительно). Далее идут модули взлома. Например, модули взлома конечных точек имеют приоритет 80. Приоритет, помимо определения порядка запуска, влияет на параллельность запускаемых модулей. Параллельно могут запускаться только модули с одинаковым приоритетом.
- Для модулей, работающих с использованием URL-адресов страниц, предлагается использовать приоритет
75. - Для модулей, работающих с использованием конечных точек, предлагается использовать приоритет
80.
Строение конфигурационного файла¶
Чтобы запустить модуль в составе сканера Fuchsia, необходимо добавить в yaml-конфигурацию описание того, как он должен запускаться. Оно может выглядеть следующим образом:
name: requester
args_template:
[
"fuchsia-franzisrunner",
"-config",
"{{.ConfigPath}}",
"-scanId",
"{{.ScanId}}",
"-scanner",
"requester",
"-concurrency",
"4",
"--",
"/usr/bin/requester",
]
may_fail: true
Модуль запускается в соответствии с протоколом fuchsia-franzisrunner, что обеспечивает простоту его интеграции в сканер Fuchsia.
Параметры {{.ConfigPath}} и {{.ScanId}} представляют собой шаблонизированные значения, обработкой которых занимается сканер Fuchsia
с использованием библиотеки Go text/template.
Параметр name¶
Имя модуля. В дальнейшем может быть использовано, например, для запроса записей журнала конкретного модуля.
Параметр args_template¶
Параметризованная команда запуска сканирующего модуля. В соответствии с протоколом fuchsia-franzisrunner составляется из
команды запуска fuchsia-franzisrunner, разделителя «--» и команды запуска сканирующего модуля. Во всех частях этой команды
можно использовать шаблонизированные значения {{.ScanId}} и другие.
Полный список параметров, которые можно передать модулю с помощью сканера Fuchsia, указав в конфигурационном файле модуля:
-
{{.Request}}— содержит в себе объект Scan Request, получаемый сканером Fuchsia для создания сканирования. Содержит конфигурацию сканирования;Пример использования:
{{.URL}}— содержит URL-адрес, на котором запускалось сканирование;{{.ScanId}}— содержит идентификатор текущего сканирования. Необходим для правильной работыfuchsia-franzisrunner;{{.ConfigPath}}— содержит путь к конфигурационному файлуfuchsiad. Необходим для правильной работыfuchsia-franzisrunner;-
{{.Files}}— используется для передачи файлов от сканера Fuchsia к модулю сканирования;Пример использования:
-
{{.ExtraSettings}}— используется для передачи произвольных дополнительных параметров модулю сканирования (см. флаг--extra-settingуfuchsiactl);Пример использования:
При запуске сканирования дополнительная переменная подаётся на вход следующим образом:
Параметр may_fail¶
Если параметр may_fail имеет значение false или не задано, то в случае завершения работы модуля с ненулевым кодом возврата следующие в очереди модули запущены не будут, а статус всего сканирования будет ошибочным.
Параметр priority¶
Позволяет указать приоритет модуля прямо в конфигурационном файле, а не в его имени. Настройка priority из конфигурационного файла более приоритетна, чем из имени конфигурационного файла.
Например, конфигурационный файл с именем 60-requester.yml имеет следующее содержимое:
name: requester
args_template:
[
"fuchsia-franzisrunner",
"-scanId",
"{{.ScanId}}",
"-config",
"{{.ConfigPath}}",
"--",
"requester",
]
priority: 30
may_fail: true
Так как в конфигурационном файле параметр priority имеет значение 30, модуль requester будет иметь приоритет 30, а значение 60 в имени конфигурационного файла будет проигнорировано.
Параметр condition¶
Условие запуска модуля. Обрабатывается с использованием библиотеки Go text/template в том же контексте, что и параметр args_template. В условии можно использовать аналогичный список параметров. Если значение condition не задано, то модуль будет запускаться всегда. Если condition задан, а результат обработки шаблона возвращает пустую строку, то модуль не будет запущен.
Пример использования:
name: openapi-hars-generator
args_template:
[
"fuchsia-franzisrunner",
"-scanId",
"{{.ScanId}}",
"-config",
"{{.ConfigPath}}",
"--",
"fuchsia-openapi-hars-generator",
"--mode",
"module",
"--spec",
"{{.Files.OpenAPISpec}}",
]
condition: '{{ index .Files "OpenAPISpec" }}'
may_fail: true
Примеры модулей сканирования¶
Модуль, принимающий на вход конечные точки¶
Модуль, написанный на языке Go
package main
import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
)
type KeyValue struct {
Name string `json:"name"`
Value string `json:"value"`
Type string `json:"type,omitempty"`
Filename string `json:"filename,omitempty"`
ContentType string `json:"content_type,omitempty"`
}
type Parameters []*KeyValue
type PostData struct {
MimeType string `json:"mimeType"`
Text string `json:"text"`
Params Parameters `json:"params,omitempty"`
}
type HAR struct {
Method string `json:"method"`
URL string `json:"url"`
HTTPVersion string `json:"httpVersion"`
Headers Parameters `json:"headers"`
QueryString Parameters `json:"queryString"`
BodySize int `json:"bodySize"`
PostData *PostData `json:"postData,omitempty"`
}
type Result struct {
StatusCode int
URL string
}
func main() {
var proxyAddr string
if x, ok := os.LookupEnv("FUCHSIA_PROXY"); ok {
proxyAddr = "http://" + x
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
if proxyAddr != "" {
proxy, err := url.ParseRequestURI(proxyAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to parse proxy URL: %v\n", err)
os.Exit(1)
}
transport.Proxy = http.ProxyURL(proxy)
}
c := http.Client{
Transport: transport,
}
dec := json.NewDecoder(os.Stdin)
h := HAR{}
err := dec.Decode(&h)
if err != nil && err != io.EOF {
fmt.Fprintf(os.Stderr, "failed to read HAR: %v\n", err)
os.Exit(1)
}
r, err := c.Get(h.URL)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create request: %v\n", err)
os.Exit(1)
}
res := &Result{
StatusCode: r.StatusCode,
URL: h.URL,
}
reportJson, err := json.Marshal(res)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal result: %v\n", err)
os.Exit(1)
}
fmt.Println(string(reportJson))
}
Конфигурация для этого модуля может выглядеть следующим образом:
name: requester
args_template:
[
"fuchsia-franzisrunner",
"-config",
"{{.ConfigPath}}",
"-scanId",
"{{.ScanId}}",
"-scanner",
"requester",
"-concurrency",
"4",
"--",
"/usr/bin/requester",
]
Модуль, принимающий на вход URL-адреса страниц¶
Модуль, написанный на языке JavaScript
const puppeteer = require("puppeteer");
(async () => {
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
});
let url;
rl.on("line", (input) => {
url = input;
rl.close();
});
rl.on("close", async () => {
if (!url) {
console.error("No URL provided");
process.exit(1);
}
const proxy = process.env.FUCHSIA_PROXY;
let browser;
try {
const args = proxy ? [`--proxy-server=${proxy}`] : [];
browser = await puppeteer.launch({
headless: "new",
args: args,
});
const page = await browser.newPage();
const response = await page.goto(url, {
waitUntil: "networkidle0",
});
const result = {
StatusCode: response.status(),
URL: url,
};
console.log(JSON.stringify(result));
} catch (err) {
console.error(`Failed to process the request: ${err.message}`);
process.exit(1);
} finally {
if (browser) {
await browser.close();
}
}
});
})();
Для удобства запуска можно использовать следующий Shell-скрипт open-page:
#!/usr/bin/env bash
export PUPPETEER_CACHE_DIR=/usr/lib/fuchsia/page-opener/node_modules/puppeteer/.cache/
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/fuchsia-chrome
exec node /usr/lib/fuchsia/page-opener/open-page.js "$@"
Конфигурация для этого модуля может выглядеть следующим образом: