HTTP/2 客户端的被动指纹识别(Akamai 白皮书,Black Hat EU 2017) 原文标题:
内容概要总结
本文是 Akamai 威胁研究团队在 Black Hat EU 2017 发布的白皮书《Passive Fingerprinting of HTTP/2 Clients》(作者 Ory Segal、Aharon Fridman、Elad Shuster)。研究基于从 Akamai 边缘服务器收集的 1000 多万个 HTTP/2 连接,发现客户端在 SETTINGS 帧、WINDOW_UPDATE 帧和 PRIORITY 帧上存在一致差异,可据此被动指纹识别。文章提出指纹格式 S[;]|WU|P[,]#(并扩展 PS[,] 表示伪头部顺序),给出 SETTINGS 参数(0x1 HEADER_TABLE_SIZE、0x2 ENABLE_PUSH、0x3 MAX_CONCURRENT_STREAMS、0x4 INITIAL_WINDOW_SIZE、0x5 MAX_FRAME_SIZE、0x6 MAX_HEADER_LIST_SIZE)与完整指纹示例(如 Chrome:1:65536;3:1000;4:6291456|15663105|0;Firefox 53:1:65536;4:131072;5:16384|12517377|3:0:0:201,...|m,p,a,s),并列出 Chrome/Firefox/Safari/Curl/Go/Jetty 的伪头部顺序差异。用例包括检测伪造 User-Agent、检测匿名代理/VPN(跨层不一致:TCP 显示 Windows、TLS/HTTP2 显示 Mac Chrome)。结论指出 HTTP/2 指纹本身不足以追踪用户,但可暴露实现类型、厂商、OS 版本。
翻译内容
原文内容(English)
AKAMAI 白皮书
HTTP/2 客户端的被动指纹识别(Passive Fingerprinting of HTTP/2 Clients)
Ory Segal,威胁研究高级总监,Akamai
Aharon Fridman,高级安全研究员,Akamai
Elad Shuster,安全数据分析师,Akamai
1.0 概述:HTTP/2
HTTP/2 是 HTTP 协议的第二大版本。它通过引入一个完整的二进制协议改变了 HTTP "在线上"(on the wire)的传输方式,该协议由 TCP 连接、流(streams)和帧(frames)构成,而不是一个纯文本协议。从 HTTP/1.x 到 HTTP/2 这种根本性的变化意味着客户端与服务器端的实现必须引入全新的代码以支持新的 HTTP/2 特性。这在协议实现中引入了细微差别(nuances),反过来可能被用来被动地指纹识别 Web 客户端。
HTTP/2 实现差异的一个例子可以在连接初始化期间于 SETTINGS 帧内发送的配置参数中找到。这些特性描述了发送方对等端的特征,可能被用于客户端指纹识别。
下表展示了所选 HTTP/2 客户端之间特性的一些细微差别与差异:
HTTP/2 配置参数示例
| User-Agent | MAX CONCURRENT STREAMS | MAX HEADER LIST SIZE | MAX HEADER TABLE SIZE | MAX FRAME SIZE | INITIAL WINDOW SIZE | ENABLE PUSH |
|---|---|---|---|---|---|---|
| Mozilla/5.0 (Android 6.0; Mobile; rv:52.0) Gecko/52.0 Firefox/52.0 | [] [1024'] | [4096'] [] | [] [] | [16384'] [16384'] | [32768'] [32768'] | [] [] |
| Mozilla/5.0 (Android 6.0.1; Tablet; rv:47.0) Gecko/47.0 Firefox/47.0 | [100'] [4096'] | [] [131072'] | [] [16384'] | [] [163840'] | [10485760'] [0'] | [] [] |
| Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; McAfee) | ||||||
| Mozilla/5.0 (Linux; Android 7.1; Pixel XL... |
HTTP/2 的 RFC(IETF 请求注释文档)在第 10.8 节 —— 隐私考量(Privacy Considerations)中提到了被动指纹识别的可能性:
"HTTP/2 的若干特性为观察者提供了将单个客户端或服务器的动作随时间关联起来的机会。这些包括设置(settings)的值、流控窗口的管理方式、为流分配优先级的方式、对刺激做出反应的时机,以及对任何由设置控制的特性的处理。"
根据这个 HTTP/2 采用站点,截至 2016 年 11 月 16 日,约有 241,000 个域名宣布支持 HTTP/2。支持者包括 Google、Amazon、Blogspot、Wikipedia 和 WordPress。
由于这是一项新采用的技术,相比 HTTP/1.x 的实现和编程库数量,已知的 HTTP/2 服务器与客户端实现数量相当少。HTTP/2 实现的完整列表可在 HTTP 工作组专门的 HTTP/2 网站上找到。Akamai 是最早实现 HTTP/2 的公司之一,允许每个客户端通过 HTTP/2 与 Akamai 网络通信。
本白皮书涵盖了 Akamai 威胁研究团队对基于独特实现特性被动指纹识别 HTTP/2 客户端可能性的调查。本文还提出了被动 HTTP/2 指纹的一种格式,以及若干属于常见客户端与实现的独特指纹示例。
2.0 被动客户端指纹识别
被动客户端指纹识别是指被动地收集来自正在联网的客户端或服务器的属性。属性可以从传输层、会话层或应用层收集(例如 TCP 属性、TLS 能力或 HTTP 实现特征)。这些属性可用于推断关于客户端的信息,例如操作系统(类型和版本)、系统运行时间,或者在某些情况下是浏览器类型。此外,客户端的被动指纹可用于为客户端在线身份增加唯一性/熵,特别是在使用多层设备指纹识别方法时。目前,被动指纹识别 Web 客户端有三种已知且常用的方法:
- TCP/IP 指纹 —— 在 p0f 库文档中有详细描述
- TLS 指纹 —— 如以下论文所述
- HTTP 指纹 —— 在 p0f 库文档中有详细描述
3.0 研究数据语料库
本研究的数据收集自 Akamai 的边缘服务器,它们每天规律地处理数百万个 HTTP/2 请求。为研究目的,应用了细粒度日志级别,以记录 HTTP/2 帧和流的完整细节。
本研究基于从 Akamai 网络多个边缘服务器收集的 1000 多万个 HTTP/2 连接。研究数据集经过匿名化处理,仅包含关于 HTTP "User-Agent" 头部的信息以及 HTTP/2 记录的事件和属性。
示例数据:
HTTP/2 指纹特征
第一步是识别 HTTP/2 协议中可能存在的指纹熵来源(如果有的话)。在数据中搜索协议中不同客户端表现出可一致、可唯一地用于指纹识别目的的行为的流或消息。
数据分析在以下协议流中发现了一致的变化:
- SETTINGS 帧
- WINDOW_UPDATE 帧
- PRIORITY 帧
SETTINGS 帧
在任何数据交换之前,HTTP/2 的 SETTINGS 帧在初始连接阶段会从客户端发往服务器、并从服务器发往客户端。该帧由 RFC 7540 定义如下:
"SETTINGS 帧(type=0x4)传达影响端点通信方式的配置参数,例如对等端行为的偏好和约束。SETTINGS 帧也用于确认对这些参数的接收。单个地,一个 SETTINGS 参数也可称为一个 "setting"。
SETTINGS 参数不是协商出来的;它们描述发送方对等端的特征,供接收方对等端使用。每个对等端可以通告同一参数的不同值。例如,客户端可能设置一个较高的初始流控窗口,而服务器可能设置一个较低的值以节省资源。
SETTINGS 帧必须在连接开始时由两个端点都发送,并可以在连接生命周期内由任一端点在任何其他时间发送。实现必须支持本规范定义的所有参数。"
RFC 7540 定义了以下 SETTINGS 参数:
| 参数名 | 作用域 |
|---|---|
| SETTINGS_HEADER_TABLE_SIZE (0x1) | 允许发送方告知远端端点用于解码头部块的头部压缩表的最大大小,以八位字节计。 |
| SETTINGS_ENABLE_PUSH (0x2) | 该设置可用于禁用服务器推送(第 8.2 节)。 |
| SETTINGS_MAX_CONCURRENT_STREAMS (0x3) | 指示发送方将允许的最大并发流数量。 |
| SETTINGS_INITIAL_WINDOW_SIZE (0x4) | 指示发送方用于流级流控的初始窗口大小(以八位字节计)。初始值为 216-1(65,535)个八位字节。 |
| SETTINGS_MAX_FRAME_SIZE (0x5) | 指示发送方愿意接收的最大帧负载大小,以八位字节计。 |
| SETTINGS_MAX_HEADER_LIST_SIZE (0x6) | 该建议性设置告知对等端发送方准备接受的最大头部列表大小,以八位字节计。 |
我们研究了从客户端发往服务器的 SETTINGS 帧,发现不同客户端在以下方面存在差异:
- 它们选择发送的 SETTINGS 参数
- SETTINGS 参数发送的顺序
- 它们为 SETTINGS 参数设置的值
WINDOW_UPDATE 帧
WINDOW_UPDATE 帧用于通知另一端点窗口大小有所增加。RFC 7540 对其定义如下:
"WINDOW_UPDATE 帧(type=0x8)用于实现流控;概述见第 5.2 节……当一条 HTTP/2 连接首次建立时,新流以 65,535 个八位字节的初始流控窗口大小创建。连接流控窗口也是 65,535 个八位字节。两个端点都可以通过在构成连接前言的 SETTINGS 帧中包含 SETTINGS_INITIAL_WINDOW_SIZE 的值来调整新流的初始窗口大小。连接流控窗口只能使用 WINDOW_UPDATE 帧来改变。"
我们观察到几乎所有连接的客户端都在 SETTINGS 帧之后发送 WINDOW_UPDATE 帧。我们发现,WINDOW_UPDATE 帧中的增量值在不同客户端之间保持一致性的差异,这是不同 HTTP/2 客户端实现的结果。
保留流的 PRIORITY 流
PRIORITY 帧用于设置任一给定流的优先级。RFC 对其定义如下:
"PRIORITY 帧(type=0x2)指定发送方建议的流优先级(第 5.3 节)。它可以在任何流状态下发送,包括空闲或已关闭的流……PRIORITY 帧可以在处于任何状态的流上发送,尽管它不能在构成单个头部块的连续帧之间发送(第 4.3 节)。注意,该帧可能在处理或帧发送已经完成后才到达,这将导致它对所标识的流没有影响。对于处于 '半关闭(远端)' 或 '已关闭' 状态的流,该帧只能影响所标识的流及其依赖流的处理;它不影响该流上的帧传输。"
我们观察到,某些客户端在连接阶段之后立即向服务器发送若干个 PRIORITY 帧,全都针对尚未打开的流。我们可以把已打开的流标识符作为指纹的一部分。例如,Firefox 浏览器倾向于表现出这种行为。查看 Firefox 的 HTTP/2 实现代码 Http2Session.cpp,我们发现了以下相关注释:
// The Hello is comprised of
// 1] 24 octets of magic, which are designed to
// flush out silent but broken intermediaries
// 2] a settings frame which sets a small flow control window for pushes
// 3] a window update frame which creates a large session flow control window
// 4] 5 priority frames for streams which will never be opened with headers
// these streams (3, 5, 7, 9, b) build a dependency tree that all other
// streams will be direct leaves of.
图 2:保留流的 PRIORITY 流示例
除流标识符之外,PRIORITY 帧还包含以下元素:
- 一个单比特标志,指示流依赖是否为独占
- 该流所依赖的流的 31 位流标识符
- 分配给该流的权重,RFC 将其定义为一个无符号 8 位整数,表示该流的优先级权重(值在 1 到 256 之间)
这些信息也可以用于指纹。
4.0 被动 HTTP/2 指纹 —— 建议格式
综合上述指纹识别因素,我们建议以下 HTTP/2 指纹格式:
S[;]|WU|P[,]#
其中 S[...] 代表一个 SETTINGS 参数及其以 Key:Value 形式表示的值。多个设置按照它们出现的顺序用分号(;)拼接。
WU 代表 WINDOW_UPDATE 增量大小 —— 如果该帧不存在则为 `00'。
P[,] = 一个元组,以以下格式表示流优先级信息:
StreamID:Exclusivity_Bit:Dependant_StreamID:Weight
多个优先级帧用逗号(,)拼接。如果该特性不存在,该值应为 `0'。
指纹示例:
让我们看看上面图 2 中的流。我们看到 Web 服务器从客户端收到了一个 SETTINGS 帧,参数如下:
| 参数名 | 参数值 |
|---|---|
| SETTINGS_HEADER_TABLE_SIZE (0x1) | 65536 |
| SETTINGS_INITIAL_WINDOW_SIZE (0x4) | 131072 |
| SETTINGS_MAX_FRAME_SIZE (0x5) | 16384 |
因此,设置指纹将记为:1:65536;4:131072;5:16384。
接下来发送了一个窗口更新,值为 12517377,因此 WU = 12517377。
至于 P[,],客户端发送了以下五个优先级帧:
| Stream ID | Exclusivity Bit | Dependent Stream ID | Weight |
|---|---|---|---|
| 3 | 0 | 0 | 201 |
| 5 | 0 | 0 | 101 |
| 7 | 0 | 0 | 1 |
| 9 | 0 | 7 | 1 |
| 11 | 0 | 3 | 1 |
由此得到的 HTTP/2 指纹将是:
1:65536;4:131072;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1
常见 HTTP/2 实现与客户端的示例指纹
以下是若干示例 HTTP/2 指纹,展示 HTTP/2 实现可以多么独特,以及它们的指纹彼此之间如何不同:
示例 1:Mac OS X 上的 Chrome 浏览器
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
HTTP/2 指纹:
1:65536;3:1000;4:6291456|15663105|0
示例 2:Windows 10 上的 Chrome 浏览器(与示例 1 中的 Chrome 相同)
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
HTTP/2 指纹:
1:65536;3:1000;4:6291456|15663105|0
示例 3:Windows 10 上的 Microsoft Edge 浏览器
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393
HTTP/2 指纹:
3:1024;4:10485760|10420225|0
示例 4:OkHttp(库)客户端(http://square.github.io/okhttp/)
User-Agent: okhttp/3.6.0
HTTP/2 指纹:
4:16777216|16711681|0
示例 5:Mac OS X 10.11 上的 Firefox 53.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:53.0) Gecko/20100101 Firefox/53.0
HTTP/2 指纹:
1:65536;4:131072;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1
示例 6:Firefox 53.0 Android 移动版
User-Agent: Mozilla/5.0 (Android 7.1.2; Mobile; rv:53.0) Gecko/53.0 Firefox/53.0
HTTP/2 指纹:
1:4096;4:32768;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1
示例 7:基于 Go 的客户端
User-Agent: Go-http-client/2.0
HTTP/2 指纹:
2:0;4:4194304;6:10485760|1073741824|0
示例 8:Curl/7.54.0
User-Agent: Curl/7.54.0
HTTP/2 指纹:
3:100;4:1073741824;2:0|1073676289|0
示例 9:nghttp2 CLI 客户端
User-Agent: nghttp2/1.22.0
HTTP/2 指纹:
3:100;4:65535|00|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1
请求伪头部字段顺序
HTTP/2 中的伪头部字段(Pseudo-header fields)由 RFC 定义如下:
"虽然 HTTP/1.x 使用消息起始行(见 [RFC7230] 第 3.1 节)来传达目标 URI、请求方法和响应状态码,但 HTTP/2 为此使用以 `:' 字符(ASCII 0x3a)开头的特殊伪头部字段。
伪头部字段不是 HTTP 头部字段。端点不得生成本文档所定义之外的其他伪头部字段。"
"为 HTTP/2 请求定义了以下伪头部字段:
- ":method" 伪头部字段包含 HTTP 方法([RFC7231] 第 4 节)。
- ":scheme" 伪头部字段包含目标 URI 的方案部分([RFC3986] 第 3.1 节)。":scheme" 不限于 "http" 和 "https" 方案的 URI。代理或网关可以转换非 HTTP 方案的请求,从而能够用 HTTP 与非 HTTP 服务交互。
- ":authority" 伪头部字段包含目标 URI 的 authority 部分([RFC3986] 第 3.2 节)。对于 "http" 或 "https" 方案的 URI,authority 不得包含已废弃的 "userinfo" 子组件……
- ":path" 伪头部字段包含目标 URI 的路径和查询部分("path-absolute" 产生式,以及可选的?' 字符后跟 "query" 产生式(见 [RFC3986] 第 3.3 和 3.4 节))。星号形式的请求在 ":path" 伪头部字段中包含值*'。"
我们注意到,请求伪头部以不同的顺序出现,这取决于客户端实现。例如,Chrome 浏览器按以下顺序发出伪头部:
:method: GET
:authority: http2.some.site
:scheme: https
:path: /
而 Firefox 浏览器按如下方式发送它们:
:method: GET
:path: /
:authority: http2.some.site
:scheme: https
下表展示了若干常见 HTTP/2 实现中伪头部顺序的差异:
| 客户端 / 实现 | 伪头部名称顺序 |
|---|---|
| Google Chrome (58.0.3029.110 on Mac OS X) | :method, :authority, :scheme, :path |
| Firefox v53.0 (Mac OS X) | :method, :path, :authority, :scheme |
| Safari v10.1 (Mac OS X) | :method, :scheme, :path, :authority |
| Curl v7.54.0 (Mac OS X) | :method, :path, :scheme, :authority |
| Go-http-client v2.0 | :authority, :method, :path, :scheme |
| Jetty HTTP2 Client v9.3.4.v20151007 | :scheme, :method, :authority, :path |
查看 Chrome 的源代码,我们可以看到这个伪头部顺序是在哪里定义的:
void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info,
const HttpRequestHeaders& request_headers,
bool direct,
SpdyHeaderBlock* headers) {
(*headers)[":method"] = info.method;
if (info.method == "CONNECT") {
(*headers)[":authority"] = GetHostAndPort(info.url);
} else {
(*headers)[":authority"] = GetHostAndOptionalPort(info.url);
(*headers)[":scheme"] = info.url.scheme();
(*headers)[":path"] = info.url.PathForRequest();
}
Class SpdyHeaderBlock 的源代码包含以下注释,提到伪头部顺序在插入期间被保持:
// This class provides a key-value map that can be used to store SPDY header.
// names and values. This data structure preserves insertion order.
该类本身使用一个 C++ std::list 容器,它保持插入顺序。
伪头部顺序可以按以下方式编码进所建议的 HTTP/2 客户端指纹:
S[;]|WU|P[,]#|PS[,]
其中 PS 可以取以下值之一:
- m(:method)
- p(:path)
- a(:authority)
- s(:scheme)
例如:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:53.0) Gecko/20100101 Firefox/53.0
HTTP/2 指纹:
1:65536;4:131072;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1|m,p,a,s
5.0 被动 HTTP/2 客户端指纹识别的用例
检测伪造的 User-Agent
HTTP/2 指纹的唯一性仅受客户端对协议的实现影响,不受具体用户环境因素的影响。HTTP/2 指纹本身并不提供足够的熵来指纹识别或追踪特定用户。然而,它确实会暴露关于特定 HTTP/2 实现类型的信息,并在许多情况下揭示关于客户端厂商、操作系统类型和版本的信息。
这些信息可用于检测伪造其 User-Agent 字符串的客户端。例如,一个用 "Go" 编程语言编写的自动化网页抓取工具,可能会发送一个伪造的 Chrome 浏览器 User-Agent 字符串,以规避反自动化保护机制。在这些情况下,检测伪造企图并推断出真实的 HTTP/2 客户端类型会相当简单。
然而,应用程序也可以利用被动 HTTP/2 指纹来获得对客户端所声称 User-Agent 字符串的信心和保证。
匿名代理/VPN 检测
Web 客户端通过匿名化代理连接以掩盖其真实身份或地理位置,这是相当常见的。在某些情况下,某些 Web 应用程序和在线服务会试图检测某个请求是否通过匿名化中间设备(如代理或 VPN)路由。通过关联被动 TCP、TLS 和 HTTP/2 指纹中(不一致的)信息,应用程序可以被动地推断出客户端是通过代理路由流量的。
设想一个客户端在 Mac OS X 上运行 Chrome 浏览器,通过一个运行在 Windows 10 机器上的中间匿名化代理路由 HTTP/2 流量。由于该匿名化代理不终结 TLS,也不终结并重写 HTTP/2 流量,TCP 指纹将显示是一台 Windows 10 机器连接到 Web 服务器,而 TLS 和 HTTP/2 指纹将暴露客户端实际上是在 Mac OS X 上运行 Chrome 浏览器这一事实。
虽然这一信息本可以仅凭 TCP 与 TLS 指纹之间的差异推断出来,但 HTTP/2 指纹有助于提高检测的整体信心。此外,虽然某些 Web 客户端允许用户以自定义 TLS 设置启动它们,但我们的研究表明,许多 HTTP/2 客户端不支持修改基本的 HTTP/2 实现细节,例如 SETTINGS 帧的值,或伪头部的名称顺序。
应当指出,虽然 HTTP/2 协议并不强制使用 TLS 加密,但某些实现只支持基于 TLS 的 HTTP/2,且目前没有浏览器支持基于未加密连接的 HTTP/2。这意味着被动 TLS 指纹几乎总是能与本文所述的 HTTP/2 特性一起被收集,从而形成更准确的指纹。
6.0 结论
HTTP/2 被认为是互联网的未来。它是 HTTP 协议的第二大版本,由 IETF 的 HTTP 工作组开发。HTTP/2 是一个完全多路复用的二进制协议。因此它可以使用一条连接实现并行化。该协议使用头部压缩以减少开销,还允许服务器主动将响应"推送"进客户端缓存。
HTTP/2 标准于 2015 年 2 月正式批准,已得到大多数 Web 浏览器和服务器的支持。
从 HTTP/1.x 到 HTTP/2 的巨大而根本性的变化意味着客户端与服务器端的实现需要引入全新的代码以支持新的 HTTP/2 特性。
本文展示了这些新实现如何产生细微差别,从而将 HTTP/2 客户端彼此区分开来。此外,我们还展示了如何利用这些独特的实现特性来被动地指纹识别 Web 客户端。我们的研究表明,被动 HTTP/2 客户端指纹识别可用于推断客户端实现的真实细节——例如,浏览器类型、版本,有时甚至是操作系统。该技术可用于更好地检测伪造或不报告其 User-Agent 字符串的客户端,同时提高对合法客户端所报告 User-Agent 字符串的信心。此外,HTTP/2 指纹可用于增强对匿名代理和 VPN 的检测,这些是互联网上一些用户所使用的。
作为全球最大、最受信任的云交付平台,Akamai 让其客户更容易在任何设备、任何时间、任何地点提供最佳和最安全的数字体验。Akamai 大规模分布式平台在规模上无与伦比,在 130 个国家拥有超过 200,000 台服务器,为客户提供卓越的性能和威胁防护。Akamai 的 Web 与移动性能、云安全、企业访问和视频交付解决方案组合由卓越的客户服务和 24/7 监控支持。要了解顶级金融机构、电子商务领导者、媒体与娱乐提供商以及政府组织为何信任 Akamai,请访问 www.akamai.com、blogs.akamai.com,或在 Twitter 上关注 @Akamai。你可以在 www.akamai.com/locations 找到我们的全球联系信息。发布于 06/17。
AKAMAI WHITE PAPER
Passive Fingerprinting
of HTTP/2 Clients
Ory Segal, Sr. Director, Threat Research, Akamai
Aharon Fridman, Sr. Security Researcher, Akamai
Elad Shuster, Security Data Analyst, Akamai
Passive Fingerprinting of HTTP/2 Clients 1
1.0 OVERVIEW: HTTP/2
HTTP/2 is the second major version of the HTTP protocol. It changes the way HTTP is transferred "on
the wire" by introducing a full binary protocol that is made up of TCP connections, streams, and
frames, rather than a plain-text protocol. Such a fundamental change from HTTP/1.x to HTTP/2
means that client-side and server-side implementations have to incorporate completely new code in
order to support new HTTP/2 features. This introduces nuances in protocol implementations, which,
in return, might be used to passively fingerprint web clients.
An example of HTTP/2 implementation nuances can be found in configuration parameters that are sent within the SET-
TINGS frames during connection initialization. These features describe the characteristics of the sending peer, and may be
used for client fingerprinting.
The table below demonstrates some of the nuances and differences in features between selected HTTP/2 clients:
Examples for HTTP/2 Configuration Parameters
User-Agent MAX MAX MAX INITIAL ENABLE
HEADER HEADER FRAME WINDOW PUSH
LIST SIZE
CONCURRENT SIZE SIZE
TABLE SIZE
STREAMS
Mozilla/5.0 (Android 6.0; [] [4096'] [] [16384'] [`32768'] []
Mobile; rv:52.0) Gecko/52.0 []
Firefox/52.0 [1024'] [] [] [16384'] [`32768'] []
Mozilla/5.0 (Android 6.0.1; [100'] [] [] [] [10485760'] []
Tablet; rv:47.0) Gecko/47.0
Firefox/47.0 [4096'] [131072'] [16384'] [163840'] [`0']
Mozilla/4.0 (compatible;
MSIE 7.0; Windows NT
10.0; WOW64; Trident/7.0;
.NET4.0C; .NET4.0E; .NET
CLR 2.0.50727; .NET CLR
3.0.30729; .NET CLR
3.5.30729; McAfee)
Mozilla/5.0 (Linux; Android
7.1; Pixel XL...
The HTTP/2 RFC (the IETF Request for Comments document) mentions the possibility of passive fingerprinting in Section
10.8 -- Privacy Considerations:
"Several characteristics of HTTP/2 provide an observer an opportunity to correlate actions of a single client or
server over time. These include the value of settings, the manner in which flow-control windows are managed,
the way priorities are allocated to streams, the timing of reactions to stimulus, and the handling of any features
that are controlled by settings."
Passive Fingerprinting of HTTP/2 Clients 2
According to this HTTP/2 Adoption site, there are approximately 241,000 domains that announced support for HTTP/2 as
of November 16, 2016. Supporters include Google, Amazon, Blogspot, Wikipedia, and WordPress.
Since this is a newly adopted technology, the number of known server and client implementations for HTTP/2 is rather low
compared to the number of HTTP/1.x implementations and programming libraries. The full list of HTTP/2 implementations
can be found in the HTTP Working Group dedicated HTTP/2 website. Akamai has been among the first to implement
HTTP/2, allowing each client to communicate with the Akamai network over HTTP/2.
This white paper covers Akamai's Threat Research team's investigation of the possibility of passively fingerprinting HTTP/2
clients based on unique implementation features. The paper also proposes a format for passive HTTP/2 fingerprints, as
well as a few examples of unique fingerprints belonging to common clients and implementations.
2.0 PASSIVE CLIENT FINGERPRINTING
Passive client fingerprinting refers to the passive collection of attributes from a network-connecting client or server.
Attributes may be collected from the transport, session, or application layer (e.g. TCP properties, TLS capabilities, or HTTP
implementation characteristics). These attributes can be used to deduce information about the client, such as operating
system (type and version), system up-time, or, in some cases, browser type. In addition, a client's passive fingerprint can be
used to add uniqueness/entropy to the client's online identity, specifically when using a multi-layered device fingerprinting
approach. Currently, there are three known and commonly used approaches to passively fingerprint web clients:
- TCP/IP Fingerprint -- described in detail in the p0f library documentation
- TLS fingerprint -- as described in the following paper
- HTTP Fingerprint -- described in detail in the p0f library documentation
3.0 RESEARCH DATA CORPUS
The data for this research was collected from Akamai's Edge servers, which regularly handle millions of HTTP/2 requests
daily. For research purposes, granular logging levels were applied in order to log the full details of the HTTP/2 frames and
streams.
This research is based on more than 10 million HTTP/2 connections collected from multiple Edge servers across the Akamai
network. The data set for the research was anonymized and only contained information about the HTTP "User-Agent"
header value and the HTTP/2 logged events and attributes.
Example data:
Passive Fingerprinting of HTTP/2 Clients 3
HTTP/2 Fingerprint Features
The first step was to identify possible sources of fingerprint entropy, if any, in the HTTP/2 protocol. The data was searched
for flows or messages in the protocol where different clients exposed a consistent unique behavior that could be used for
fingerprinting purposes.
The data analysis yielded a consistent variation in the following protocol flows:
- SETTINGS frame
- WINDOW_UPDATE frame
- PRIORITY frame
SETTINGS Frame
Before any data is exchanged, the HTTP/2 SETTINGS frame is sent from both client to server and server to client during the
initial connection phase. The frame is defined by RFC 7540 as follows:
"The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints communicate,
such as preferences and constraints on peer behavior. The SETTINGS frame is also used to acknowledge the
receipt of those parameters. Individually, a SETTINGS parameter can also be referred to as a "setting."
SETTINGS parameters are not negotiated; they describe characteristics of the sending peer, which are used
by the receiving peer. Different values for the same parameter can be advertised by each peer. For example,
a client might set a high initial flow-control window, whereas a server might set a lower value to conserve
resources.
A SETTINGS frame MUST be sent by both endpoints at the start of a connection and MAY be sent at any
other time by either endpoint over the lifetime of the connection. Implementations MUST support all of the
parameters defined by this specification."
The following SETTINGS parameters are defined by RFC 7540:
Parameter Name Scope
SETTINGS_HEADER_TABLE_SIZE (0x1) Allows the sender to inform the remote endpoint of the
maximum size of the header compression table used to
decode header blocks, in octets.
SETTINGS_ENABLE_PUSH (0x2) This setting can be used to disable server push (Section 8.2).
SETTINGS_MAX_CONCURRENT_STREAMS (0x3) Indicates the maximum number of concurrent streams that
the sender will allow.
SETTINGS_INITIAL_WINDOW_SIZE (0x4) Indicates the sender's initial window size (in octets) for
stream-level flow control. The initial value is 216-1 (65,535)
octets.
SETTINGS_MAX_FRAME_SIZE (0x5) Indicates the size of the largest frame payload that the
sender is willing to receive, in octets.
SETTINGS_MAX_HEADER_LIST_SIZE (0x6) This advisory setting informs a peer of the maximum size of
header list that the sender is prepared to accept, in octets.
We looked into the SETTINGS frames sent from client to server, and we found that different clients differ in:
The SETTINGS parameters they choose to send
The order by which the SETTINGS parameters are sent
The values they set for the SETTINGS parameters
Passive Fingerprinting of HTTP/2 Clients 4
WINDOW_UPDATE Frame
The WINDOW_UPDATE frame is sent in order to notify the other endpoint of an increment in the window size. It is
defined by RFC 7540 as follows:
"The WINDOW_UPDATE frame (type=0x8) is used to implement flow control; see Section 5.2 for an overview...
When an HTTP/2 connection is first established, new streams are created with an initial flow-control window
size of 65,535 octets. The connection flow-control window is also 65,535 octets. Both endpoints can adjust the
initial window size for new streams by including a value for SETTINGS_INITIAL_WINDOW_SIZE in the SETTINGS
frame that forms part of the connection preface. The connection flow-control window can only be changed
using WINDOW_UPDATE frames."
We observed that almost all the connecting clients send the WINDOW_UPDATE frame after the SETTINGS frame. We
discovered that the increment value in the WINDOW_UPDATE frame consistently differs from client to client, as a result of
different HTTP/2 client implementations.
PRIORITY for Reserved Streams Flow
The PRIORITY frame is sent in order to set a priority of any given stream. It is defined by the RFC as follows:
"The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream (Section 5.3). It can be sent in
any stream state, including idle or closed streams....The PRIORITY frame can be sent on a stream in any state,
though it cannot be sent between consecutive frames that comprise a single header block (Section 4.3). Note
that this frame could arrive after processing or frame sending has completed, which would cause it to have
no effect on the identified stream. For a stream that is in the "half-closed (remote)" or "closed" state, this
frame can only affect processing of the identified stream and its dependent streams; it does not affect frame
transmission on that stream."
We observed that certain clients, right after the connection phase, send the server several PRIORITY frames, all for
streams that have not been opened yet. We can take the stream identifiers that were opened and use them as a part
of the fingerprint. For example, Firefox browsers tend to demonstrate such a behavior. Looking at the Firefox HTTP/2
implementation code in Http2Session.cpp, we spotted the following relevant comment:
// The Hello is comprised of
// 1] 24 octets of magic, which are designed to
// flush out silent but broken intermediaries
// 2] a settings frame which sets a small flow control window for pushes
// 3] a window update frame which creates a large session flow control window
// 4] 5 priority frames for streams which will never be opened with headers
// these streams (3, 5, 7, 9, b) build a dependency tree that all other
// streams will be direct leaves of.
Image 2: Example
Flow with PRIORITY
for Reserved
Streams
In addition to the stream identifier, the PRIORITY frame also includes the following elements:
A single-bit flag indicating that stream dependency is exclusive
A 31-bit stream identifier for the stream that this stream depends on
The weight assigned to that stream, which is defined by the RFC as an unsigned 8-bit integer representing
a priority weight for the stream (values are between 1 and 256)
This information can also be used in the fingerprint.
Passive Fingerprinting of HTTP/2 Clients 5
4.0 Passive HTTP/2 Fingerprint -- Suggested Format
Combining the above fingerprinting factors, we suggest the following HTTP/2 Fingerprint format:
S[;]|WU|P[,]#
Where S[...] stands for a SETTINGS parameter and its value in the form of Key:Value. Multiple settings are
concatenated using a semicolon (;) according to the order of their appearance.
WU stands for the WINDOW_UPDATE increment size -- `00' if the frame is not present
P[,] = A tuple representing stream priority information in the following format:
StreamID:Exclusivity_Bit:Dependant_StreamID:Weight
Multiple priority frames are concatenated by a comma (,). If this feature does not exist, the value should be `0'.
Fingerprint Example:
Let's look at the flow from Image 2 above. We see the web server received a SETTINGS frame from the client, with the
following parameters:
Parameter Name Parameter Value
SETTINGS_HEADER_TABLE_SIZE (0x1) 65536
SETTINGS_INITIAL_WINDOW_SIZE (0x4) 131072
SETTINGS_MAX_FRAME_SIZE (0x5) 16384
Hence, the settings fingerprint will be denoted as: 1:65536;4:131072;5:16384.
Next, a window update was sent, with a value of 12517377 hence WU = 12517377.
As for P[,], the client sent the following five priority frames:
Stream ID Exclusivity Bit Dependent Stream ID Weight
3 0 0 201
5 0 0 101
7 0 0 1
9 0 7 1
11 0 3 1
The resulting HTTP/2 fingerprint would be:
1:65536;4:131072;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1
Sample Fingerprints for Common HTTP/2 Implementations & Clients
Below are a few sample HTTP/2 fingerprints that demonstrate how unique HTTP/2 implementations can be, and how their
fingerprints differ from one another:
Example 1: Chrome Browser on Mac OS X
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/58.0.3029.96 Safari/537.36
HTTP/2 fingerprint:
1:65536;3:1000;4:6291456|15663105|0
Passive Fingerprinting of HTTP/2 Clients 6
Example 2: Chrome Browser on Windows 10 (Identical to Chrome in Example #1)
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/58.0.3029.96 Safari/537.36
HTTP/2 fingerprint:
1:65536;3:1000;4:6291456|15663105|0
Example 3: Microsoft Edge Browser on Windows 10
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393
HTTP/2 fingerprint:
3:1024;4:10485760|10420225|0
Example 4: OkHttp (library) client (http://square.github.io/okhttp/)
User-Agent: okhttp/3.6.0
HTTP/2 fingerprint:
4:16777216|16711681|0
Example 5: Firefox 53.0 On Mac OS X 10.11
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:53.0) Gecko/20100101 Firefox/53.0
HTTP/2 fingerprint:
1:65536;4:131072;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1
Example 6: Firefox 53.0 Android Mobile
User-Agent: Mozilla/5.0 (Android 7.1.2; Mobile; rv:53.0) Gecko/53.0 Firefox/53.0
HTTP/2 fingerprint:
1:4096;4:32768;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1
Example 7: Go-based client
User-Agent: Go-http-client/2.0
HTTP/2 fingerprint:
2:0;4:4194304;6:10485760|1073741824|0
Example 8: Curl/7.54.0
User-Agent: Curl/7.54.0
HTTP/2 fingerprint:
3:100;4:1073741824;2:0|1073676289|0
Example 9: nghttp2 CLI client
User-Agent: nghttp2/1.22.0
HTTP/2 fingerprint:
3:100;4:65535|00|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1
Passive Fingerprinting of HTTP/2 Clients 7
Request Pseudo-Header Fields Order
Pseudo-header fields in HTTP/2 are defined by the RFC as follows:
"While HTTP/1.x used the message start-line (see [RFC7230], Section 3.1) to convey the target URI, the method
of the request, and the status code for the response, HTTP/2 uses special pseudo-header fields beginning with `:'
character (ASCII 0x3a) for this purpose.
Pseudo-header fields are not HTTP header fields. Endpoints MUST NOT generate pseudo-header fields other
than those defined in this Document."
"The following pseudo-header fields are defined for HTTP/2 requests:
The ":method" pseudo-header field includes the HTTP method ([RFC7231], Section 4).
The ":scheme" pseudo-header field includes the scheme portion of the target URI ([RFC3986], Section 3.1).
":scheme" is not restricted to "http" and "https" schemed URIs. A proxy or gateway can translate requests
for non-HTTP schemes, enabling the use of HTTP to interact with non-HTTP services.
The ":authority" pseudo-header field includes the authority portion of the target URI ([RFC3986],
Section 3.2). The authority MUST NOT include the deprecated "userinfo" subcomponent for "http"
or "https" schemed URIs...
The ":path" pseudo-header field includes the path and query parts of the target URI (the "path-absolute"
production and optionally a `?' character followed by the "query" production (see Sections 3.3 and 3.4 of
[RFC3986]). A request in asterisk form includes the value `*' for the ":path" pseudo-header field."
We noticed that request pseudo-headers appeared in a different order which depends on client implementation.
For example, Chrome browsers issued the pseudo-headers in the following order:
:method: GET
:authority: http2.some.site
:scheme: https
:path: /
While Firefox browsers sent them as follows:
:method: GET
:path: /
:authority: http2.some.site
:scheme: https
The following table demonstrates the difference in pseudo-headers order in several common HTTP/2 implementations:
Client / Implementation Pseudo Headers Name Order
:method, :authority, :scheme, :path
Google Chrome (58.0.3029.110 on Mac OS X) :method, :path, :authority, :scheme
Firefox v53.0 (Mac OS X) :method, :scheme, :path, :authority
Safari v10.1 (Mac OS X) :method, :path, :scheme, :authority
Curl v7.54.0 (Mac OS X) :authority, :method, :path, :scheme
Go-http-client v2.0 :scheme, :method, :authority, :path
Jetty HTTP2 Client v9.3.4.v20151007
Passive Fingerprinting of HTTP/2 Clients 8
Looking at Chrome's source code, we can see where this pseudo-header order is defined:
void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info,
const HttpRequestHeaders& request_headers,
bool direct,
SpdyHeaderBlock* headers) {
(*headers)[":method"] = info.method;
if (info.method == "CONNECT") {
(*headers)[":authority"] = GetHostAndPort(info.url);
} else {
(*headers)[":authority"] = GetHostAndOptionalPort(info.url);
(*headers)[":scheme"] = info.url.scheme();
(*headers)[":path"] = info.url.PathForRequest();
}
The source code for the Class SpdyHeaderBlock includes the following comment, which mentions that pseudo header
order is maintained during insertion:
// This class provides a key-value map that can be used to store SPDY header.
// names and values. This data structure preserves insertion order.
The Class itself uses a C++ std::list container, which preserves insertion order.
Pseudo-header order can be encoded into the suggested HTTP/2 client fingerprint in the following manner:
S[;]|WU|P[,]#|PS[,]
Where PS can have one of the following: values:
m (:method)
p (:path)
a (:authority)
s (:scheme)
For example:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:53.0) Gecko/20100101 Firefox/53.0
HTTP/2 fingerprint:
1:65536;4:131072;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1|m,p,a,s
5.0 Use Cases for Passive HTTP/2 Client Fingerprinting
Spoofed User-Agent Detection
HTTP/2 fingerprint uniqueness is only influenced by the client's implementation of the protocol and is not affected by
specific user environment factors. The HTTP/2 fingerprint, by itself, does not provide enough entropy to fingerprint or
track specific users. However, it does expose information about the type of specific HTTP/2 implementation and, in many
cases, reveals information about the client's vendor, operating system type, and version.
Passive Fingerprinting of HTTP/2 Clients 9
This information may be leveraged to detect clients that spoof their User-Agent string. For example, an automated web
scraping tool, written using the "Go" programming language, may send a spoofed Chrome web browser User-Agent
string in order to evade anti-automation protection mechanisms. In these cases, it would be quite simple to detect a
spoofing attempt and deduce the real HTTP/2 client type.
However, applications could also use the passive HTTP/2 fingerprint to gain confidence and assurance about a client's
stated User-Agent string.
Anonymous Proxy/VPN Detection
It is quite common to see web clients connecting through anonymizing proxies in order to mask their true identity or
geolocation. In some cases, certain web applications and online services try to detect whether or not a request was routed
through an anonymizing intermediary device such as Proxy or VPN. By correlating (discrepant) information from the
passive TCP, TLS, and HTTP/2 fingerprints, an application can passively deduce that the client was routing traffic through a
proxy.
Imagine a client running a Chrome browser on Mac OS X, routing HTTP/2 traffic through an intermediary anonymizing
proxy that is running on a Windows 10 machine. Since the anonymizing proxy does not terminate TLS and does not
terminate and rewrite HTTP/2 traffic, the TCP fingerprint will show that a Windows 10 machine was connecting to the
web server, while the TLS and HTTP/2 fingerprints will expose the fact that the client is actually running a Chrome browser
on Mac OS X.
While this information could have been deduced solely on the discrepancy between the TCP and TLS fingerprints, the
HTTP/2 fingerprint contributes to the overall confidence of the detection. Additionally, while some web clients enable
a user to launch them with customized TLS settings, our research shows that many HTTP/2 clients don't support
modification of basic HTTP/2 implementation details such as the SETTINGS frame values, or the pseudo-headers name
order.
It should be noted that while the HTTP/2 protocol does not mandate the use of TLS encryption, some implementations
only support HTTP/2 over TLS, and currently no browser supports HTTP/2 over unencrypted connections. This means
that passive TLS fingerprints can almost always be collected in conjunction with the HTTP/2 features mentioned in this
document to form a more accurate fingerprint.
6.0 CONCLUSION
HTTP/2 is considered the future of the Internet. It is the second major version of the HTTP protocol, which was developed
by the IETF's HTTP Working Group. HTTP/2 is a binary protocol that is fully multiplexed. It can therefore use one
connection for parallelism. The protocol uses header compression to reduce overhead and also allows servers to "push"
responses proactively into client caches.
The HTTP/2 standard was officially approved in February 2015, and is already supported by most web browsers and servers.
The dramatic and fundamental changes from HTTP/1.x to HTTP/2 mean that client-side and server-side implementations
need to incorporate completely new code in order to support new HTTP/2 features.
This paper demonstrates how these new implementations create small nuances, which differentiate HTTP/2 clients from
one another. In addition, we have shown how these unique implementation features can be leveraged to passively
fingerprint web clients. Our research shows that passive HTTP/2 client fingerprinting can be used to deduce the true
details about the client's implementation -- for example, browser type, version, and sometimes even the operating system.
This technique can be used to better detect clients that spoof or don't report their User-Agent string, and at the same time
increase confidence in User-Agent strings reported by legitimate clients. Moreover, HTTP/2 fingerprints can be used to
enhance anonymous proxies and VPNs, which are used by some users on the Internet.
As the world's largest and most trusted cloud delivery platform, Akamai makes it easier for its customers to provide the best and most secure digital experiences on any device,
anytime, anywhere. Akamai's massively distributed platform is unparalleled in scale with over 200,000 servers across 130 countries, giving customers superior performance and
threat protection. Akamai's portfolio of web and mobile performance, cloud security, enterprise access, and video delivery solutions are supported by exceptional customer service
and 24/7 monitoring. To learn why the top financial institutions, e-commerce leaders, media & entertainment providers, and government organizations trust Akamai please visit
www.akamai.com, blogs.akamai.com, or @Akamai on Twitter. You can find our global contact information at www.akamai.com/locations. Published 06/17.