
urx v0.11.0
Извлекает URL-адреса из архивов OSINT для аналитики безопасности
Извлекает URL-адреса из OSINT-архивов для анализа безопасности.
Urx — это инструмент командной строки, предназначенный для сбора URL-адресов из OSINT-архивов, таких как Wayback Machine и Common Crawl. Написанный на Rust для обеспечения эффективности, он использует асинхронную обработку для быстрого опроса нескольких источников данных. Этот инструмент упрощает процесс сбора информации об URL-адресах для указанного домена, предоставляя исчерпывающий набор данных, который может быть использован для различных целей, включая тестирование безопасности и анализ.
Возможности
- Параллельное получение URL-адресов из нескольких источников (Wayback Machine, Common Crawl, OTX, Arquivo.pt)
- Подключение любого другого CDX-сервера индексов — национальных веб-архивов, частного pywb, OutbackCDX — с помощью
--cdx-endpoint URL, без изменения кода - По умолчанию без ключей: Wayback, Common Crawl, OTX, Arquivo.pt и URLScan (анонимно) работают без API-ключа
- Провайдер BeVigil: URL-адреса, извлечённые из распакованных Android-приложений — эндпоинты, которые не обходил ни один веб-архив
- Поддержка ротации API-ключей для провайдеров VirusTotal и URLScan для смягчения ограничений скорости
- Аутентифицированное тестирование:
-H,--cookieи--user-agentприменяются к каждому запросу, который urx делает к цели (--check-status,--extract-links,--extract-js-endpoints,--expand-specs), и намеренно никогда не отправляются в архив - Фильтрация результатов по расширениям файлов, подстрокам или полным регулярным выражениям (
--match-regex/--filter-regex) - Предопределённые пресеты, как по семейству файлов ("no-images", "only-js"), так и по интересу безопасности ("only-secrets", "only-backup", "only-config", "only-api")
- Фильтрация на стороне архива: передача кода состояния, MIME-типа и диапазона дат непосредственно в CDX-запрос, чтобы отфильтрованные захваты вообще не пересекали сеть
- Клиентская фильтрация метаданных (
--meta-*): фильтрация по дате первого/последнего захвата, записанному MIME-типу и записанному статусу единообразно для каждого провайдера, после сбора - Цели с ограничением по пути:
urx example.com/shopпередаёт область в сам CDX-запрос (url=example.com/shop*), поэтому поддерево большого сайта стоит лишь часть всего индекса вместо фильтрации на стороне клиента - Файлы области bug-bounty (
--scope-file): собственный список*.example.com/!admin.example.comпрограммы, используемый дословно, повторяемый и объединяемый, при этом исключения всегда побеждают - Нормализация и дедупликация URL-адресов: сортировка параметров запроса, удаление завершающих слешей, объединение семантически идентичных URL-адресов и схлопывание почти-дубликатов, отличающихся только идентификаторами, хешами или датами (
--dedup-similar) - Поддержка нескольких форматов вывода: обычный текст, JSON, JSON Lines, CSV и
wordlist— сегменты пути и имена параметров, из которых построена цель, без идентификаторов, хешей и дат - Представления параметров и фаззинга:
--params(полный перечень параметров цели),--params-by-endpoint(какой эндпоинт принимает что) и--fuzz-placeholder FUZZ(один шаблонный URL на каждую сигнатуру параметра, готовый для ffuf или dalfox) - Метаданные захвата архива:
first_seen,last_seen,mime,archive_statusиdigestвозвращаются с каждым URL-адресом, о котором сообщил CDX-архив, без дополнительных сетевых затрат - Потоковый вывод (
--stream): URL-адреса записываются по мере их сообщения каждым провайдером, поэтому конвейер начинает работать немедленно, вместо ожидания самого медленного архива - Поддержка прямого ввода файлов: чтение URL-адресов непосредственно из WARC-файлов, сжатых файлов URLTeam и текстовых файлов
- Вывод результатов в консоль или файл, либо потоковая передача через stdin для интеграции в конвейер
- Тестирование URL-адресов:
- Фильтрация и проверка URL-адресов на основе кодов состояния HTTP и шаблонов.
- Извлечение дополнительных ссылок из собранных URL-адресов — якорей, скриптов, таблиц стилей, действий форм, iframe, изображений, медиа-источников, объектов, встраиваний и целей meta-refresh
- Извлечение из архивных тел ответов собранных URL-адресов (
--archive-body), чтобы страницы, которых больше не существует, всё ещё отдавали содержавшиеся в них ссылки — один запрос на каждое отдельное тело, благодаря дедупликации по CDX-дайджесту - С
--extract-js-endpointsизвлекайте также из архивного JavaScript: бандл, названный по хешу сборки, отдаёт 404 в момент повторного развёртывания сайта, и архив — единственное место, где его API-поверхность всё ещё существует - Сохраняйте воспроизведённые тела (
--archive-body-dir) как корпус для поиска того, что не ищет ни один извлекатель ссылок — комментариев разработчиков, встроенных учётных данных, внутренних имён хостов — без дополнительных запросов - Разворачивание спецификаций API (
--expand-specs): документы OpenAPI 3.x, Swagger 2.0 и GraphQL introspection, в формате JSON или YAML, превращаются в каждый описанный ими маршрут — один запрос даёт всю документированную поверхность - Метаданные ответа:
--check-statusтакже записываетLocation,Content-LengthиContent-Type, а--check-titleдобавляет HTML<title>
- Обнаружение архивных robots.txt и sitemap.xml (
--archived-discovery): каждая отдельная версия, которую хранит Wayback Machine, поэтомуDisallow:от 2015 года всё ещё называет пути, о которых сайт с тех пор перестал упоминать - Кэширование и инкрементальное сканирование:
- Локальное кэширование в SQLite или удалённое в Redis для избежания повторного сканирования доменов
- Инкрементальный режим для обнаружения только новых URL-адресов с момента последнего сканирования
- Настраиваемый TTL кэша и автоматическая очистка устаревших записей
- Подкоманда
urx cacheдля просмотра и обслуживания кэша:stats,list,prune,drop <domain>,clear

Установка
Из Cargo```bash
https://crates.io/crates/urx
cargo install urx
### Из Homebrew```bash
# https://formulae.brew.sh/formula/urx
brew install urx
Из исходного кода```bash
git clone https://github.com/hahwul/urx.git cd urx cargo build --release
Скомпилированный бинарный файл будет доступен по пути `target/release/urx`.
### Из Docker
[ghcr.io/hahwul/urx](https://github.com/hahwul/urx/pkgs/container/urx)
### Автодополнение для оболочки
`urx` генерирует собственный скрипт автодополнения, поэтому он всегда соответствует флагам
того бинарного файла, который у вас фактически установлен.```bash
# zsh — any directory on your $fpath works
urx --completions zsh > ~/.zfunc/_urx
# (make sure ~/.zfunc is on the fpath, then `compinit`)
# bash
urx --completions bash > ~/.local/share/bash-completion/completions/urx
# fish
urx --completions fish > ~/.config/fish/completions/urx.fish
powershell и elvish также поддерживаются. Флагу не требуется целевой домен.
Страница руководства```bash
urx --manpage > ~/.local/share/man/man1/urx.1 man urx
## Использование
### Базовое использование```bash
# Scan a single domain
urx example.com
# Scan multiple domains
urx example.com example.org
# Scan domains from a file
cat domains.txt | urx
Опции```
Usage: urx [OPTIONS] [DOMAINS]... [COMMAND]
Commands: cache Inspect and maintain the URL cache: stats, list, prune, drop ..., clear
Arguments: [DOMAINS]... Domains to fetch URLs for
Options: -c, --config Config file to load --provider-config Separate provider config file holding only API keys (default: $XDG_CONFIG_HOME/urx/provider-config.toml). CLI/env > provider-config > main config. --completions Print a shell completion script (bash, zsh, fish, powershell, elvish) to stdout and exit --manpage Print the roff man page to stdout and exit -h, --help Print help -V, --version Print version
Input Options:
--files ... Read URLs directly from files (supports WARC, URLTeam compressed, and text files)
--domain-list File of newline-separated domains to scan (repeatable; merged with positional DOMAINS and stdin; # comments allowed)
Output Options:
-o, --output Output file to write results
--output-dir Write one file per domain into this directory (extension matches --format). Coexists with --output / stdout.
-f, --format Output format: "plain", "json" (one array), "jsonl" (one JSON object per line), "csv", "wordlist" (path segments and parameter names, deduplicated and sorted) [default: plain]
--stream Write URLs as each provider reports them instead of once at the end (unsorted; bypasses cache; rejects options needing the full result set)
--merge-endpoint Merge endpoints with the same path and merge URL parameters
--normalize-url Normalize URLs for better deduplication (sorts query parameters, removes trailing slashes)
--dedup-similar Collapse URLs that differ only in variable data (numeric ids, UUIDs, hashes, dates, query values)
--params Replace the URL list with every query parameter name the run saw, once each
--params-by-endpoint
One line per endpoint: the endpoint and the comma-separated union of the parameter names seen on it (id-looking path segments collapse to {id})
--fuzz-placeholder
Replace every query parameter value with VALUE, keeping one URL per parameter signature — output you can feed straight to ffuf or dalfox
Provider Options:
--providers
Providers to use (comma-separated, e.g., "wayback,cc,otx,arquivo,vt,urlscan") [default: wayback,cc,otx]
--exclude-providers <EXCLUDE_PROVIDERS>
Providers to exclude (comma-separated). Wins on conflict with --providers / --all-providers.
--all-providers
Enable every supported provider. API-keyed providers only activate when a key is available.
--list-providers
List every supported provider then exit.
--subs
Include subdomains when searching
--cc-index <CC_INDEX>
Common Crawl index to use; accepts comma-separated list to query multiple indexes in parallel (e.g. CC-MAIN-2026-17,CC-MAIN-2025-51). latest (the default) resolves the newest via collinfo.json. [default: latest]
--cdx-endpoint
Query an additional CDX index server (any pywb, OutbackCDX, or classic Internet-Archive-style CDX API) by its full API URL, e.g. https://vefsafn.is/cdx. Repeatable. Each endpoint becomes a provider with id cdx:<host> and honours --subs, --from/--to and the --archive-* filters. See "Custom CDX Endpoints" below
--cdx-dialect
Which CDX dialect the --cdx-endpoint servers speak: pywb or classic. Unset: urx probes each endpoint once and falls back to pywb when the answer is ambiguous
--from
Restrict every CDX-backed provider (wayback, cc, arquivo, --cdx-endpoint) to captures at or after DATE (YYYY/YYYYMM/YYYYMMDD/YYYYMMDDhhmmss). Alias: --wayback-from
--to
Restrict every CDX-backed provider to captures at or before DATE (same format as --from). Alias: --wayback-to
--archive-status
Keep only captures the archive recorded with this HTTP status code (e.g. "200"). Applied by the CDX index itself, so unlike --include-status it costs no extra requests. A multi-value list works on wayback only — see "Archive-side Filtering" below
--archive-exclude-status
Drop captures the archive recorded with these HTTP status codes (comma-separated, e.g. "404,500"). Multi-value works on every CDX provider
--archive-mime
Keep only captures with this recorded MIME type (e.g. "application/json"). Catches endpoints with no file extension, which -e/--extensions cannot
--archive-exclude-mime
Drop captures with these recorded MIME types (comma-separated, e.g. "text/html,image/png")
--vt-api-key <VT_API_KEY>
API key for VirusTotal (can be used multiple times for rotation, can also use URX_VT_API_KEY environment variable with comma-separated keys)
--urlscan-api-key <URLSCAN_API_KEY>
Optional API key for Urlscan; the provider also works anonymously (rate-limited ~30 req/min per IP). Can be used multiple times for rotation, or via URX_URLSCAN_API_KEY (comma-separated keys)
--github-api-key <GITHUB_API_KEY>
Personal access token for the GitHub Code Search provider (also reads URX_GITHUB_API_KEY, comma-separated for rotation)
--bevigil-api-key <BEVIGIL_API_KEY>
API key for BeVigil, which returns URLs extracted from unpacked Android apps (also reads URX_BEVIGIL_API_KEY, comma-separated for rotation). Required for the bevigil provider
Discovery Options:
--exclude-robots
Exclude robots.txt discovery
--exclude-sitemap
Exclude sitemap.xml discovery
--archived-discovery
Also read every distinct archived version of robots.txt and sitemap.xml the Wayback Machine holds
--archived-discovery-limit
Maximum archived documents fetched per domain by each archived provider (nested sitemaps count) [default: 50]
Display Options:
-v, --verbose Show verbose output
--silent Silent mode (no output)
--no-progress No progress bar
--no-color Disable ANSI color in the progress UI and output (NO_COLOR is also honored)
--show-sources Annotate output URLs with the providers that returned them
--show-meta Annotate plain-text URLs with the archive capture metadata
--stats Print a per-provider summary to stderr at end of run
Filter Options:
-p, --preset
Filter Presets (e.g., "no-resources,no-images,no-audio,only-js,only-style,only-secrets,only-backup,only-config,only-api")
-e, --extensions
Filter URLs to only include those with specific extensions (comma-separated, e.g., "js,php,aspx")
--exclude-extensions <EXCLUDE_EXTENSIONS>
Filter URLs to exclude those with specific extensions (comma-separated, e.g., "html,txt")
--patterns
Filter URLs to only include those containing specific patterns (comma-separated)
--exclude-patterns <EXCLUDE_PATTERNS>
Filter URLs to exclude those containing specific patterns (comma-separated)
--match-regex
Keep only URLs matching this regular expression (repeatable, ORed; case-sensitive; never comma-split)
--filter-regex
Drop URLs matching this regular expression (repeatable; one match is enough)
--show-only-host
Only show the host part of the URLs
--show-only-path
Only show the path part of the URLs
--show-only-param
Only show the parameters part of the URLs
--min-length <MIN_LENGTH>
Minimum URL length to include
--max-length <MAX_LENGTH>
Maximum URL length to include
--strict
Enforce exact host validation (default)
--no-strict
Disable host validation (keep URLs on any host a provider returns). Wins over --strict. A target's path scope still applies: only the host check is waived
--scope-file
Bug-bounty scope file: one host pattern per line, ! to exclude, *.example.com for a wildcard (which covers the apex too), # for a comment. Repeatable and unioned; exclusions always win. See "Scope Files" below
--meta-first-seen-after
Keep URLs whose oldest archived capture is on or after DATE (YYYY/YYYYMM/YYYYMMDD/YYYYMMDDhhmmss)
--meta-first-seen-before
Keep URLs whose oldest archived capture is on or before DATE
--meta-last-seen-after
Keep URLs whose newest archived capture is on or after DATE — "still alive as of"
--meta-last-seen-before
Keep URLs whose newest archived capture is on or before DATE — "dead since"
--meta-mime
Keep only URLs whose archived MIME type is one of these (comma-separated; image/* matches any subtype)
--meta-exclude-mime
Drop URLs whose archived MIME type is one of these
--meta-status
Keep only URLs whose archived status code matches (comma-separated; 20x / 5xx patterns)
--meta-exclude-status
Drop URLs whose archived status code matches
Network Options:
--network-scope <NETWORK_SCOPE> Control which components network settings apply to (all, providers, testers, or providers,testers) [default: all]
--proxy Use proxy for HTTP requests (format: http://proxy.example.com:8080)
--proxy-auth <PROXY_AUTH> Proxy authentication credentials (format: username:password)
--insecure Skip SSL certificate verification (accept self-signed certs)
--random-agent Use a random User-Agent for HTTP requests
-H, --header <NAME: VALUE> Extra request header, repeatable; sent only on requests urx makes to the target, never to an archive
--cookie Cookie header for requests to the target; shorthand for -H "Cookie: ..."
--user-agent User-Agent for requests to the target, overriding the default and --random-agent
--timeout Request timeout in seconds [default: 120]
--retries Number of retries for failed requests [default: 2]
--parallel Maximum domains fetched concurrently per provider (and concurrent URL tests); a provider's --rate-limit is shared across them [default: 5]
--rate-limit <RATE_LIMIT> Rate limit (requests per second)
--rate-limit-by Per-provider rate overrides (e.g. vt=1,wayback=10); falls back to --rate-limit for unlisted providers
--max-time <MAX_TIME> Global ceiling on provider enumeration time in seconds (0 = unlimited) [default: 0]
Testing Options:
--check-status
Check HTTP status code of collected URLs [aliases: ----cs]
--check-title
Also record each response's HTML while checking statuses; implies --check-status
--include-status <INCLUDE_STATUS>
Include URLs with specific HTTP status codes or patterns (e.g., --is=200,30x) [aliases: ----is]
--exclude-status <EXCLUDE_STATUS>
Exclude URLs with specific HTTP status codes or patterns (e.g., --es=404,50x,5xx) [aliases: ----es]
--extract-links
Extract additional links from collected URLs (requires HTTP requests)
--extract-js-endpoints
Fetch collected JavaScript files and extract the endpoint paths and URLs found in their string literals (requires HTTP requests); with --archive-body this also mines the archived copy of each script
--max-js-files
Maximum number of files --extract-js-endpoints will fetch (0 = unlimited) [default: 500]
--archive-body
Fetch the archived body of each collected URL from the Wayback Machine and extract the links inside it (works for pages that no longer exist)
--archive-body-limit
Maximum number of archived bodies --archive-body fetches per run; bounds distinct bodies, not URLs [default: 500]
--archive-body-dir
Keep every body --archive-body replays in DIR, with an index.jsonl mapping each file back to its URL, capture and content type
--expand-specs
Fetch the API specification documents among the collected URLs (OpenAPI, Swagger, GraphQL introspection; JSON or YAML) and expand every route they document into a URL. See "Expanding API Specifications" below
--max-spec-files
Maximum number of specification documents --expand-specs will fetch (0 = unlimited) [default: 50]
Cache Options:
--incremental Enable incremental scanning mode (only return new URLs compared to previous scans)
--cache-type Cache backend: sqlite or redis [default: sqlite]
--cache-path Path for the SQLite cache database
--redis-url Redis connection URL for remote caching
--cache-ttl Cache time-to-live in seconds [default: 86400]
--no-cache Disable caching entirely
Notification Options:
--notify POST a run summary to this webhook when the run ends (repeatable; also URX_NOTIFY_URL, provider-config notify_url, or [notify].url)
--notify-on <NOTIFY_ON> When to send: new (only if URLs were emitted), always, or never [default: new]
--notify-format <NOTIFY_FORMAT> Payload shape: json (urx summary), slack ({"text"}), or discord ({"content"}) [default: json]
`--extract-links` читает каждый тег, содержащий URL, а не только якоря: `<a href>`,
`<script src>`, `<link href>`, `<form action>`, ``, ``,
`<source src>`, `<object data>`, `<embed src>` и цели
`<meta http-equiv="refresh">`. Относительные URL разрешаются относительно страницы (с учётом `<base href>`),
дубликаты схлопываются, а обнаруженные ссылки проходят через те же фильтры
и проверку хоста, что и остальная часть запуска. Полную таблицу см. в
[docs/content/guide/cli-options.md](https://github.com/hahwul/urx/blob/main/docs/content/guide/cli-options.md).
`--extract-js-endpoints` идёт на шаг дальше и читает сам JavaScript:
каждый собранный URL, похожий на скрипт, загружается, и его
строковые литералы анализируются на предмет путей и URL, которые вызывает приложение —
`fetch("/api/v2/users")`, `axios.post("/graphql")`, статический префикс
`` `/api/orders/${id}` ``. Это эндпоинты, которые никогда не появляются в HTML.
Вывод агрессивно очищается от шума (MIME-типы, спецификаторы модулей, base64,
значения CSS, фрагменты регулярных выражений и многое другое отбрасываются), каждое тело ограничено
10 MiB, количество загружаемых файлов ограничено `--max-js-files`, а
обнаруженные эндпоинты проходят те же фильтры и проверку хоста, что и всё
остальное. Полная политика извлечения и подавления шума приведена в
[docs/content/guide/cli-options.md](https://github.com/hahwul/urx/blob/main/docs/content/guide/cli-options.md#javascript-endpoint-extraction).
`--archive-body` выполняет ту же извлекающую обработку над телами, которые Wayback Machine
*сохранила*, а не над живым сайтом, поэтому страница, удалённая годы назад,
всё ещё выдаёт содержавшиеся в ней ссылки. См.
[Mining Archived Response Bodies](#mining-archived-response-bodies).
### Примеры```bash
# Save results to a file
urx example.com -o results.txt
# Output in JSON format
urx example.com -f json -o results.json
# Filter for JavaScript files only
urx example.com -e js
# Exclude HTML and text files
urx example.com --exclude-extensions html,txt
# Filter for API endpoints
urx example.com --patterns api,v1,graphql
# Exclude specific patterns
urx example.com --exclude-patterns static,images
# Use Fileter Preset (similar to --exclude-extensions=png,jpg,.....)
urx example.com -p no-images
# Use specific providers
urx example.com --providers wayback,otx
# Add the keyless Arquivo.pt (Portuguese web archive) provider
urx example.com --providers wayback,cc,otx,arquivo
# Query another CDX index server alongside the defaults (id: cdx:vefsafn.is)
urx example.is --cdx-endpoint https://vefsafn.is/cdx
# ...or on its own, rate-limited, with the archive-side filters it shares with wayback/cc
urx example.is --cdx-endpoint https://vefsafn.is/cdx --providers cdx:vefsafn.is \
--rate-limit-by cdx:vefsafn.is=1 --from 2020 --archive-status 200
# URLScan works without a key (anonymous, rate-limited); a key just raises limits
urx example.com --providers urlscan
# BeVigil: endpoints pulled out of unpacked Android apps (key required; auto-enables the provider)
URX_BEVIGIL_API_KEY=*** urx example.com
# Using VirusTotal and URLScan providers
# 1. Explicitly add to providers (with API keys via command line)
urx example.com --providers=vt,urlscan --vt-api-key=*** --urlscan-api-key=***
# 2. Using environment variables for API keys
URX_VT_API_KEY=*** URX_URLSCAN_API_KEY=*** urx example.com --providers=vt,urlscan
# 3. Auto-enabling: providers are automatically added when API keys are provided
urx example.com --vt-api-key=*** --urlscan-api-key=*** # No need to specify in --providers
# 4. Multiple API key rotation (to mitigate rate limits)
# Using repeated flags for multiple keys
urx example.com --vt-api-key=key1 --vt-api-key=key2 --vt-api-key=key3
# Using environment variables with comma-separated keys
URX_VT_API_KEY=key1,key2,key3 URX_URLSCAN_API_KEY=ukey1,ukey2 urx example.com
# Combining CLI flags and environment variables (CLI keys are used first)
URX_VT_API_KEY=env_key1,env_key2 urx example.com --vt-api-key=cli_key1 --vt-api-key=cli_key2
# URLs from robots.txt and sitemap.xml are included by default
# Exclude URLs from robots.txt files
urx example.com --exclude-robots
# Exclude URLs from sitemap
urx example.com --exclude-sitemap
# Also read every archived version of robots.txt and sitemap.xml, so paths the
# site once listed and has since removed come back
urx example.com --archived-discovery
# Only the versions captured in a given era
urx example.com --archived-discovery --from 2014 --to 2016 --exclude-sitemap
# Include subdomains
urx example.com --subs
# Check status of collected URLs
urx example.com --check-status
# Read URLs directly from a text file
urx --files urls.txt
# Combine file input with filtering
urx --files urls.txt --patterns api,admin -f json
# Extract additional links from collected URLs
# (anchors, scripts, stylesheets, form actions, iframes, images, media
# sources, objects, embeds, and meta-refresh targets)
urx example.com --extract-links
# Discovered links go through the same filters as everything else, so this
# keeps only the JavaScript the pages reference
urx example.com --extract-links -e js
# Read the collected JavaScript and pull out the API paths it calls
urx example.com --extract-js-endpoints --patterns api
# Chain them: collect the site's bundles, then mine those for endpoints
urx example.com --extract-links --extract-js-endpoints --max-js-files 100
# Mine the links inside the *archived* bodies instead — dead pages included.
# One request per distinct body; the limit bounds bodies, not URLs
urx example.com --archive-body --archive-body-limit 200 --rate-limit 5
# Network configuration
urx example.com --proxy http://localhost:8080 --timeout 60 --parallel 10 --insecure
# Advanced filtering
urx example.com -e js,php --patterns admin,login --exclude-patterns logout,static --min-length 20
# HTTP Status code based filtering (live requests: urx re-fetches each URL)
urx example.com --include-status 200,30x,405 --exclude-status 20x
# Archive-side filtering (free: the CDX index already knows these)
# Skip everything the archive recorded as a 404 — no extra requests
urx example.com --archive-exclude-status 404
# Only captures the archive served as JSON — finds extensionless API endpoints
urx example.com --archive-mime application/json
# Drop HTML to leave assets and endpoints behind
urx example.com --archive-exclude-mime text/html
# Restrict the crawl window across wayback, cc, arquivo, and any --cdx-endpoint alike
urx example.com --from 2023 --to 2024
# Disable host validation
urx example.com --strict false
# URL normalization and deduplication
# Normalize URLs by sorting query parameters and removing trailing slashes
urx example.com --normalize-url
# Combine normalization with endpoint merging for comprehensive deduplication
urx example.com --normalize-url --merge-endpoint
# URL normalization with file input
urx --files urls.txt --normalize-url
# Collapse /post/1, /post/2, /post/99999 ... into a single representative line
urx example.com --dedup-similar
# Regular-expression filtering (repeat either flag; they are never comma-split)
urx example.com --match-regex '/api/v[0-9]+/'
urx example.com --match-regex '\.php$' --match-regex '\.aspx$'
urx example.com --filter-regex '/(assets|static)/'
# Regexes are case-sensitive; ask for insensitivity explicitly
urx example.com --match-regex '(?i)admin'
# Security presets: match by path shape as well as by extension
urx example.com -p only-secrets # /.env, /.git/config, id_rsa, *.pem
urx example.com -p only-backup # *.bak, *.sql, /backup/, index.php~
urx example.com -p only-config # *.yaml, web.config, .htaccess, Dockerfile
urx example.com -p only-api # /api/, /v1/, /graphql, /swagger, *.wsdl
# Scope files: a bug bounty program's own host list, used verbatim
urx example.com --subs --scope-file scope.txt
# Metadata filters, applied after collection so every provider is covered
urx example.com --providers wayback --meta-last-seen-after 2024 --meta-exclude-mime 'image/*'
urx example.com --providers wayback --meta-mime application/json --meta-status 200
# What parameters does this target take, and where?
urx example.com --params
urx example.com --params-by-endpoint
# One templated URL per parameter signature, straight into a fuzzer
urx example.com --fuzz-placeholder FUZZ | ffuf -w - -u FUZZ
# A target-specific wordlist instead of a URL list
urx example.com --subs -f wordlist -o words.txt
# Open the API specifications the sweep found and expand every route in them
urx example.com -p only-api --expand-specs
# Status checks also keep the response head; --check-title adds the <title>
urx example.com --check-status -f jsonl
urx example.com --check-title --show-meta
# Inspect and maintain the cache
urx cache stats
urx cache drop example.com
Ограничение области запуска путём
Цель может указывать путь, и это означает именно то, что написано: urx example.com/shop
собирает часть сайта в разделе /shop.```bash
urx example.com/shop
urx https://example.com/api/v2 # a pasted URL works too
Это не фильтр, применяемый постфактум. Индекс CDX обрабатывает префиксные запросы
нативно, поэтому urx отправляет `url=example.com/shop*`, и архив никогда не передаёт
остальную часть сайта по сети — на крупной цели это разница
между несколькими сотнями строк и несколькими сотнями тысяч. Провайдеры, которые не могут
выразить путь в своём запросе (OTX, VirusTotal, urlscan, GitHub, BeVigil,
ZoomEye), опрашиваются по хосту, а их ответы сужаются afterwards, как
и результаты запуска `--subs`, где форма `*.host` и префикс пути
не могут быть объединены в одном запросе CDX.
Область означает *на или под* путём: `/shop` и `/shop/cart` входят, `/shopping`
нет. Регистр игнорируется, потому что сервер CDX приводит весь URL к нижнему регистру, когда
строит свой индексный ключ — `example.com/Shop*` и `example.com/shop*` возвращают
одни и те же строки, все в нижнем регистре, поэтому проверка с учётом регистра выбросила бы
всё, что только что вернул архив. Строка запроса или фрагмент в
цели отбрасываются — они сужают запрос, а не область.
> Примечание: раньше urx отбрасывал путь из цели, поэтому
> `urx https://example.com/shop` сканировал весь `example.com`. Теперь он
> сканирует `/shop`. Передайте только хост для старого поведения; запуск, цель которого
> содержит путь, сообщает об этом в stderr.
### Фильтрация по регулярным выражениям
`--patterns` / `--exclude-patterns` — это простые проверки на подстроку: обе стороны
приводятся к нижнему регистру, и каждый метасимвол является литералом. `--match-regex` /
`--filter-regex` — это аналоги для регулярных выражений, и они отличаются тремя способами, которые стоит
запомнить:
| | `--patterns` | `--match-regex` |
|---|---|---|
| Сопоставление | подстрока | полный [синтаксис regex](https://docs.rs/regex/latest/regex/#syntax) |
| Регистр | нечувствителен (обе стороны в нижнем регистре) | **чувствителен** — используйте `(?i)`, чтобы отключить |
| Несколько значений | один флаг через запятую | повторяйте флаг; запятые никогда не разделяются |
Оба флага regex вычисляются по **всей строке URL** в том виде, в каком она собрана
(схема, хост, путь и запрос), поэтому работают и `^https://`, и `\.js$`.
Исключение имеет приоритет: URL, соответствующий `--filter-regex`, отбрасывается, даже если
`--match-regex` также совпал с ним. Некорректное выражение приводит к сбою запуска при
старте, до обращения к какому-либо архиву.
### Файлы области
Область программы bug bounty — это список хостов, и каждая платформа записывает его
одинаково. `--scope-file` принимает этот список дословно, вместо того чтобы заставлять вас
вручную переводить его в анкорированные чередования regex — где ошибка в
анкорировании молча *расширяет* область, а не приводит к сбою.```text
# scope.txt — in scope
*.example.com
api.example.org
# out of scope, even though the wildcard above covers them
!admin.example.com
!*.internal.example.com
--no-verify — пропустить проверку TLS-сертификата (не рекомендуется).
--timeout — таймаут запроса в секундах (по умолчанию: 10).
--user-agent — переопределить заголовок User-Agent.
--proxy — направлять запросы через прокси (например, http://127.0.0.1:8080).
--header — добавить пользовательский заголовок (можно указывать несколько раз).
--cookie — отправить cookies вместе с запросом.
--data — отправить данные в теле запроса (для методов POST/PUT).
--method — явно указать HTTP-метод.
--follow-redirects — следовать перенаправлениям HTTP.
--max-redirects — ограничить количество перенаправлений (по умолчанию: 5).
--output — сохранить тело ответа в файл.
--verbose — включить подробный вывод.
--silent — подавить весь вывод, кроме тела ответа.
--json — выводить ответ в формате JSON.
--raw — выводить необработанный ответ без форматирования.
--status — выводить только код состояния HTTP.
--headers — выводить только заголовки ответа.
--body — выводить только тело ответа.
--version — показать информацию о версии и выйти.
--help — показать справочное сообщение и выйти.```bash
urx example.com --subs --scope-file scope.txt
urx --domain-list targets.txt --subs --scope-file scope-a.txt --scope-file scope-b.txt
`*.example.com` соответствует как apex-домену, так и всему под ним (трактовка bug-bounty, что и означает таблица scope платформы); голое имя хоста соответствует ровно этому хосту; одинокий `*` превращает файл в чистый deny-list; исключения всегда побеждают; `#` начинает комментарий. Всё, что urx не может обработать — порт, путь, wildcard в середине — это ошибка при запуске с указанием файла и строки, а не молчаливо расширенный scope. Фильтр применяется ко всем провайдерам и к извлечённым ссылкам, и он сочетается с `--strict`, а не заменяет его, поэтому строка scope `*.example.com` всё ещё требует `--subs`.
### Фильтры метаданных архива
`--from`/`--to` и предикаты `--archive-*` передаются в собственный запрос архива, что делает их бесплатными, а также ограничивает их провайдерами на основе CDX — и два диалекта CDX расходятся настолько сильно, что положительный многозначный список (`--archive-status 200,301`) невыполним на серверах pywb. Восемь фильтров `--meta-*` выполняются *после* сбора, по одному объединённому набору метаданных захвата на URL, поэтому они применяются ко всем провайдерам единообразно.```bash
# Endpoints still being captured recently, with HTML and images out of the way
urx example.com --providers wayback --meta-last-seen-after 2024 --meta-exclude-mime 'text/html,image/*'
# Pages that died: nothing captured since 2019
urx example.com --providers wayback --meta-last-seen-before 2019
# JSON the archive served successfully
urx example.com --providers wayback --meta-mime application/json --meta-status 200
# First archived during 2020 (partial dates pad to the start / end of the period)
urx example.com --providers wayback --meta-first-seen-after 2020 --meta-first-seen-before 2020
URL-адреса без метаданных — от провайдеров без CDX, из входных данных --files, из кэша — разделяются по направлению предиката: положительный предикат не может быть удовлетворён отсутствующим значением, поэтому URL отбрасывается; исключение отбрасывает только то, что положительно совпадает, поэтому URL сохраняется. --verbose сообщает о разделении, а когда отсутствующие метаданные объясняют весь набор результатов, urx сообщает об этом даже без -v, потому что иначе попадание в кэш делает пустой запуск похожим на цель, в которой нечего искать.
Схлопывание почти-дубликатов
Архив с радостью вернёт /post/1 вплоть до /post/99999. Это одна конечная точка, и --dedup-similar выводит для них одну строку. Сегмент пути рассматривается как данные — а не как часть маршрута — когда он целиком является одним из:
- последовательностью цифр (
/post/1, /page/42)
- UUID (
/u/550e8400-e29b-41d4-a716-446655440000)
- 32/40/64-символьным hex-дайджестом (md5, sha1, sha256)
- датой с разделителями (
/blog/2024-01-02/)
- длинным токеном со смешанным регистром, содержащим цифры (идентификаторы сессий, подписанные блобы)
Сегменты, которые лишь содержат цифры, остаются на месте, поэтому /api/v1/ и /api/v2/ — по-прежнему две конечные точки, а slug в нижнем регистре — это проза, а не токен. Строки запроса группируются только по именам параметров: ?q=cats&page=1 и ?q=dogs&page=7 схлопываются, тогда как ?q=cats в одиночку — нет, поскольку удаление параметра меняет запрос.
Выживший в каждой группе — это лексикографически наименьший URL, поэтому два запуска по одним и тем же данным выводят одно и то же. --verbose сообщает, сколько URL было схлопнуто. Эта опция не зависит от --normalize-url и --merge-endpoint и сочетается с любой из них; всем трём нужен полный набор результатов, поэтому ни одна из них не работает с --stream.
Представления параметров и фаззинга
--show-only-param лишь отрезает строку запроса от каждого URL, что не может ответить на первый вопрос, который задаёт тестировщик: какие параметры принимает эта цель? На него отвечают три представления, построенные на той же группировке, которую использует --dedup-similar.```console
$ urx example.com --params
page
q
ref
sort
utm_source
$ urx example.com --params-by-endpoint
https://example.com/post/{id} ref,utm_source
https://example.com/search page,q,sort
$ urx example.com --fuzz-placeholder FUZZ
https://example.com/post/1?ref=FUZZ
https://example.com/post/2?utm_source=FUZZ
https://example.com/search?q=FUZZ&page=FUZZ
https://example.com/search?q=FUZZ&sort=FUZZ
`--params-by-endpoint` сворачивает сегменты пути, похожие на id, в `{id}` точно так же, как это делает `--dedup-similar`, и выводит endpoint полностью, поскольку urx обычно сканирует несколько хостов за один запуск. `--fuzz-placeholder` сохраняет один URL на сигнатуру параметра и оставляет его реальный путь — `{id}` не привёл бы к маршруту — поэтому вывод можно напрямую подавать в фаззер:```bash
urx example.com --fuzz-placeholder FUZZ | ffuf -w - -u FUZZ
urx example.com --fuzz-placeholder FUZZ | dalfox pipe
Все три требуют полного набора результатов, поэтому они работают только пакетно и взаимно исключают друг друга, а также исключают представления --show-only-*.
Вывод списка слов
-f wordlist превращает запуск в целевой список слов: каждый сегмент пути
и имя параметра запроса, которые он встретил, дедуплицированные по всему запуску и отсортированные,
по одному термину на строку.```bash
urx example.com --subs -f wordlist -o words.txt
ffuf -w words.txt -u https://example.com/FUZZ
Сегменты, которые выглядят как данные, а не как имена маршрутов, опускаются, повторно используя
группы теста `--dedup-similar` — список слов, полный `4711`, UUID, дат и
сессионных токенов, хуже, чем отсутствие списка слов, поскольку каждое из этих слов существует
ровно на одной цели. Сегмент, чья основа является идентификатором, также опускается
(`article-1234.html`). Регистр сохраняется: сегменты пути чувствительны к регистру на
большинстве источников, поэтому приведение `WebResource.axd` к нижнему регистру создало бы слово, которое выдаёт 404
везде, где бы оно ни использовалось. Объединение должно быть взято по полному набору, поэтому
формат только пакетный.
### Потоковый вывод
По умолчанию urx собирает всё, затем фильтрует, сортирует и выводит один раз. На
большой цели это означает отсутствие вывода до тех пор, пока не завершится самый медленный архив.
`--stream` записывает каждый URL в момент, когда провайдер, сообщивший о нём, возвращается:```bash
# Matches start appearing immediately instead of after the slowest provider
urx big-target.com --stream | grep admin
# Line-delimited JSON stays valid while it is still being written
urx big-target.com --stream -f jsonl | jq -r 'select(.url | test("/api/")) | .url'
Потоковые URL проходят ровно те же фильтры, что и пакетный запуск, и всё так же дедуплицируются. Отличаются две вещи:
- Порядок. Результаты приходят в порядке завершения провайдеров, поэтому вывод не отсортирован. Пропустите через
sort, если нужен порядок.
- Область применения. Опции, которым нужен полный набор результатов, отклоняются сразу (с сообщением, называющим каждую из них):
--merge-endpoint, --dedup-similar, --check-status /
--include-status / --exclude-status, --extract-links,
--extract-js-endpoints, --archive-body, --expand-specs,
--incremental, --show-sources, --show-meta, фильтры --meta-*,
--params, --params-by-endpoint, --fuzz-placeholder, --output-dir и
--files. Кэширование обходится;
--format json отклоняется в пользу jsonl, потому что массив JSON должен
знать, какая запись последняя, а --format wordlist — потому что ни один
термин не может считаться новым, пока не пришли все URL.
Поскольку карта результатов пакетного режима в этом режиме никогда не заполняется, потоковый запуск также занимает гораздо меньше памяти — только набор дедупликации уже записанных URL.
Метаданные захвата архива
Индекс CDX записывает больше, чем URL: каждый захват несёт метку времени, MIME-тип и HTTP-статус, которые увидел архив, и дайджест тела. urx сохраняет всё это, поэтому провайдеры на основе CDX — wayback, cc, arquivo и любой --cdx-endpoint — сообщают каждый URL вместе с:
Поле Значение first_seenМетка времени самого старого захвата, 14-значная форма CDX (YYYYMMDDhhmmss) last_seenМетка времени самого нового захвата mimeMIME-тип самого последнего захвата, в котором он был записан archive_statusHTTP-статус, который архив записал во время захвата digestРепрезентативный дайджест содержимого по всем захватам
archive_status — это не status: status появляется только при --check-status, который заново запрашивает URL в реальном времени, тогда как archive_status — это то, что получил краулер при захвате страницы. URL вполне может иметь archive_status 200 и быть мёртвым сегодня.
Когда один и тот же URL приходит из нескольких захватов или нескольких архивов, значения объединяются: first_seen — самая старая метка времени из всех сообщённых, last_seen — самая новая, а mime/archive_status берутся из самого последнего захвата, в котором они были. Провайдеры без индекса захватов (otx, vt, urlscan, zoomeye, github, bevigil, robots, sitemap и вход --files) сообщают только URL — никакие значения для них не выдумываются.
То, как метаданные отображаются, зависит от формата:
json / jsonl — каждое поле появляется как ключ, когда у него есть значение, и полностью опускается, когда его нет, точно так же, как sources.
csv — столбец добавляется только тогда, когда хотя бы одна строка имеет для него значение, поэтому запуск без метаданных всё равно даёт единственный столбец url.
- простой текст — по умолчанию без изменений, один голый URL на строку, так что существующие конвейеры продолжают работать. Передайте
--show-meta, чтобы добавить поля.```bash
Rich records: when the URL was alive, and what it served
urx example.com --providers wayback -f jsonl
{"url":"https://example.com/old.php","first_seen":"20040112093000",
"last_seen":"20180722140311","mime":"text/html","archive_status":"200",
"digest":"HT2DYGA5UKZCPBSFVCV3JOBXGW2G5UUA"}
Triage by age: everything last captured before 2010
urx example.com -f jsonl | jq -r 'select(.last_seen < "20100101000000") | .url'
Opt plain output into the metadata
urx example.com --providers wayback --show-meta
Потоковая передача (`--stream`) сообщает только URL-адреса. URL-адрес выводится при первом обнаружении, до того как поступят захваты, которые расширили бы его диапазон `first_seen`/`last_seen`, поэтому `--show-meta` отклоняется там по той же причине, что и `--show-sources`.
Попадание в кэш также не несёт метаданных: кэш хранит URL-адреса, поэтому домен, обслуженный из кэша, сообщает свои URL-адреса без полей захвата. Используйте `--no-cache` (или дождитесь истечения TTL) для запуска, который их перезаполнит.
### Метаданные живого ответа
`--check-status` уже отправляет запрос и ожидает заголовок ответа, поэтому то, что несёт этот заголовок, достаётся бесплатно: `Location`, `Content-Length` и `Content-Type` записываются вместе с кодом состояния. Перенаправления по-прежнему никогда не отслеживаются, поэтому сообщаемый статус всегда принадлежит запрошенному URL-адресу, а `location` просто указывает, куда вёл 3xx.
`--check-title` добавляет HTML `<title>`. Это единственное поле, которое не бесплатно — для заголовка нужно тело ответа — поэтому оно находится за собственным флагом. Чтение ограничено дважды (не более 64 КиБ, и оно останавливается на закрывающем теге) и полностью пропускается для тела, которое сервер объявил как не-HTML, поэтому JSON API или изображение ничего не стоят. Заголовок сжимается по пробелам, декодируется сущности и обрезается до 200 символов. `--check-title` подразумевает `--check-status`.```bash
urx example.com --check-status -f jsonl
urx example.com --check-title --show-meta
urx example.com --check-status --is 30x -f jsonl | jq -r '.url + " -> " + .location'
Правило раскрытия следует тому, что уже задано метаданными архива: json/jsonl/csv всегда содержат поля (отсутствующие ключи опускаются, а столбцы CSV добавляются после существующих), тогда как простой текст остаётся по одному голому URL на строку, если только --show-meta не требует иного. В простом выводе заголовок заключается в кавычки, поскольку это единственное значение, которое регулярно содержит пробелы.
Аутентифицированные и пользовательские запросы
--check-status, --extract-links, --extract-js-endpoints и
--expand-specs все повторно запрашивают собранные URL у самой цели. -H
предоставляет этим запросам любые необходимые им заголовки:```bash
urx example.com --check-status -H "Authorization: Bearer $TOKEN"
urx example.com --extract-links --cookie "session=abc; role=admin"
urx example.com --check-status --user-agent "acme-security-scan/1.0"
`-H` можно указывать повторно, принимает `Name: value`, и некорректный заголовок останавливает запуск, а не проходит незамеченным — аргумент, который молча отбрасывается, оставляет анонимное сканирование выглядящим как аутентифицированное. `--cookie` и `--user-agent` — это сокращения для соответствующих заголовков.
**Эти заголовки никогда не достигают архива.** Они отправляются только теми компонентами, которые общаются с целью: четырьмя тестерами выше, а также провайдерами `robots` и `sitemap`, которые тоже загружают данные с цели. Все остальные провайдеры запрашивают web.archive.org, index.commoncrawl.org или сторонний API, и то же самое делает `--archive-body`, когда воспроизводит захват; передача им сессионного cookie цели означала бы отправку учётных данных сервису, который сохраняет всё полученное, без какой-либо выгоды. Запросы к архивам сохраняют собственный User-Agent urx, который `--random-agent` по-прежнему ротирует.
### Извлечение тел ответов из архива
`--extract-links` загружает каждый собранный URL с живого сайта, что как раз является неправильным местом для поиска страниц, которые наиболее важны при OSINT-разведке: тех, которых больше не существует. `--archive-body` вместо этого загружает тела, сохранённые Wayback Machine. Для каждого собранного URL, несущего метку времени захвата, urx воспроизводит этот захват в его исходной форме
(`https://web.archive.org/web/<timestamp>id_/<url>` — флаг `id_` отключает панель инструментов Wayback и перезапись ссылок, так что тело представляет собой исходные байты) и запускает по нему то же извлечение ссылок, которое использует `--extract-links`.```bash
# Links from the archived bodies of everything the CDX providers found
urx example.com --archive-body
# Bound the run and pace it; the archive is one host no matter how many URLs
urx example.com --archive-body --archive-body-limit 200 --rate-limit 5
# Only the JavaScript those pages referenced back then
urx example.com --archive-body -e js
Почему для этого требуется гораздо меньше запросов, чем для waymore. Каждая строка CDX содержит
дайджест содержимого, и два захвата с одинаковым дайджестом имеют побайтово
идентичное тело. Архивы полны таких: каждый вариант ?utm_source= страницы,
каждый /index.html рядом с его /, каждая перестановка трекинговых параметров
отдаёт идентичные байты, поэтому список из десятков тысяч URL обычно
сжимается до нескольких тысяч различных тел. У waymore нет такого понятия — он
скачивает один ответ на URL и справляется с объёмом с помощью грубого
ограничения -l 5000, которое одновременно нагружает архив и урезает покрытие. urx
засчитывает каждый дайджест при первом его появлении и пропускает каждый последующий URL, который
воспроизвёл бы те же байты, поэтому то же покрытие стоит одного запроса на
различное тело. --archive-body-limit (по умолчанию 500) ограничивает различные тела,
а не URL; дубликаты никогда не учитываются в нём, и --verbose сообщает, сколько
было пропущено.
Извлечение архивного JavaScript. Поверхность API современного приложения живёт в его бандлах
в виде строковых литералов, и --extract-js-endpoints извлекает их с живого
сайта — где они часто уже отсутствуют. Бандлы именуются по хешу сборки, поэтому
app.a3f9c2.js выдаёт 404 в момент переразвёртывания сайта, и указанные в нём эндпоинты
исчезают вместе с ним. Запустите оба флага вместе, и urx извлекает данные из архивной копии
вместо этого, а также из встроенных блоков <script> архивной страницы наряду с её ссылками:```bash
urx example.com --archive-body --extract-js-endpoints
**Сохранение тел.** Запросы уже выполняются, поэтому запись тел на диск не требует дополнительных затрат и отвечает на вопросы, которые не задаёт ни один извлекатель ссылок: комментарий `<!-- staging.internal -->`, токен, встроенный сборкой 2019 года, трассировка стека с указанием версии фреймворка.```bash
urx example.com --archive-body --archive-body-dir ./corpus
grep -ri "api[_-]key" ./corpus
Каждый файл назван по своему URL плюс хеш от него, а corpus/index.jsonl
сопоставляет каждый файл обратно с его URL, временной меткой захвата, дайджестом и типом содержимого.
Хранятся только текстовые тела — HTML, script, JSON, XML, CSS, обычный текст —
поэтому каталог не заполняется изображениями и шрифтами сайта. Поскольку
выборка дедуплицируется по дайджесту, корпус охватывает гораздо больше целевого контента на
запрос, чем один ответ на URL.
Детали, которые стоит знать:
- Подходят только URL с временной меткой захвата. Провайдеры CDX (
wayback,
cc, arquivo) её предоставляют; вход --files, не-CDX провайдеры и кэшированные
результаты (кэш хранит только URL) её не имеют. urx сообщает об этом, когда
нечего воспроизводить — передайте --no-cache, чтобы получить свежие захваты.
- Воспроизводится самый новый захват каждого URL. Временная метка, сообщённая другим
архивом, попадает на ближайший захват Wayback; URL, который Wayback Machine никогда
не видел, отвечает 404 и пропускается. Захваты, записанные архивом как ошибки,
не извлекаются, точно так же как
--extract-links игнорирует живые страницы ошибок.
- Обнаруженные ссылки проходят через те же фильтры, проверку хоста и выходные
преобразования, что и всё остальное, и каждое тело ограничено 10 MiB.
--rate-limit, --rate-limit-by wayback=N, --parallel, --proxy,
--timeout и --retries применяются ко всем запросам воспроизведения.
- Несовместимо с
--stream, как и любая опция, выполняющаяся после сбора.
Расширение спецификаций API
Обход -p only-api находит /swagger.json, /openapi.yaml и /v3/api-docs,
а затем никогда их не открывает: --extract-links разбирает HTML, --extract-js-endpoints
отбрасывает тела application/json, а --archive-body запускает HTML-парсер по
тому, что возвращает архив. --expand-specs читает их и разворачивает каждый маршрут,
который они описывают, в результирующий набор — один запрос даёт всю документированную
поверхность, точную и уже параметризованную.```bash
urx example.com -p only-api --expand-specs
urx example.com --expand-specs --max-spec-files 10 --rate-limit 2
Recover an API the live host no longer serves: read the archived document
urx example.com --archive-body --expand-specs
Что разворачивается:
* **OpenAPI 3.x** — `servers[].url` (абсолютный, относительно документа и шаблонный,
с `{var}`, разрешаемым из `variables[var].default` или первого значения `enum`)
в сочетании с каждым ключом `paths`; собственные `servers` элемента пути
переопределяют серверы документа.
* **Swagger 2.0** — `schemes` × `host` + `basePath`, каждая часть откатывается к
соответствующей части URL самого документа. `ws`/`wss` отбрасываются.
* **Интроспекция GraphQL** — один URL на каждое поле query, mutation и subscription,
записываемый как эндпоинт плюс `?query=…`. Схема, сохранённая в файл,
разрешается в свой эндпоинт (`/graphql/schema.json` → `/graphql`).
JSON и YAML читаются оба. Цели выбираются сначала по имени и бесплатно (подстрока
маркера спецификации — `swagger`, `openapi`, `api-docs`, `graphql`,
`introspection` — плюс расширение `json`/`yaml`/`yml`, когда оно есть, так что
`swagger-ui.html` не стоит ни одного запроса), затем по `Content-Type` ответа.
Шаблоны путей выводятся так, как их записывает документ (`/users/{id}`, а не
`/users/%7Bid%7D`). Тела ограничены 10 MiB, а YAML-документ с более
чем 32 ссылками-алиасами отклоняется до разбора, чтобы исключить бомбы
разворачивания. `--max-spec-files` (по умолчанию 50) ограничивает число
загружаемых документов. При также включённом `--archive-body` архивная
спецификация читается как одна без дополнительных затрат на запрос — тело уже
было загружено.
### Архивные robots.txt и sitemap.xml
Провайдеры `robots` и `sitemap` читают *живые* файлы, которые говорят лишь о том,
что сайт скрывает или перечисляет сегодня. `--archived-discovery` также читает
каждую отдельную версию этих файлов, сохранённую Wayback Machine. `Disallow:`
от 2015 года называет пути, о которых сайт с тех пор перестал упоминать — часто
потому, что их хотели забыть, а не потому, что они исчезли, — а старый sitemap
перечисляет всё, что сайт когда-то хотел обойти сканером.```bash
# Every archived version of robots.txt and sitemap.xml, alongside the live ones
urx example.com --archived-discovery
# Bound it and pace it; both archived providers answer to --rate-limit-by
urx example.com --archived-discovery --archived-discovery-limit 20 --rate-limit-by robots=2,sitemap=2
# Only the versions captured in a given era
urx example.com --archived-discovery --from 2014 --to 2016
Как это работает и почему это дёшево:
- Версии документа перечисляются с помощью одного CDX-запроса на файл
(
robots.txt, sitemap.xml, sitemap_index.xml, sitemap.txt), используя
collapse=digest, чтобы последовательные захваты, отдавшие одинаковые байты,
сворачивались в одну строку. Запрашиваются только строки, записанные как
успешные: индекс объединяет www. и apex в один список, и их чередующиеся
строки 301/200 иначе ломают свёртку — для github.com/robots.txt это 325k
строк без фильтра и 14k с ним, при одних и тех же 107 различных версиях.
- Каждая отдельная версия воспроизводится в сыром виде (
/web/<timestamp>id_/…) и
передаётся тому же парсеру, что и живой файл. Никакого второго парсера:
robots.txt 2015 года читается ровно по тем же правилам, что и текущий, включая
проверки абсолютных путей и пропуска шаблонов. Архивный <sitemapindex>
прослеживается в его дочерние элементы такими, какими они были в тот же момент.
- Захваты, записанные архивом как ошибки (robots.txt github.com был 401 часть
2007 года), пропускаются без запроса и сообщаются только при
--verbose.
--archived-discovery-limit (по умолчанию 50) ограничивает количество
документов, загружаемых на домен каждым архивным провайдером, начиная с самых
новых версий; вложенные sitemap учитываются. --verbose сообщает, когда лимит
обрезал список.
- Архивные варианты работают как собственные экземпляры провайдеров — "Robots.txt
(archived)" и "Sitemap (archived)" в
--stats и --show-sources — но под
существующими идентификаторами robots / sitemap, поэтому --exclude-robots,
--exclude-sitemap и --rate-limit-by robots=N управляют как живыми, так и
архивными чтениями. --from / --to сужают, какие версии рассматриваются.
- Работает с
--stream; это провайдер, как и любой другой.
Фильтрация на стороне архива
--archive-status, --archive-mime, --from и --to вычисляются CDX-индексом
архива, а не urx. Стоит знать о двух последствиях:
- Они применяются только к провайдерам на основе CDX —
wayback, cc,
arquivo и любому --cdx-endpoint. Другие провайдеры их игнорируют; urx
предупреждает, когда ни один не включён.
- Архивы не используют один диалект фильтров. Wayback Machine (и любой
endpoint с
--cdx-dialect classic) трактует значения как регулярные
выражения, поэтому --archive-status "30." соответствует любому 3xx. Common
Crawl, Arquivo.pt и pywb-endpoints сопоставляют точно, и их индекс
объединяет повторяющиеся фильтры через AND — поэтому многозначный
положительный список вроде --archive-status 200,301 там невыполним. urx
пропускает этот фильтр для таких провайдеров (с предупреждением) вместо
отправки запроса, который вернулся бы пустым. Многозначные исключения
означают "не это и не то" и работают везде.
Используйте --archive-status, когда вам нужно то, что архив записал во время
сканирования, и --check-status / --include-status, когда вам нужен статус
цели сейчас; последние повторно запрашивают каждый URL.
Пользовательские CDX-endpoints
Каждый веб-архив, построенный на pywb, OutbackCDX или CDX-сервере Internet
Archive, предоставляет один и тот же API запросов. Вместо жёсткого задания
провайдера для каждого архива, --cdx-endpoint URL превращает любой такой
сервер в провайдера на месте:```bash
The Icelandic web archive, alongside the default providers
urx example.is --cdx-endpoint https://vefsafn.is/cdx
Several at once; each gets its own progress line, stats row and rate limit
urx example.com --cdx-endpoint https://vefsafn.is/cdx --cdx-endpoint http://localhost:8080/cdx
--rate-limit-by cdx:vefsafn.is=1
* Идентификатор провайдера — `cdx:<host>` (`cdx:vefsafn.is`), именно его используют
`--exclude-providers`, `--rate-limit-by`, `--stats` и `--show-sources`.
Указание эндпоинта включает его; запись в `--providers` не требуется, а
`--providers cdx:vefsafn.is` запускает его отдельно. `--list-providers` показывает
эндпоинты, указанные в той же командной строке, с идентификаторами, под которыми они будут запущены.
* Всё, что поддерживают встроенные CDX-провайдеры, действует и здесь: `--subs`,
`--from`/`--to`, фильтры `--archive-*`, пагинация, `--rate-limit` и
описанные выше метаданные захвата.
* `--cdx-dialect classic|pywb` задаёт диалект сервера (имена полей, семантика фильтров,
формат строк и схема пагинации следуют из него — см. «Archive-side Filtering»). Если не задан,
urx один раз за запуск опрашивает эндпоинт и откатывается к `pywb`, более распространённому
диалекту; задавайте его явно, когда проба не может определить (например, пустой ответ для
неизвестного домена).
* Также можно задать в файле конфигурации (`cdx_endpoint = [...]`, `cdx_dialect`).
**Проверенные эндпоинты.** На момент написания единственный публичный эндпоинт, подтверждённо
работающий от начала до конца, — `https://vefsafn.is/cdx` (исландский веб-архив
Landsbókasafn, диалект pywb). О нём нужно знать две вещи: он игнорирует `limit`, `page`
и `showNumPages` и возвращает полный набор результатов для каждого запроса, что
urx обрабатывает; и после нескольких запросов он может начать отвечать
страницей бот-защиты в стиле Anubis («Session Verification»). urx обнаруживает HTML-ответ
вместо строк CDX и сообщает о нём как об ошибке провайдера с указанием
эндпоинта — это никогда не засчитывается как «нет URL». Если вы с этим столкнулись, замедлитесь с
`--rate-limit-by cdx:vefsafn.is=1` или повторите попытку позже.
**Известно, что не работает.** UK Web Archive (`webarchive.org.uk`), веб-архив Библиотеки
Конгресса (`webarchive.loc.gov`), Bibliotheca Alexandrina и
Национальная библиотека Австралии (`web.archive.org.au`) — все они находятся за бот-защитой
или перенаправлениями, которые блокируют их CDX API для клиента командной строки.
urx не пытается это обойти, поэтому указание `--cdx-endpoint` на них
даёт ошибку HTML-ответа, описанную выше.
### Кэширование и инкрементальное сканирование
Urx поддерживает кэширование для повышения производительности при повторных сканированиях и инкрементальное сканирование для обнаружения только новых URL.```bash
# Enable caching with SQLite (default)
urx example.com --cache-type sqlite --cache-path ~/.urx/cache.db
# Use Redis for distributed caching
urx example.com --cache-type redis --redis-url redis://localhost:6379
# Incremental scanning - only show new URLs since last scan
urx example.com --incremental
# Set cache TTL (time-to-live) to 12 hours
urx example.com --cache-ttl 43200
# Disable caching entirely
urx example.com --no-cache
# Combine incremental scanning with filters
urx example.com --incremental -e js,php --patterns api
# Configuration file with caching settings
urx -c example/config.toml example.com
Управление кэшем
urx cache проверяет и обслуживает кэш, не трогая базу данных вручную. Каждая подкоманда учитывает те же --cache-type, --cache-path, --redis-url и --cache-ttl, что и сканирование, и все пять работают с обоими бэкендами.```bash
urx cache stats # entries, domains, URLs, age span, size, expired count
urx cache list # per-domain counts, last scan, TTL remaining
urx cache list --domain '*.example.com'
urx cache prune # delete only what --cache-ttl has expired
urx cache drop example.com # rescan one target without clearing the rest
urx cache clear --yes # delete everything
machine-readable
urx cache stats -f json | jq '.expired_entries'
Сопоставление доменов нечувствительно к регистру и **точное**, если только шаблон не содержит
`*` — сопоставление по подстроке по умолчанию позволило бы `drop example.com` удалить
`notexample.com` тоже. `clear` спрашивает перед удалением и отказывается работать с
неинтерактивным stdin, вместо того чтобы предполагать ответ, `drop` называет любой шаблон,
который ничему не соответствовал, просмотр кэша никогда его не создаёт, а Redis очищается
с помощью `SCAN`, а не блокирующей `KEYS` (при этом любой пароль в `--redis-url`
редактируется перед выводом).
#### Сценарии использования кэширования```bash
# Daily monitoring - only alert on new URLs (built-in webhook, see below)
urx target.com --incremental --silent --notify https://hooks.slack.com/services/... --notify-format slack
# ...or hand the new URLs to an external notifier
urx target.com --incremental --silent | notify-tool
# Efficient domain lists processing
cat domains.txt | urx --incremental --cache-ttl 3600 > new_urls.txt
# Distributed team scanning with Redis
urx example.com --cache-type redis --redis-url redis://shared-cache:6379
# Fast re-scans during development
urx test-domain.com --cache-ttl 300 # 5-minute cache for rapid iterations
Уведомления через вебхуки
--notify <URL> отправляет POST-запрос со сводкой о запуске на вебхук по завершении запуска,
что превращает --incremental в монитор: поместите его в cron, и вебхук
сработает только тогда, когда появится что-то новое.```bash
Slack incoming webhook, only when the run finds new URLs (the default)
urx target.com --incremental --silent
--notify https://hooks.slack.com/services/T000/B000/XXXX --notify-format slack
Discord, and send even when nothing is new
urx target.com --incremental --notify "$DISCORD_HOOK" --notify-format discord --notify-on always
Several receivers, urx's own JSON schema (the default format)
urx target.com --incremental --notify https://n8n.example/hook --notify https://ntfy.example/urx
Keep the webhook out of the shell history
export URX_NOTIFY_URL=https://hooks.slack.com/services/...
urx target.com --incremental --notify-format slack
- `--notify-on` по умолчанию имеет значение `new`: ничего не отправляется, когда запуск выдаёт ноль
URL-адресов, поэтому тихий запуск cron остаётся тихим. `always` отправляет в любом случае; `never`
сохраняет конфигурацию, но отключает отправку.
- `--notify-format json` (по умолчанию) отправляет схему urx: `domains`,
`incremental`, `url_count`, `new_url_count`, `elapsed_ms`, список `providers`
по каждому провайдеру (те же числа, что выводит `--stats`), и `sample` из не более
чем 20 выданных URL-адресов с установленным `sample_truncated`, когда найдено больше.
`slack` отправляет `{"text": ...}`, а `discord` отправляет `{"content": ...}` с
коротким человекочитаемым сообщением; сообщения длиннее, чем допускает сервис,
обрезаются по границе строки и заканчиваются на `[truncated: N lines cut ...]`.
- Доставка никогда не изменяет код выхода. URL-адреса уже находятся в stdout или в
`--output` к моменту вызова webhook, поэтому неработающий webhook — это предупреждение
в stderr, и запуск всё равно завершается с кодом 0. `--verbose` показывает статус ответа.
- URL webhook — это учётные данные. urx никогда не выводит больше, чем его схему и
хост — ни в `--verbose`, ни в предупреждениях, ни в `--stats`. Чтобы не хранить его
в конфиге, который попадает в систему контроля версий, поместите его в `URX_NOTIFY_URL` или как
`notify_url` в файле конфигурации провайдера; `[notify].url` в основном конфиге
тоже работает. Приоритет: CLI/env > конфигурация провайдера > основной конфиг.
- Запрос учитывает `--proxy`, `--proxy-auth`, `--timeout` и `--insecure`.
`--network-scope` не применяется: он разделяет трафик, направленный на цель
и архивы, а webhook — это ваша собственная конечная точка.
- `--silent` всё равно отправляет (это основной вариант использования); он только скрывает
диагностику.
## Интеграция с другими инструментами
Urx хорошо работает в конвейерах с другими инструментами безопасности и разведки:```bash
# Find domains, then discover URLs
echo "example.com" | urx | grep "login" > potential_targets.txt
# Combine with other tools
cat domains.txt | urx --patterns api | other-tool
Вдохновение
Urx был вдохновлён gau (GetAllUrls) — инструментом, который извлекает известные URL-адреса из AlienVault's Open Threat Exchange, Wayback Machine и Common Crawl. Хотя Urx обладает схожей базовой функциональностью, он был создан с нуля на Rust с упором на производительность, конкурентность и расширенные возможности фильтрации.
Участие в разработке
Urx — это проект с открытым исходным кодом, созданный с ❤️
если вы хотите внести свой вклад в этот проект, пожалуйста, ознакомьтесь с CONTRIBUTING.md и отправьте Pull-Request с вашими интересными материалами.