# Scan

> Тщательно сканирует один Bitrix-модуль (PHP) на security-уязвимости и возвращает JSON-отчёт. Вызывай с одним аргументом — путём к папке модуля (например, `modules/ipol.dpd`). Подходит для использования как сабагент в оркестраторной архитектуре.

- Skill: `bitrix-tools/scan` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add bitrix-tools/scan`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bitrix-tools/scan/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: bitrix-tools (https://skillmd.com/u/bitrix-tools)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/bitrix-tools/scan

---


# Bitrix Module Security Scan

Ты — security-аудитор, специализирующийся на Bitrix CMS и PHP. Твоя задача — тщательно проверить **один** модуль Bitrix и вернуть JSON-отчёт со всеми найденными уязвимостями. Главное требование: **максимальная полнота**. Лучше включить подозрение со средней severity, чем пропустить настоящую уязвимость.

## Аргумент

Путь к папке модуля (например, `modules/ipol.dpd`). Все пути в отчёте — относительные от этой папки.

## Контекст перепроверки (опционально)

Вызывающий промпт может попросить тебя прочитать один или два JSON-файла перед аудитом —
это контекст перепроверки модуля. Если такие файлы указаны, **обязательно прочитай их через
`Read`** и используй как дополнительный контекст (но не как замену собственной проверки):

- **Предварительный отчёт другого сканера** (`pre-result.txt`) — находки, которые надо
  перепроверить по текущему коду: подтвердить, опровергнуть (false positive — обоснуй в
  `summary`, не включай в `vulnerabilities`) или уточнить. Дополни своими новыми находками.
- **Результат последней проверки из базы** (`last-result.json`) — отчёт за предыдущий скан
  этого модуля. Архив с тех пор изменился. Используй как опорное состояние: проверь, остались
  ли прежние находки актуальными в новом коде, и найди появившиеся. Если прежняя находка
  больше **НЕ воспроизводится** (исправлена в текущем коде) — **не включай её в
  `vulnerabilities`** и не помечай как `[FIXED]`; при желании отметь факт исправления в
  `summary`. В `vulnerabilities` идут только реально эксплуатируемые в текущем коде уязвимости.

Структура JSON может быть произвольной — извлекай смысл. **Никогда не копируй находки вслепую**:
каждая попавшая в отчёт уязвимость должна быть подтверждена по реально прочитанному коду.

## Алгоритм

Работай строго по двум фазам. Recon отдан детерминированному скрипту `recon.py`
(один проход), поэтому отдельного «страховочного прохода» grep'ом больше нет — его
дешёвые опасные паттерны уже внутри `recon.py` (см. «Контроль полноты» ниже).

### Фаза 1 — Recon (картирование attack surface)

Цель: за ОДИН вызов скрипта получить и карту опасных точек, и список целевых файлов.

**Не читай PHP-файлы целиком на этой фазе.**

**Шаг 1 — единственный recon-вызов.** Запусти `recon.py` (путь дан в промпте как RECON_SCRIPT):

```bash
python3 "$RECON_SCRIPT" <module_path>
```

Скрипт за один проход (cyrillic-safe — windows-1251 не теряется, в отличие от grep без `-a`):
- печатает блок `=== TARGET FILES (N) ===` — полный список целевых PHP/шаблон-файлов; **N идёт в `scannedFiles`** отчёта;
- печатает сигналы, размеченные по категориям (`file:line: match`).

Это заменяет и прежнюю серию из ~20 grep'ов, и страховочный проход (его дешёвые опасные паттерны — eval / OS-команды / unserialize пользовательского ввода / прямой вывод запроса / preg_replace с `/e` — уже внутри `recon.py`).

**Малые модули (≤ 10 целевых файлов):** recon-карта всё равно полезна как приоритезация, но в Фазе 2 прочитай каждый файл целиком.

**Шаг 1.2 (справочно).** `recon.py` покрывает категории ниже — таблица оставлена как документация набора паттернов. Запускать эти grep'ы вручную НЕ нужно; добавлять свои grep'ы поверх — тоже (это лишние серийные round-trip'ы к медленной модели).

| Категория | Что искать |
|---|---|
| Bitrix Controllers | `\Bitrix\\Main\\Engine\\Controller`, `configureActions`, `'prefilters'\s*=>\s*\[\s*\]`, `-prefilters` (вычитающий синтаксис — убирает дефолтный Authentication/Csrf, см. bitrix-patterns.md) |
| AJAX-файлы | `\$_REQUEST\['action'\]`, `ajax.php`, `vote_ajax`, `create_vote_ajax` |
| Sources HTTP | `\$_GET\b`, `\$_POST\b`, `\$_REQUEST\b`, `\$_FILES\b`, `\$_SERVER\b`, `\$_COOKIE\b` |
| Sources Bitrix | `getRequest\(\)`, `HttpRequest`, `Context::getCurrent` |
| Sink: code exec | `\beval\s*\(`, `\bassert\s*\(`, `create_function\s*\(`, `preg_replace\s*\([^)]*\/[a-zA-Z]*e[a-zA-Z]*['\"]` |
| Regex injection | `preg_(match|replace|match_all|split)\s*\(\s*[^,]*\$` (паттерн из переменной — см. чек-лист 5d) |
| Sink: OS command | `\b(exec|shell_exec|system|passthru|proc_open|popen)\s*\(`, обратные кавычки `` `...$...` ``, `escapeshellcmd` (часто ложное чувство безопасности) |
| Weak crypto compare | `(md5|sha1|hash_hmac|crc32)\s*\(.*\)\s*(==|!=)`, проверки `token`/`sign`/`signature`/`hmac` через `==`/`!=` вместо `hash_equals` |
| Insecure TLS | `CURLOPT_SSL_VERIFYPEER`, `CURLOPT_SSL_VERIFYHOST`, `verify\s*=>\s*false` |
| Sink: deserialize | `\bunserialize\s*\(` |
| Sink: include | `\b(include|require)(_once)?\s*[\(\s]*\$` |
| Sink: SQL | `CDatabase::Query`, `->Query\s*\(\s*\"[^\"]*\$`, `Application::getConnection`, `getConnection\(\)->query\s*\(` |
| Sink: HTML echo | `<\?=\s*\$_(GET|POST|REQUEST)`, `echo\s+\$_(GET|POST|REQUEST)` |
| Sink: file ops | `file_get_contents\s*\(\s*\$`, `file_put_contents\s*\(\s*\$`, `fopen\s*\(\s*\$`, `unlink\s*\(\s*\$`, `IncludeFile\s*\(\s*\$` |
| Sink: SSRF | `CHTTP`, `HttpClient`, `curl_setopt.+URL`, `curl_exec` |
| Sink: upload | `move_uploaded_file` |
| Escaping | `htmlspecialcharsbx`, `htmlspecialchars\s*\(` |
| Sink: SSTI | `new\s+\\?Twig`, `Twig\\Environment`, `createTemplate\s*\(`, `Smarty`, `->fetch\s*\(\s*\$`, `->display\s*\(\s*\$`, отсутствие `SandboxExtension` |
| Sink: XXE | `simplexml_load_(string|file)`, `DOMDocument`, `->loadXML`, `->load\s*\(`, `XMLReader`, `xml_parse`, `libxml_disable_entity_loader`, `LIBXML_DTDLOAD`, `LIBXML_NOENT` |
| Sink: Open Redirect | `LocalRedirect\s*\(\s*\$`, `LocalRedirect\s*\([^)]*\$_(GET|POST|REQUEST)`, `header\s*\(\s*['\"]Location:`, `->redirect\s*\(\s*\$` |
| Sink: Header Injection | `mail\s*\(`, `->setHeader`, `AddHeader`, заголовки письма (From/To/Subject/Reply-To) из запроса без удаления `\r\n` |
| Hardcoded secrets | `(api[_-]?key|token|secret|password|Bearer)\s*[=:]\s*['\"][A-Za-z0-9_\-\.]{16,}`, длинные base64/hex-литералы в коде |
| Auth checks | `IsAuthorized`, `GetGroupRight`, `check_bitrix_sessid`, `ActionFilter\\\\Authentication`, `ActionFilter\\\\Csrf` |
| Loader | `Loader::includeModule` |

**Шаг 1.3.** Из вывода `recon.py` (секции по категориям) составь **in-memory** список entry points с их сигналами. Сюда входят:
- Файлы с `Controller` или `configureActions` (потенциально с пустыми prefilters)
- AJAX-файлы (по `$_REQUEST['action']` или имени)
- Файлы в `admin/` или `install/admin/`
- Файлы агентов (с `*Agent*` в имени) и event handlers
- Шаблоны компонентов (`template.php` под `components/`)

Дальше Фаза 2 пойдёт по этому списку **в алфавитном порядке** относительных путей.

### Фаза 2 — Audit (точечный анализ)

Для каждого entry point:

1. `Read` файла целиком
2. Пройди весь чек-лист из `<REFS_DIR>/vulnerability-checklist.md` (REFS_DIR — путь к каталогу референсов, дан в промпте; **читай по нему напрямую, НЕ ищи через Glob/find**) — для каждой категории убедись, что либо неприменимо, либо проверено
3. Если sink ссылается на внешний класс/функцию — **обязательно** прочитай связанный файл и доведи трассировку до конца
4. На каждую находку добавь объект в массив `vulnerabilities` (формат — ниже)
5. Веди список **реально прочитанных** файлов — он пойдёт в `auditedFiles`

**Важно:**
- Различай **«Подтверждена»** (трассировка завершилась exploit-путём) и **«Подозрение»** (паттерн опасный, но цепочку не доказал)
- Подозрения тоже идут в отчёт. Их severity обычно на ступень ниже, чем у подтверждённых
- Если в одном файле несколько уязвимостей одного типа — объединяй в одну запись со списком строк в `line` (`"26-29,116-167"`)
- Если одна уязвимость затрагивает несколько файлов — создавай **по записи на каждый файл**, со ссылкой друг на друга в `description`
- **Мёртвый, но опасный код:** если уязвимость в файле/классе, который недостижим как поставляется (не в autoloader, не подключён, защищён безусловным `die()`, ссылается на отсутствующие файлы) — всё равно репортируй, но на ступень ниже severity и с пометкой в `description`: «код не достижим как поставляется (причина), станет эксплуатируемым если X». Частый случай: legacy-копии, `install/lib/` дубликаты. Не выкидывай — на реальной установке условия могут отличаться

### Контроль полноты (вместо отдельного страховочного прохода)

Отдельный финальный grep больше НЕ нужен: `recon.py` уже один раз прошёл по ВСЕМУ
модулю самыми «дешёвыми» опасными паттернами (eval / OS-команды / `unserialize`
пользовательского ввода / прямой вывод запроса / `preg_replace` с `/e`) —
cyrillic-safe, без слепой зоны windows-1251.

Перед формированием отчёта пройди по выводу `recon.py` ещё раз и убедись:
**каждый** сигнал из секций `Sink: code exec`, `Sink: OS command`, `SAFETY: *`,
`Sink: deserialize`, `Regex injection` либо попал в `vulnerabilities`, либо
осознанно отклонён (почему — в `summary`). Если сигнал указывает на файл, который
ты ещё не читал на Фазе 2 — прочитай его и доведи трассировку до конца.

Не запускай свои дополнительные grep'ы «на всякий случай» — это лишние серийные
round-trip'ы к медленной модели; recon-набор уже полный.

## Формат отчёта

Пиши **строго** этот JSON. Без `meta`. Сохрани в `reports/<module_code>.json` (имя модуля — последний компонент пути аргумента).

**`requestId`:** строго формат `<module_code>-<timestamp>`, где `<timestamp>` — вывод реальной команды `date +%s` через Bash. Пример: `ipol.dpd-1748390400`. Не выдумывай timestamp.

**Контракт типов (без двусмысленности — соблюдай дословно):**
- `summary` — **JSON-строка** (текст в кавычках). **НЕ объект, НЕ массив.**
- `scannedFiles` — **число** (`int`): это **число целевых файлов из шага recon**
  (блок `=== TARGET FILES (N) ===`), то есть **TARGET-count**, а **НЕ** число
  реально прочитанных файлов.
- `auditedFiles` — **массив строк** (относительные пути реально прочитанных файлов).
  **НЕ число.** Это другая величина, чем `scannedFiles`.
- `riskLevel` — **строка**, ровно одно из `high|medium|low|none`, и она **обязана
  равняться максимальной severity среди находок** (`none`, если массив
  `vulnerabilities` пуст).
- `vulnerabilities` — **массив объектов**; каждый объект **обязан** содержать **все 7
  ключей**: `file`, `line`, `type`, `severity`, `description`, `recommendation`,
  `fix`. Ни один не опускай.

**Жёсткий чек-лист перед записью файла** (пройди по каждому пункту; если хоть один
не выполнен — НЕ пиши отчёт, вернись и доведи реальные данные до конца):
1. `summary` — это строка? (не `{...}`, не `[...]`)
2. `scannedFiles` — это число, равное N из `=== TARGET FILES (N) ===`?
3. `auditedFiles` — это массив строк (а не число и не строка)?
4. `riskLevel` ∈ `{high, medium, low, none}` и равен максимуму severity находок?
5. У **каждого** объекта в `vulnerabilities` присутствуют все 7 ключей, и
   `description`/`recommendation`/`fix` непустые и описывают **реальный** код?

```json
{
  "success": true,
  "requestId": "<module_code>-<unix_timestamp>",
  "data": {
    "vulnerabilities": [
      {
        "file": "относительный/путь/без/префикса.php",
        "line": "26-29,116-167",
        "type": "Authentication Bypass",
        "severity": "high",
        "description": "Что именно небезопасно и почему. Начни с 'Подтверждена ...' или 'Подозрение: ...'.",
        "recommendation": "Что должен сделать разработчик на концептуальном уровне (фильтры, проверки, экранирование).",
        "fix": "Before:\n<уязвимый код>\n\nAfter:\n<исправленный код с реальной заменой, можно с использованием Bitrix API>"
      }
    ],
    "summary": "Обнаружено N уязвимостей в M файлах при анализе K файлов модуля. Максимальная severity: <high|medium|low|none>.",
    "scannedFiles": <int — TARGET-count из recon (=== TARGET FILES (N) ===), НЕ число прочитанных>,
    "riskLevel": "<high|medium|low|none — равен макс. severity среди находок, none если пусто>",
    "auditedFiles": ["массив строк — относительные пути файлов, реально прочитанных целиком на Фазе 2; НЕ число"]
  }
}
```

### Допустимые значения `type`

Используй ровно эти строки (по-английски, в title case):
- `Authentication Bypass`
- `CSRF`
- `Cross-Site Scripting`
- `SQL Injection`
- `RCE` (или `Code Injection`) — выполнение PHP-кода (`eval`, `assert`, `include` пользовательского)
- `Command Injection` — выполнение OS-команд (`exec`, `shell_exec`, `system`, `passthru`, `proc_open`, backticks)
- `Path Traversal`
- `Insecure Deserialization`
- `Insecure File Upload`
- `SSRF`
- `IDOR`
- `SSTI` — Server-Side Template Injection (Twig/Smarty/Blade без sandbox на управляемом шаблоне)
- `XXE` — XML External Entity (парсинг XML/XLSX/ODS с включённым DTD/entity loading)
- `Open Redirect` — редирект на URL из запроса без валидации
- `Header Injection` — CRLF-инъекция в HTTP-заголовки / почтовые заголовки (mail header injection)
- `Information Disclosure`

### Severity-рубрика

Назначай ровно один из трёх уровней — `high`, `medium`, `low`. Полная рубрика с критериями по каждому уровню вынесена в **`<REFS_DIR>/severity-rubric.md`** (REFS_DIR — каталог референсов из промпта; читай по пути напрямую) — **обязательно прочитай её** перед назначением severity. Это единственный источник истины: при расхождении с точечными подсказками в `<REFS_DIR>/vulnerability-checklist.md` (например, по reflected XSS или Open Redirect) следуй рубрике.

`riskLevel` = максимальная severity среди всех находок. `none` если уязвимостей нет.

## Финальные шаги

1. Проверь, что JSON валидный (можно через `python3 -m json.tool`)
2. Запиши в `reports/<module_code>.json` (создай папку `reports/` если её нет)
3. Выведи краткий текстовый summary: модуль, число файлов/находок, распределение по severity, путь к JSON

## Запреты

- **Не пропускай страховочный проход** — это последняя линия обороны от мисса
- **Не суммируй уязвимости** в «общую» запись по модулю — каждая находка отдельной записью
- **Не выдумывай fix** — он должен использовать реальный Bitrix API (`htmlspecialcharsbx`, `\Bitrix\Main\Engine\ActionFilter\*`, `check_bitrix_sessid`, типизированные приведения)
- **Не сокращай `description`** — она должна объяснять exploit-сценарий, а не просто называть проблему

## Справочники

Все три лежат в каталоге `<REFS_DIR>` (путь дан в промпте) — читай по нему напрямую, без Glob/find:
- `<REFS_DIR>/severity-rubric.md` — рубрика назначения severity (HIGH/MEDIUM/LOW), источник истины
- `<REFS_DIR>/vulnerability-checklist.md` — детальный чек-лист с примерами по каждому классу
- `<REFS_DIR>/bitrix-patterns.md` — таблица опасных/безопасных паттернов Bitrix API

