Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
slipstream — NAT Slipstreaming 允许攻击者远程访问绑定到受害者机器的任何 TCP/UDP 服务,绕过受害者的 NAT/防火墙,只需受害者网络上的任何人访问一个网站即可。 | Kitploit
工具/GitHubGitHub/samyk/slipstream
侦察漏洞利用Web安全网络安全渗透测试
GitHubsamyk/slipstream

slipstream

NAT Slipstreaming 允许攻击者远程访问绑定到受害者机器的任何 TCP/UDP 服务,绕过受害者的 NAT/防火墙,只需受害者网络上的任何人访问一个网站即可。

查看仓库
2.0k21423年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
网站

NAT Slipstreaming

NAT 滑流攻击 允许攻击者远程访问受害者 NAT 后方 任意系统 上的任何 TCP/UDP 服务,只需受害者访问一个网站即可绕过受害者的 NAT/防火墙(远程任意防火墙端口控制)。

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

NAT 滑流攻击架构 此处为动画版本,使用 我的 fork 版本的 draw.io 生成,支持导出边缘上下文流动和动画控制

目录

  • 摘要
  • 详解
    • 网络地址转换 (NAT)
      • 连接跟踪
      • 应用层网关
    • 路由器调查 / 固件转储
    • 固件逆向工程
      • 寻找感兴趣的文件
      • 探索有趣的函数
      • 需要调查的端口/服务
      • 逆向内核对象
    • 连接跟踪 / 应用层网关调查
      • Linux Netfilter
    • 数据包边界 / 分片控制
    • TCP 时序攻击 / 内部子网与 IP 发现
      • 时序攻击
    • 浏览器协议混淆
      • 实时浏览器数据包篡改
  • 其他发现
  • 下载
  • 联系方式

摘要

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 滑流攻击的工作原理如下:

  • 受害者访问恶意网站(或带有恶意广告的网站)
  • 首先必须通过浏览器提取受害者的内部 IP 并发送给服务器
    • 通过 WebRTC 数据通道(通过 HTTPS)尝试提取内部 IP
      • 某些浏览器(如 Chrome)仅通过 HTTPS 的 WebRTC 泄露本地 IP,但我们的一些攻击需要 HTTP,因此首先重定向到攻击软件的 HTTPS 版本以提取本地 IP
      • 如果能够获取到本地 IP,我们将其包含在 URL 中重定向到 HTTP 版本,以绕过其他跨域保护机制(提供的 .local mDNS/Bonjour 地址对攻击无效)
    • 如果 WebRTC 未泄露内部 IP(如 Safari)或没有 WebRTC(IE11 及更早版本),则执行 基于 Web 的 TCP 时序攻击
      • 在后台加载针对所有常见网关的隐藏 img 标签(例如 192.168.0.1)
      • 为 img 标签附加 onerror/onsuccess 事件
      • 如果网关返回任何 TCP RST(或 SYN + HTTP 响应),则检测到有效子网
      • 在检测到的子网(/24)上对所有 IP 重新执行时序攻击,测量 onerror/onsuccess 触发的时间
      • 最快的响应很可能是内部 IP,但所有响应均被视为受害者内部 IP 候选并受到攻击
  • 通过隐藏表单和自动 HTTP POST 向攻击者的“HTTP 服务器”(绑定到非标准端口)发送大型 TCP 信标,强制 TCP 分段并发现受害者 IP 栈的最大 MTU 大小
    • 攻击者 TCP 服务器发送 最大分段大小 TCP 选项,以调整受害者出站数据包大小(RFC 793 x3.1),从而控制浏览器 TCP 数据包的大小
  • 通过 WebRTC TURN 认证机制从浏览器向攻击者服务器的非标准端口发送大型 UDP 信标,强制 IP 分片,并填充 TURN username 字段
    • 我们执行与 TCP 分段类似的攻击,但基于 UDP,因为 IP 分片会提供与 TCP 分段不同的值
    • 服务器检测到受害者 MTU 大小、IP 头部大小、IP 数据包大小、TCP 头部大小、TCP 分段大小,并将其发送回受害者浏览器,用于后续的数据包填充
  • (v1)生成包含内部 IP 的“SIP 数据包”到新的隐藏表单,触发应用层网关连接跟踪
    • 向 TCP 端口 5060(SIP 端口)上的服务器发起“HTTP POST”,避开受限浏览器端口
    • POST 数据被“填充”到精确的 TCP 分段大小/数据包边界,然后通过 Web 表单附加并提交“SIP 数据包”

成功分割为有效 SIP 数据包的数据包

详解

网络地址转换 (NAT)

我们使用 NAT(网络地址转换)有几个原因。NAT 最有用的特性是允许单个公共 IP 地址被多个系统共享。它通过创建一个本地网络,为所有连接的机器提供本地 IP 地址,当其中一个系统访问互联网时,它会重写出站数据包以使用公共 IP,以便响应返回 NAT,反之亦然,重写目标 IP 为特定客户端的 IP。

NAT 需要区分来自内部主机的连接到相同地址/端口(google.com:443),因为最终它们的外出端口、目标 IP 和源 IP 都是相同的。如果两个不同的内部对等体尝试从相同的源端口连接,现代 NAT 会更改其中一个源端口(有些网络对所有 TCP/UDP 源端口都这样做)。

NAT

连接跟踪

来自 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.

root@kitploit:~
如果你的 NAT 后面的某台机器向外出站一个数据包,你的路由器预期远程主机可能会响应,于是它会记录相关信息,特别是源端口、目标端口、源 IP 地址、目标 IP 地址以及你的内部 IP,然后将任何匹配的数据包返回给你的内部 IP。

如果你的局域网中的另一台主机尝试使用相同的源端口、目标端口及 IP 发起同样的连接,你的 NAT 将无法区分它们(它们在局域网内的源 IP 不同,但在广域网侧都被改写为相同的公网 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

root@kitploit:~
![R7000-V1.0.9.64_10.2.64.chk](https://assets.kitploit.com/production/public/readmes/3946/ff37a83fee71d670f7d0298b6c22182a4e55d0e3ec949d82256b532820438723.png)

`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

binwalk R7000-V1.0.9.64_10.2.64.chk

我使用macOS,binwalk开箱即用依赖某些Linux应用程序,这会导致binwalk -e(用于提取文件)失败,因此我手动提取(并且我喜欢perl golf)。```sh tigerblood:~c/ng$ perl -ne'$@.=$_}{print+substr$@,2221098' R7000-V1.0.9.64_10.2.64.chk > squash.fs

root@kitploit:~
Or use [`inout`](https://github.com/samyk/samytools/blob/master/inout), eg `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。另外,你也可以在 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 ../..

root@kitploit:~
最后,我们可以解压 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

我们现在有了原始操作系统可供探索!

逆向工程固件

查找有趣的文件

现在让我们看看能否找到与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

root@kitploit:~
Nothing interesting, so let's `g` for binary files whose content matches /ftp/, ignoring some files we don't care about.```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

root@kitploit:~
### 探索潜在有用的函数

好的,两个值得关注的文件——`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.

root@kitploit:~
### 调查端口/服务

虽然我们找到了一些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/)时,我通过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 = dec -> bin):```sh
tigerblood:/Users/samy/d$ 3 db 65536 6667 65536+6667
000000010000000000000000
000000000001101000001011
000000010001101000001011

我们可以通过我的diffbits工具更好地查看它,这是一个用于查看位串之间以及多组位串之间相似性和差异性的简单工具,对逆向专有二进制协议很有用。

diffbits

逆向内核对象

请打开你选择的反汇编器。我使用了来自NSA的朋友们的Ghidra,因为它是免费且开源的。

我们通过strings在tdts.ko中看到的一些函数是ftp_decode和ftp_decoder,所以其他ALG可能也会有_decode函数。让我们看看...

Ghidra _decode

好吧,一堆_decode函数...往下滚动,一个有趣的是sip_decode。

Ghidra tdts.ko

检查我们的受限浏览器端口,我们看到5060,即默认SIP端口,在Chrome中没有被限制 :)

在HTTP POST中尝试SIP数据包

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()

root@kitploit:~
如果我们嗅探,就会看到(通过 [`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" 听起来像是这样)。

Ghidra sip_decode

啊,这就是我们失败的原因。看起来它在对 INVITE(对 REGISTER 也有类似解析)执行 strncasecmp——匹配(不区分大小写,有趣的是 SIP INVITE 是大写)数据包开头的单词 "INVITE",如果不相等(ARM 汇编 bne),则分支到 0,因此如果单词确实匹配,字典序将为 0,我们将继续执行 ct_sip_get_header,这听起来很有趣,否则似乎会退出。

这就是问题所在……虽然我们可以使用 Web 浏览器创建出站套接字(通过 HTTP(S) 的 TCP,通过带有 WebRTC 的 TURN 的 UDP),但我们无法足够控制浏览器以使用 "INVITE" 单词开始 TCP 数据部分,而这是此模块所期望的。在 2010 年的 IRC 版本 中,IRC ALG 仅逐行查看,忽略所有 HTTP 标头数据,然后使用 POST 数据中的换行符发送有效的 "IRC DCC"。但是,这个 SIP ALG 严格得多,控制请求的开头是不可能的。如果使用 TLS,加密的标头将开始数据包。如果使用 HTTP,HTTP 方法将开始数据包(GET、POST 等)。我们能否以其他方式利用这一点?

连接跟踪 / 应用层网关研究

Linux Netfilter

为了更好地理解连接跟踪和应用层网关,我们可以查看它们在 netfilter,Linux 的网络栈 中的行为。我根据对 Linux 源代码的解析,创建了一个最常见 ALG 及其行为的图表。

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 连接,而我们需要创建以 SIP 方法(如 REGISTER 或 INVITE)开头的 TCP/UDP 数据包。

Flash 曾经允许出站套接字,但格式是我们无法完全控制的。Java 需要权限。WebSockets 仍然是 HTTP。TLS 是加密的。WebRTC (RFC 7742) 是加密的。STUN (RFC 3489) 和 TURN (RFC 5766) 是固定格式,TURNS (RFC 7065) 是加密的。

TCP 分段

从高层次来看,我们无法控制 TCP 数据包的开头,但如果我们发送过大的数据包呢?必须存在一个最大数据包大小……此时,数据包必须被分片成多个数据包。如果我们能溢出 TCP 数据包大小并精确控制部分数据,我们能否导致数据包分段,并使我们的数据位于下一个溢出数据包的最开头?

嗯,我们需要知道浏览器将发送多少数据,这因浏览器而异,甚至因用户而异,因为他们可能发送不同的 HTTP 标头。HTTPS 不起作用,因为大部分内容已加密,而 HTTP POST 允许我们控制大部分标头。

要获取数据包的大致大小,我们向 http://our.attack.server:5060/pktsize 发送一个大型(6000 字节)HTTP POST,其中包含一个 ID 和通过隐藏 Web 表单填充的数据。在攻击服务器上,我们运行一个数据包嗅探器,它会查找我们数据包的边界,以确定 MTU(最大传输单元)大小、IP 标头大小、可能的 IP 选项、TCP 标头大小、可能的 TCP 选项、数据包大小,以及我们控制的数据包部分。

我们还运行一个自定义服务器,它监听 TCP 端口 5060,并响应 HTTP 流量以安抚浏览器,使客户端看起来一切正常(格式错误的响应会导致控制台出现错误,或者响应不正确的服务器会使状态旋转器一直旋转)。

POST large form to measure MTU and TCP data size

我们进一步尝试通过在初始 SYN 响应期间发送一个最大分段大小(mss)TCP 选项来控制 TCP 数据包数据大小,以操纵受害者的出站数据包大小(RFC 793 第 3.1 节)。这告诉受害者机器将 TCP 数据包保持在一定大小。

自定义最大分段大小 (img/sniff1.png)

你可以在 Linux 上通过将 advmss <size> 附加到 ip route 来实现这一点。我们将使用 1500。```sh ip route replace default via [gateway] dev eth0 advmss 1500

root@kitploit:~
一旦我们获取到数据包,我们通过一个独立的POST请求将大小数据发送回受害者客户端,该请求中包含了受害者的ID,以便我们能够将其与来自受害者的原始请求关联起来。此时,客户端已经很好地了解了如何填充数据包,使得任意数据能够落在TCP数据包中的任何特定位置。

### 使用UDP和TURN的IP分片

一些NAT只允许在SIP连接最初是UDP的情况下访问UDP端口,因此在这种情况下我们使用TURN。TURN是一种支持SIP和WebRTC等点对点通信中继的协议。TURN使用UDP,而TURNS(TURN+TLS)使用TCP。现代浏览器支持TURN用于WebRTC,以防它们无法直接建立点对点连接进行媒体共享。

TURN允许通过用户名和密码进行身份验证,用户名以明文发送。有趣的是,用户名没有大小或字符限制,因此我们可以利用这一点执行相同类型的数据包溢出。

由于TURN基于UDP,如果溢出超过MTU大小(UDP不支持分段),IP数据包本身将被分片。第二个数据包不仅包含我们控制的数据部分,还包含UDP头部!这对我们的攻击并不重要,但很有趣,并且肯定可以产生其他攻击方式。最终,我们可以通过UDP执行相同的攻击,根据计算出的MTU大小而非MSS大小对齐数据包边界,使我们的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)。

我们在Linux的`nf_conntrack_sip.c`中的[process\_sip\_request](https://github.com/samyk/linux/blob/ea2cec24c8d429ee6f99040e4eb6c7ad627fe777/net/netfilter/nf_conntrack_sip.c#L1260)中看到了这个检查:

![SIP REGISTER Via IP validation](https://assets.kitploit.com/production/public/readmes/3946/46b6a320ab0e8972a37c8e057cebb36d5fb32905ef2894ea6a06bfe508e7db59.png)

在[2010](https://samy.pl/natpin/)年,我们使用了[LiveConnect](https://developer.mozilla.org/en-US/docs/Archive/Web/LiveConnect),它允许在某些条件下从Javascript执行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服务器也不知道该IP,除非客户端首先发送了它。问题在于并非所有浏览器都提供这种机制。

截至目前,在Chrome上使用WebRTC获取本地IP地址(而不是`.local` mDNS/Bonjour地址)需要使用HTTPS,但攻击的其余部分需要HTTP,因此我们首先检测是否使用HTTP,如果不是,则重定向到HTTPS。然后我们尝试使用WebRTC提取本地IP地址。无论结果如何,我们随后会重定向回HTTP,并将IP地址附加到URL,以通过其他通信方法绕过跨域限制。

### 时间攻击

如果使用Safari、IE <= 11或其他不支持WebRTC或故意不透露内部IP的浏览器(如Safari),我们可以使用Web时间攻击来揭示受害者的内部IP地址。

我们通过首先在页面上生成隐藏的HTML `` 标签来实现这一点,这些标签指向常见的网关(192.168.*.1、10.0.0.1和[其他](https://github.com/samyk/slipstream/blob/main/server#L159)),并配合Javascript的`onsuccess`和`onerror`事件。每次将img写入页面时,都会启动一个计时器。如果`onsuccess`加载,表示该IP有一个Web服务器响应;如果没有运行Web服务器但该IP存在于网络中,它将返回TCP RST(重置,表示端口未开放),触发`onerror`。如果IP不存在,则不会发送RST,响应时间将超过1秒,此时我们知道该IP不在我们的网络上。

一旦我们看到其中一个事件被触发,我们就知道我们可能所在的内部子网,然后我们对子网上的每个IP(例如192.168.0.[2-255])执行相同的攻击,这次执行更精确的时间测量以确定哪个IP响应**最快**。这很可能就是我们自己(受害者)的内部IP,因为我们甚至不需要离开网络接口。即使我们由于某种原因不是第一个,我们仍然会尝试攻击网络上响应的所有IP。

## 浏览器协议混淆

一旦客户端获取了数据包大小和内部IP地址,它就会构造一个特制的Web表单,该表单填充POST数据直到我们认为数据包将被分片,此时附加包含内部IP地址的SIP REGISTER。表单通过Javascript提交,无需受害者同意。:)

[![successful packet broken into valid SIP packet](https://assets.kitploit.com/production/public/readmes/3946/9d8133cafc497975e4bfc89034da0246e8bdd460f73b213e91d71bf14dd097e6.png)](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`来DoS一个SIP客户端,并移除他人的conntrack
- 如果某个端口已被占用,监听的端口会递增直到端口溢出为0
- STUN在任何现代浏览器中都没有实现身份验证

# 下载

感谢阅读!您可以从我的[NAT Slipstream github](https://github.com/samyk/slipstream)下载概念验证代码。

# 联系方式

**联系人:** [@SamyKamkar](https://twitter.com/samykamkar)

在<https://samy.pl>上可以找到我的更多项目,或者可以通过<[email protected]>联系我。
下载工具
  • 受害者 IP 栈将 POST 分割成多个 TCP 数据包,使得“SIP 数据包”(作为 POST 数据的一部分)单独出现在一个不包含任何 HTTP 头部的 TCP 数据包中
  • 如果浏览器更改了 multipart/form 边界的大小(Firefox)或由于其他原因导致数据包大小改变,大小变化会传达给客户端,客户端自动以新大小重新发送
  • 打开 UDP 端口时,SIP 数据包通过 TURN 协议在特制的 username 字段中发送,强制 IP 分片并实现精确边界控制
  • (v2)生成“H.323 数据包”(使用基于 TCP 的 STUN,绕过 v1 的补丁和浏览器端口限制)包含内部 IP,触发应用层网关连接跟踪,但强制重定向到网络上的 任何其他主机,作为一个“呼叫转接”数据包
    • 向 TCP 端口 1720(H.323 端口)上的服务器发起“H.323 呼叫转接”,避开受限浏览器端口,尽管该端口被阻止——通过使用 WebRTC STUN 功能实现端口规避,该功能不遵守受限端口列表
    • username 字段被“填充”到精确的 TCP 分段大小/数据包边界,然后通过 Web 表单附加并提交“H.323 数据包”
    • 受害者 IP 栈将 POST 分割成多个 TCP 数据包,使得“H.323 数据包”(作为 STUN 数据的一部分)单独出现在一个不包含任何 HTTP 头部的 TCP 数据包中
    • 如果浏览器更改了 multipart/form 边界的大小(Firefox)或由于其他原因导致数据包大小改变,大小变化会传达给客户端,客户端自动以新大小重新发送
  • 受害者 NAT 看到 SIP 端口上正确的 SIP REGISTER 数据包或正确的 H.323 呼叫转接数据包(不含 HTTP 数据),触发 ALG 打开数据包中定义的任何 TCP/UDP 端口到网络上任何受害者主机
    • 受害者 NAT 重写 SIP 或 H.323 数据包,将内部 IP 替换为公共 IP,向攻击者提示攻击成功
    • (v2)由于 H.323 呼叫转接可以定向到任何其他 IP,数据包可以包含受害者网络上任何其他主机的任何内部 IP,从而触发 NAT 将端口转发到网络上的任何系统
    • 即使受害者 NAT 通常重写源端口,ALG 仍会被迫将端口转发到攻击者选择的端口,因为它认为受害机器(或网络上的其他机器,完全由攻击者决定)打开了该端口,攻击者可以在到达的 SIP/H.323 数据包中看到新的源端口
    • 攻击者现在可以绕过受害者 NAT 并直接连接回网络上任何机器的任何端口,从而暴露之前受保护/隐藏的服务和系统
  • 有待研究……也许由你来探索?
    • 非恶意用途:这种技术基本上为浏览器提供了完整的 TCP 和 UDP 套接字能力,可以与系统上的任何协议进行本地通信;连接可以通过云服务器抽象进行回连,但浏览器仅与云服务器通信,就像它是一个套接字一样,使浏览器能够在非 Web 友好协议上进行通信
    • 如果在虚拟机(VM)中使用共享网络(用于通过主机路由来保护主机免受攻击,不让它直接进入网络)进行测试,如果数据包能够出去,端口将在父宿主主机上打开,而不是在 VM 上 ;)
    • IP 分片允许完全控制 IP 数据部分中的所有数据,这意味着完全控制 UDP 头部,包括溢出数据包中的源/目标端口……这还能被用来滥用什么呢?
  • strncasecmp(*dptr, handler->method, ...) 除非方法(例如 REGISTER)出现在数据包(TCP 或 UDP)数据部分的开头,否则处理程序将退出,就像我们上面看到的 INVITE 那样……REGISTER 只是另一个 SIP 命令
  • 这是一个挑战,因为如果我们仅使用 Web 浏览器,我们无法生成原始 TCP 连接并以我们自己的数据开始任何数据包,因为它将被 HTTP/TLS 标头填充……或者我们可以吗?
  • process_register_request(...) 通过 sip_handlers 调用 nf_ct_expect_init(...),我们初始化防火墙孔(允许远程人员连接回来的端口),但我们还没有打开它
  • 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(...) 仅当 SIP 服务器发送了有效的 SIP 响应后,NAT 才会转发端口