Перейти к содержанию

Модуль, запускаемый при помощи fuchsia-franzisrunner

Терминология

  1. Конечная точка — описание HTTP-запроса в расширенном формате HAR.
  2. Тег — сущность, описываемая меткой (формат — строка) и атрибутами (формат — объект в нотации JSON). Является результатом работы модуля.
  3. Отчет об уязвимости — тег, атрибуты которого соответствуют JSON-схеме.

Компонент fuchsia-franzisrunner

Компонент fuchsia-franzisrunner — это программный компонент, задачами которого являются запуск модулей сканера Fuchsia и запись тегов в базу данных. Запускаемые модули индифферентны к схеме базы данных, но для корректного взаимодействия обязаны соблюдать протокол общения с fuchsia-franzisrunner, описанный ниже.

Протокол взаимодействия fuchsia-franzisrunner и запускаемого модуля

Входные данные модуля

При запуске модуля компонент fuchsia-franzisrunner передает входные данные модулю посредством их записи в стандартный поток ввода stdin.

Допустимые входные данные:

  1. конечная точка в формате JSON;
  2. тег от любого другого модуля в формате JSON;
  3. 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.

  • Модуль предусматривает работу на всех найденных конечных точках:

    fuchsia-franzisrunner -mode deps -filter none ...
    

    Это эквивалентно примеру ниже благодаря использованию значений по умолчанию:

    fuchsia-franzisrunner ...
    
  • Модуль предусматривает работу только на корневом URL-адресе сканируемого приложения:

    fuchsia-franzisrunner -mode pages -filter root ...
    
  • Модуль предусматривает работу на всех найденных тегах, полученных от другого модуля:

    fuchsia-franzisrunner -mode tags -filter none -label first-scanner ...
    

    Это эквивалентно примеру ниже благодаря использованию значения по умолчанию:

    fuchsia-franzisrunner -mode tags -label first-scanner ...
    
  • Модуль предусматривает работу только на конечных точках, которые имеют тег от другого модуля:

    fuchsia-franzisrunner -mode deps -filter tagged -label first-scanner ...
    
  • Модуль предусматривает работу на всех найденных URL-адресах веб-страниц, с фильтрацией по Content-Type:

    fuchsia-franzisrunner -mode pages -filter none ...
    

    Это эквивалентно примеру ниже благодаря использованию значений по умолчанию:

    fuchsia-franzisrunner -mode pages ...
    

Выходные данные модуля

Для корректной работы модуль должен писать в стандартный поток вывода stdout только результат своей работы. Все записи журнала и прочий вывод должны передаваться в стандартный поток вывода ошибок stderr. Записи журнала, записанные в stderr, сохраняются отдельно. Их можно получить с помощью команды fuchsiactl logs.

Модуль должен записывать результаты своей работы в формате NDJSON (поток JSON-объектов, разделенных переводом строки). Результатом работы модуля может быть:

  • пустой вывод;
  • один JSON-объект;
  • несколько JSON-объектов разделенных переводом строки.

Для дальнейшей идентификации результатов работы модуля используются метки, получаемые с флагом -label.

  • Если все результаты модуля можно воспринимать одинаково, метка задается при помощи флага -scanner.
  • Если один модуль может порождать результаты работы, метки которых отличаются, то необходимо использовать флаг -labelsFromScanner и специальный формат JSON-тега.

Флаг -scanner должен быть задан в обоих случаях.

Примеры

  • Запускаемый модуль пишет теги с одинаковыми метками example-scanner.

    Запуск будет выглядеть следующим образом:

    fuchsia-franzisrunner -scanner example-scanner ...
    

    В стандартный поток вывода сканер может передать следующий набор тегов:

    {"type": "IssueFound", "issue": "...", "url": "https://example.com/path1"}
    {"type": "IssueFound", "issue": "...", "url": "https://example.com/path2"}
    

    В результате работы модуля в базу данных запишется два тега с label=example-scanner.

    • В атрибуты первого тега попадет объект:

      {"type": "IssueFound", "issue": "...", "url": "https://example.com/path1"}
      
    • В атрибуты второго тега попадет объект:

      {"type": "IssueFound", "issue": "...", "url": "https://example.com/path2"}
      
  • Запускаемый модуль пишет теги с разными метками.

    Запуск будет выглядеть следующим образом:

    fuchsia-franzisrunner -scanner example-scanner -labelsFromScanner ...
    

    В стандартный поток вывода сканер может передать следующий набор тегов в специальном формате:

    {"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, атрибутами которого будет являться объект:

      {"type": "IssueFound", "issue": "...", "url": "https://example.com/path1"}
      
    • один тег с label=example-scanner2, атрибутами которого будет являться объект:

      {"type": "IssueFound", "issue": "...", "url": "https://example.com/path2"}
      

В случае, если результат работы модуля должен отображаться в разделе «Уязвимости» в панели управления, тогда в качестве метки тега должно быть задано имя модуля, а в качестве атрибутов тега передаваться JSON объект, соответствующий схеме. Если корректное отображение не обязательно (например, если модуль сохраняет какую-либо промежуточную информацию для следующего модуля) в качестве формата атрибутов тега может быть любой допустимый JSON-объект.

Флаги fuchsia-franzisrunner

Полный список флагов можно получить, выполнив команду fuchsia-franzisrunner -help. В данном разделе будут описаны основные флаги.

  • -mode — выбор формата входных данных для передачи модулю. На текущий момент поддерживаются следующие режимы:

    • deps — в качестве входных данных модулю подаются все найденные конечные точки;
    • tags — в качестве входных данных модулю подаются теги из базы данных, записанные туда другими модулями. При использовании данного режима работы необходимо также указывать флаг -label и в нем указывать метку модуля, теги которого будут использоваться в качестве входных данных. Например, есть модуль, который записал тег вида {"key": "value"} с меткой test-scanner. Если модуль должен работать с данными тегами в качестве входных данных, запуск fuchsia-franzisrunner будет выглядеть следующим образом:

      fuchsia-franzisrunner -mode tags -label test-scanner ...
      
    • 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

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

fuchsiactl logs --scan 100 --modules requester

Параметр args_template

Параметризованная команда запуска сканирующего модуля. В соответствии с протоколом fuchsia-franzisrunner составляется из команды запуска fuchsia-franzisrunner, разделителя «--» и команды запуска сканирующего модуля. Во всех частях этой команды можно использовать шаблонизированные значения {{.ScanId}} и другие.

Полный список параметров, которые можно передать модулю с помощью сканера Fuchsia, указав в конфигурационном файле модуля:

  • {{.Request}} — содержит в себе объект Scan Request, получаемый сканером Fuchsia для создания сканирования. Содержит конфигурацию сканирования;

    Пример использования:

    [
      "fuchsia-franzisrunner",
      "-config",
      "{{.ConfigPath}}",
      "-scanId",
      "{{.ScanId}}",
      "--",
      "crawler",
      "-dirbusting={{if .Request.GetWithDirbuster}}true{{else}}false{{end}}",
      "--url",
      "{{.URL}}",
    ]
    
  • {{.URL}} — содержит URL-адрес, на котором запускалось сканирование;
  • {{.ScanId}} — содержит идентификатор текущего сканирования. Необходим для правильной работы fuchsia-franzisrunner;
  • {{.ConfigPath}} — содержит путь к конфигурационному файлу fuchsiad. Необходим для правильной работы fuchsia-franzisrunner;
  • {{.Files}} — используется для передачи файлов от сканера Fuchsia к модулю сканирования;

    Пример использования:

    [
      "fuchsia-franzisrunner",
      "-scanId",
      "{{.ScanId}}",
      "-config",
      "{{.ConfigPath}}",
      "--",
      "fuchsia-openapi-hars-generator",
      "--mode",
      "module",
      "--spec",
      "{{.Files.OpenAPISpec}}",
    ]
    
  • {{.ExtraSettings}} — используется для передачи произвольных дополнительных параметров модулю сканирования (см. флаг --extra-setting у fuchsiactl);

    Пример использования:

    [
      "fuchsia-franzisrunner",
      "-config",
      "{{.ConfigPath}}",
      "-scanId",
      "{{.ScanId}}",
      "-scanner",
      "wpscan-wrapper",
      "--",
      "/usr/bin/wpscan-wrapper",
      "--api-token",
      "{{.ExtraSettings.api_token}}",
    ]
    

При запуске сканирования дополнительная переменная подаётся на вход следующим образом:

fuchsiactl scan --url https://example.com --extra-setting=wpscan-wrapper:api_token=xxxxxxxxx

Параметр 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 "$@"

Конфигурация для этого модуля может выглядеть следующим образом:

name: page-opener
args_template:
  [
    "fuchsia-franzisrunner",
    "-config",
    "{{.ConfigPath}}",
    "-scanId",
    "{{.ScanId}}",
    "-scanner",
    "requester",
    "-concurrency",
    "4",
    "-mode",
    "pages",
    "--",
    "node",
    "/usr/bin/open-page",
  ]
may_fail: true