关于HTTP请求走私的小记

0x00 写在前面

之前一次线上赛,遇到一道Web题,涉及了HTTP请求走私。由于之前未学习过,从而我展开了HTTP请求走私的学习之旅。

0x01 HTTP请求走私是什么

HTTP请求走私是一种干扰网站处理从一个或多个用户接收的HTTP请求序列的方式的技术。使攻击者可以绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。

0x02 为什么会产生HTTP请求走私

请求走私漏洞成因

前端服务器(CDN)和后端服务器接收数据不同步,引起对客户端传入的数据理解不一致,从而导致漏洞的产生。

大多数HTTP请求走私漏洞的出现是因为HTTP规范提供了两种不同的方法来指定请求的结束位置:Content-Length标头和Transfer-Encoding标头。
同时使用两种不同的方法时,Content-Length无效。当使用多个服务器时,对客户端传入的数据理解不一致时,就会出现有些服务器认为Content-Length的长度有效,有些以Transfer-Encoding有效。而一般情况下,反向代理服务器与后端的源站服务器之间,会重用TCP链接。这样超出的长度就会拼接到下一次请求进行请求,从而导致HTTP请求走私漏洞。

RFC2616规范

如果接收的消息同时包含传输编码头字段(Transfer-Encoding)和内容长度头(Content-Length)字段,则必须忽略后者。

由于规范默许可以使用Transfer-EncodingContent-Length处理请求,因此很少有服务器拒绝此类请求。每当我们找到一种方法,将Transfer-Encoding隐藏在服务端的一个chain中时,它将会回退到使用Content-Length去发送请求。

走私攻击实现

当向代理服务器发送一个比较模糊的HTTP请求时,由于两者服务器的实现方式不同,代理服务器可能认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。

扩展:为什么会出现多次请求

这与最为广泛的HTTP 1.1的协议特性——Keep-Alive&Pipeline有关。

HTTP1.0之前的协议设计中,客户端每进行一次HTTP请求,需要同服务器建立一个TCP链接。

而现代的Web页面是由多种资源组成的,要获取一个网页的内容,不仅要请求HTML文档,还有JS、CSS、图片等各种资源,如果按照之前的协议设计,就会导致HTTP服务器的负载开销增大。于是在HTTP1.1中,增加了Keep-AlivePipeline这两个特性。

Keep-Alive:在HTTP请求中增加一个特殊的请求头Connection: Keep-Alive,告诉服务器,接收完这次HTTP请求后,不要关闭TCP链接,后面对相同目标服务器的HTTP请求,重用这一个TCP链接。这样只需要进行一次TCP握手的过程,可以减少服务器的开销,节约资源,还能加快访问速度。这个特性在HTTP1.1中默认开启的。

Pipeline(http管线化):http管线化是一项实现了多个http请求但不需要等待响应就能够写进同一个socket的技术,仅有http1.1规范支持http管线化。在这里,客户端可以像流水线一样发送自己的HTTP请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端。

现在,浏览器默认不启用Pipeline的,但是一般的服务器都提供了对Pipleline的支持。

继续阅读关于HTTP请求走私的小记

Transfer-Encoding 的作用

通过HTTP传送数据时,有些时候并不能事先确定body的长度,因此无法得到Content-Length的值, 就不能在header中指定Content-Length了,造成的最直接的影响就是:接收方无法通过Content-Length得到报文体的长度, 那怎么判断发送方发送完毕了呢?HTTP 1.1协议在header中引入了Transfer-Encoding,当其值为chunked时, 表明采用chunked编码方式来进行报文体的传输

HTTP 1.1中有两个实体头(Entity-Header)直接与编码相关,分别为Content-Encoding和Transfer-Encoding.
先说Content-Encoding, 该头表示实体已经采用了的编码方式.Content-Encoding是请求URL对应实体(Entity)本身的一部分.比如请求URL为http://host/image.png.gz时,可能会得到的Content-Encoding为gzip.Content-Encoding的值是不区分大小写的,目前HTTP1.1标准中已包括的有gzip/compress/deflate/identity等.
与Content-Encoding头对应,HTTP请求中包含了一个Accept-Encoding头,该头用来说明用户代理(User-Agent,一般也就是浏览器)能接受哪些类型的编码. 如果HTTP请求中不存在该头,服务器可以认为用户代理能接受任何编码类型.

接下来重点描述Transfer-Encoding, 该头表示为了达到安全传输或者数据压缩等目的而对实体进行的编码. Transfer-Encoding与Content-Encoding的不同之处在于:
1, Transfer-Encoding只是在传输过程中才有的,并非请求URL对应实体的本身特性.
2, Transfer-Encoding是一个"跳到跳"头,而Content-Encoding是"端到端"头.
该头的用途举例如,请求URL为http://host/abc.txt,服务器发送数据时认为该文件可用gzip方式压缩以节省带宽,接收端看到Transfer-Encoding为gzip首先进行解码然后才能得到请求实体.
此外多个编码可能同时对同一实体使用,所以Transfer-Encoding头中编码顺序相当重要,它代表了解码的顺序过程.同样,Transfer-Encoding的值也是不区分大小写的,目前HTTP1.1标准中已包括的有gzip/compress/deflate/identity/chunked等.
Transfer-Encoding中有一类特定编码:chunked编码.该编码将实体分块传送并逐块标明长度,直到长度为0块表示传输结束, 这在实体长度未知时特别有用(比如由数据库动态产生的数据). HTTP1.1标准规定,只要使用了Transfer-Encoding的地方就必须使用chunked编码,并且chunked必须为最后一层编码.任何HTTP 1.1应用都必须能处理chunked编码.
与Transfer-Encoding对应的请求头为TE,它主要表示请求发起者愿意接收的Transfer-Encoding类型. 如果TE为空或者不存在,则表示唯一能接受的类型为chunked.
其他与Transfer-Encoding相关的头还包括Trailer,它与chunked编码相关,就不细述了.

顾名思义,Content-Length表示传输的实体长度,以字节为单位(在请求方法为HEAD时表示会要发送的长度,但并不实际发送.).Content-Length受Transfer-Encoding影响很大,只要Transfer-Encoding不为identity,则实际传输长度由编码中的chunked决定,Content-Length即使存在也被忽略.

关于HTTP Message Body的长度
在HTTP中有消息体(Message body)和实体(Entity body)之分,简单说来在没有Transfer-Encoding作用时,消息体就是实体,而应用了Transfer-Encoding后,消息体就是编码后的实体,如下:

具体详细的 RFC 7230 说明如下:

参考链接


Transfer-Encoding 的作用

前端万字精华「浏览器简史及其核心原理详解」

今天来聊一聊程序员尤其是前端,离不开的工具「浏览器」。浏览器只要是学习过计算机的小伙伴都不陌生吧?它的主要功能就是向服务器发出请求,在浏览器窗口中展示HTML文档、PDF、图片、视频等网络内容。这些网络资源的位置由用户使用 URI(统一资源标示符)来指定

继续阅读前端万字精华「浏览器简史及其核心原理详解」

Gradle 都做了哪些缓存?

前言

GradleAndroid的构建工具,它的主要目标就是实现快速的编译构建,而这主要就是通过缓存实现的。本文主要介绍Gradle的缓存机制,具体包括以下内容

  1. Gradle缓存机制
  2. Gradle内存缓存
  3. Gradle项目缓存
  4. Gradle本机缓存
  5. Gradle远程缓存

继续阅读Gradle 都做了哪些缓存?

MMKV 高性能的数据存取框架解读

目标

了解MMKV

MMKV的基本应用

MMKV的原理概念

多进程设计思想

性能对比

源码解读

简介

MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。

官方文档:https://github.com/Tencent/MMKV/blob/master/README_CN.md

项目地址:https://github.com/Tencent/MMKV

继续阅读MMKV 高性能的数据存取框架解读

Flutter this and base files have different roots 问题

Flutter项目是能运行的,打开Flutter里面的Android项目才会报下面错误。

报错的项目配置信息如下:

Flutter一开始Android build是没问题的,开发着突然就报这个下面的错误,开始怀疑是不是有什么缓存啥的,然后各种排除都没找到什么原因,后面想着降版本吧,kotlin降了没用,后面尝试最后一个Gradle降版本竟然成功了。

build.gradle文件

参考链接


Flutter快速解析TextField的内部原理

在 Flutter 里 TextField 是一个比较复杂的控件,而在整个 TextField 里嵌套了许多不同实现的控件,它们组成了我们常用的输入框效果,如下图所示是关于 TextField 的主要构成部分,也是本篇主要讲解的内容。

继续阅读Flutter快速解析TextField的内部原理

解决Android Studio Electric Eel | 2022.1.1 Patch 2下removeContentEntry: removed content entry url ‘file://*****‘ still exists after removing的问题

Android Studio Electric Eel | 2022.1.1 Patch 2 中构建项目的时候,出现了

这样的报错。

经过多次尝试,发现直接删除 .idea目录是有效的。

关于该问题详细的讨论可以参考 https://stackoverflow.com/questions/66214555/gradle-sync-failed-removecontententry-removed-content-entry-url-still-ex

参考链接


解决idea下removeContentEntry: removed content entry url ‘file://*****‘ still exists after removing的问题

解决Android Studio Electric Eel | 2022.1.1 Patch 2系统下gradle.properties中文注释乱码

以前在 Windows系统上的 Android Studio 上编辑 gradle.properties 的时候增加了不少的中文注释,现在升级到 Android Studio Electric Eel | 2022.1.1 Patch 2 结果发现中文都变成乱码了。

原因是升级到 Android Studio Electric Eel | 2022.1.1 Patch 2 之后,Windows系统上 gradle.properties 的默认编码格式被设置成了 ISO-8859-1 ,导致文件显示乱码。

现在调整回 UTF-8 格式即可解决问题。如下图:

继续阅读解决Android Studio Electric Eel | 2022.1.1 Patch 2系统下gradle.properties中文注释乱码

优化Android Studio构建速度

构建时间太长会拖慢您的开发过程。本页将介绍一些有助于解决构建速度瓶颈的技巧。

提高应用构建速度的一般过程如下:

  1. 采取一些对大多数 Android Studio 项目立竿见影的措施,优化 build 配置
  2. 对构建进行性能剖析,确定并诊断一些对您的项目或工作站来说比较棘手的瓶颈问题。

开发应用时,您应尽可能将其部署到搭载 Android 7.0(API 级别 24)或更高版本的设备中。较新版本的 Android 平台有更出色的机制来向您的应用推送更新,例如 Android 运行时 (ART) 以及对多个 DEX 文件的原生支持。

注意:您完成首次干净构建后,可能会注意到后续构建(干净和增量)的执行速度明显加快了(即使您没有使用本页面介绍的任何优化措施)。这是因为 Gradle 守护程序有一个性能提升“预热”期,类似于其他 JVM 进程。

优化 build 配置

按照下面的提示操作,以提高 Android Studio 项目的构建速度。

保持工具处于最新状态

几乎每次更新时,Android 工具都会获得构建方面的优化和新功能,本页介绍的一些提示假设您使用的是最新版本。为了充分利用最新的优化措施,请确保以下工具始终是最新版本:

避免编译不必要的资源

避免编译和打包不测试的资源(例如,其他语言本地化和屏幕密度资源)。您可以仅为“dev”变种的版本指定一个语言资源和屏幕密度,如下面的示例中所示:

尝试将 Gradle 插件门户放置在最后

在 Android 中,所有插件都位于 google() 和 mavenCentral() 代码库中。不过,build 可能需要使用 gradlePluginPortal() 服务解析的第三方插件。

Gradle 会按照声明的顺序搜索代码库,因此,如果先列出的代码库包含大多数插件,build 性能就会得到提升。因此,您可以尝试将 gradlePluginPortal() 条目放置在 settings.gradle 文件的代码库中最靠后的位置。在大多数情况下,这样可以最大限度地减少冗余插件搜索次数,并提高构建速度。

如需详细了解 Gradle 如何导航多个代码库,请参阅 Gradle 文档中的声明多个代码库

将静态 build 配置值用于调试 build

始终为会进入调试 build 类型的清单文件或资源文件的属性使用静态值。

您每次想运行更改时,都需要完整的应用 build 才能使用动态版本代码、版本名称、资源或可更改清单文件的任何其他构建逻辑,即使实际更改可能仅需要 1 次热交换也是如此。如果您的 build 配置需要此类动态属性,请将这些属性隔离到您的发布 build 变体中,并使相应值对您的调试 build 保持静态,如下例所示:

如需了解如何在项目中设置动态版本代码,请参阅 GitHub 上的 setVersionsFromTask 配方

使用静态依赖项版本

在 build.gradle 文件中声明依赖项时,请避免使用动态版本号(以加号结尾的版本号,例如 'com.android.tools.build:gradle:2.+')。使用动态版本号可能会导致意外的版本更新和难以解析版本差异,并会因 Gradle 检查有无更新而减慢构建速度。 请改用静态版本号。

创建库模块

在应用中查找可以转换成 Android 库模块的代码。以这种方式将您的代码模块化,可以让构建系统仅编译您修改的模块,并缓存输出以用于未来的构建。此外,这种方式也会让并行项目执行更有效(当您启用该优化时)。

为自定义构建逻辑创建任务

创建构建性能剖析报告后,如果性能剖析报告显示相当长的一部分构建时间用在了“配置项目”阶段,请检查 build.gradle 脚本并查找您可以添加到自定义 Gradle 任务中的代码。将某些构建逻辑移到任务中后,您可以确保它仅在需要时运行,可以缓存结果以用于后续构建,并且该构建逻辑将可以并行运行(如果您已启用并行项目执行)。如需详细了解自定义构建逻辑的任务,请参阅官方 Gradle 文档

提示:如果您的构建包含大量自定义任务,您可能需要通过创建自定义任务类来整理 build.gradle 文件。将您的类添加到 project-root/buildSrc/src/main/groovy/ 目录中;Gradle 会自动将这些类添加到项目中所有 build.gradle 文件的类路径中。

将图片转换为 WebP 格式

WebP 是一种既可以提供有损压缩(像 JPEG 一样)也可以提供透明度(像 PNG 一样)的图片文件格式,不过与 JPEG 或 PNG 相比,WebP 格式可以提供更好的压缩。

减小图片文件大小可以加快构建速度(无需在构建时进行压缩),尤其是当应用使用大量图片资源时。不过,在解压缩 WebP 图片时,您可能会注意到设备的 CPU 使用率有小幅上升。通过使用 Android Studio,您可以轻松地将图片转换为 WebP 格式

停用 PNG 处理

即使您不将 PNG 图片转换为 WebP 格式,仍然可以在每次构建应用时停用自动图片压缩,以加快构建速度。

如果您使用的是 Android Gradle 插件 3.0.0 或更高版本,则系统会在默认情况下针对“调试”编译类型停用 PNG 处理。如需针对其他 build 类型停用此优化,请将以下代码添加到 build.gradle 文件中:

由于 build 类型或产品变种不定义此属性,因此在构建应用的发布版本时,您需要手动将此属性设置为 true

使用 JVM 并行垃圾回收器进行实验

通过配置 Gradle 所用的最佳 JVM 垃圾回收器,可以提升构建性能。虽然 JDK 8 默认配置为使用并行垃圾回收器,JDK 9 及更高版本已配置为使用 G1 垃圾回收器

为提高构建性能,我们建议您使用并行垃圾回收器测试 Gradle 构建。在 gradle.properties 中设置以下内容:

如果此字段中已设置了其他选项,请添加一个新选项:

如需衡量采用不同配置时的构建速度,请参阅对构建进行性能剖析

增加 JVM 堆大小

如果您发现构建速度较慢(尤其是在 Build Analyzer 结果中发现构建时间超时 15% 的情况),则应增加 Java 虚拟化机器 (JVM) 堆大小。 在 gradle.properties 文件中,将限制设置为 4 GB、6 GB 或 8 GB,如以下示例所示:

然后测试构建速度是否有提升。确定最佳堆大小最简单的方法是增加限额,然后测试是否有足够的构建速度提升效果。

如果您还使用 JVM 并行垃圾回收器,则整行命令应如下所示:

您可以通过开启 HeapDumpOnOutOfMemoryError 标记分析 JVM 内存错误。这样,JVM 会在内存耗尽时生成堆转储。

使用非传递 R 类

使用非传递 R 类可为具有多个模块的应用构建更快的 build。这样做有助于确保每个模块的 R 类仅包含对其自身资源的引用,而不会从其依赖项中提取引用,从而帮助防止资源重复。这样可以获得更快的 build,以及避免编译的相应优势。这是 Android Gradle 插件 8.0.0 及更高版本中的默认行为。

从 Android Studio Bumblebee 开始,新项目的非传递 R 类默认处于开启状态。 对于使用早期版本的 Android Studio 创建的项目,请依次前往 Refactor > Migrate to Non-transitive R Classes,将项目更新为使用非传递 R 类。

如需详细了解应用资源和 R 类,请参阅应用资源概览

使用非常量 R 类

您可在应用和测试中使用非常量 R 类字段,以提高 Java 编译的增量并允许进行更精准的资源缩减。 对库而言,R 类字段始终不是常量,因为在为依赖于相应库的应用或测试打包 APK 时,资源会进行编号。 这是 Android Gradle 插件 8.0.0 及更高版本中的默认行为。

停用 Jetifier 标志

由于大多数项目都直接使用 AndroidX 库,因此您可以移除 Jetifier 标志,以便获得更好的构建性能。如需移除 Jetifier 标志,请在 gradle.properties 文件中设置 android.enableJetifier=false

Build Analyzer 可以执行一项检查,确认是否可以安全移除该标记,使您的项目能够具有更好的构建性能,并不再使用未加维护的 Android 支持库。如需详细了解 Build Analyzer,请参阅排查构建性能问题

使用配置缓存(实验性功能)

配置缓存是一项实验性功能,可让 Gradle 记录有关构建任务图的信息,并在后续 build 中重复使用该任务图,而不必再次重新配置整个 build。

如需启用配置缓存,请按以下步骤操作:

  1. 检查所有项目插件是否兼容。

    使用 Build Analyzer 检查项目是否与配置缓存兼容。Build Analyzer 会运行一系列测试 build,以确定是否可以为项目启用该功能。请参阅问题 13490,查看受支持的插件列表。

  2. 将以下代码添加到 gradle.properties 文件:

启用配置缓存后,当您首次运行项目时,build 输出应该会显示 Calculating task graph as no configuration cache is available for tasks。 在后续运行期间,构建输出应该会显示 Reusing configuration cache

如需详细了解配置缓存,请参阅配置缓存深度解析这篇博文和有关配置缓存的 Gradle 文档。

参考链接