Background
- 당연한 이야기지만 HTTP/1.x에서 보완하고 싶은 점이 있기 때문에 HTTP/2가 나오게 됨.
HTTP/1.1에서의 문제점
HOL (head-of-line)차단
- HTTP/1.1에서는 파이프라이닝을 통해서 TCP연결을 계속해줘야하는 문제점을 해결했었음
- 위 방식의 문제점은 병목 현상이 불가피하다는 점임.
- 예를 들어서 웹페이지 하나를 로드하는데, 많은 패킷들을 받아오는데, 먼저 요청을 보낸 리소스에 대한 요청에서 시간이 소요되면 뒤에오는 요청들도 자연스럽게 지연되는 상황이 벌어짐.
- 이러한 현상을 HOL(head-of-line)차단 이라고 한다.
- 위 문제점은 TCP의 고질적인 문제인데, 패킷 로스가 생기면 해당 패킷에 대해서 ACK이 오지 않았기 때문에 패킷을 재전송하게 되고 이로 인해서 다음번에 전송하는 패킷들이 지연됨
- 이 문제점은 요청의 순서와 응답의 순서는 같아야 한다고 HTTP1.1에 명세되어 있어서 그렇다고 함.
- 별도의 TCP 연결을 추가해서 위 문제를 해결할 수 있지만 리소스의 소모가 너무 크다는 문제
RTT 증가
- 하나의 Connection에 하나의 요청을 처리하기 때문에 요청을 새로 할경우 connection을 새로 만들기 때문에 자연스럽게 RTT가 증가한다.
- Connect: keep-alive이지만 여기서 하는말은 (요청⇒ 응답, 요청 ⇒ 응답) 이렇게 하나의 Connection에 하나의 요청을 처리한다는 말임.
무거운 헤더 구조
- HTTP헤더에 많은 정보가 들어간다는건 DevTools를 한번이라도 열어봤다면 안다.
- 이러한 정보들은 중복되는 정보들이 많고, 동일한 정보를 지속적으로 전달해야 하기 때문에 매 패킷마다 차지하는 크기가 존재해서 무거워지게 된다.
- 이런식으로는 전달하는 정보보다 헤더가 더 큰 경우도 다반사
HTTP2 DUDUDUNGA
HTTP2
- HTTP2의 포커스는 퍼포먼스에 있는데, end-user의 latency, network와 server의 리소스 사용에 특히 초점을 둔다.
- HTTP2는 우리가 알고있는 웬만한 사이트에서는 이미 도입되어 있는 상태이고, 전체적으로 봐도 30%정도는 도입되어 있다고 한다.
- HTTP2에서는 HTTP/1.1에서의 주 문제점이었던 (요청⇒응답, 요청⇒응답)을 multiplexing 전송을 지원함으로써 성능을 향상시킨다.
HTTP2의 Frame, Stream
- HTTP/1.1에서 요청, 응답은 Message 단위로 구성하는데, Message는 status line, head, body등으로 구성되어 있음.
- Frame: HTTP/2 통신에서 제일 작은 정보의 단위
- Message: HTTP/1.1에서와 마찬가지로 요청, 혹은 응답의 단위
- Stream: 클라이언트와 서버 사이에 맺어진 연결을 통해 양방향으로 주고받는 하나 이상의 복수개의 Message
- 하나의 스트림에 여러개의 데이터 프레임이 들어가 병렬적으로 요청을 할 수 도 있고, 하나의 스트림에 하나의 요청만 존재할 수 도 있음.

HTTP2의 개선방식
Multiplexed Streams & binary frames
- 한개의 Connection으로 동시에 여러개의 메시지를 주고 받을 수 있다.
- HTTP/1.1에서와 다르게 (요청⇒응답, 요청⇒응답)순서로 와야 하는게 아닌 여러개의 요청이 응답을 기다리지 않고 보낼 수 있음.

- 위와 같이 병렬적으로 처리될 수 있는 이유는 HTTP/2에서 지원하는 최소 단위인 Frame으로 쪼개서 보내기 때문에 가능하다.
- 윗 그림의 윗그림을 확인해보면 하나의 Connection에 여러개의 Stream이 들어가 있고, 그안에 Message, 그안에 Frame이 들어가 있는 구조를 볼 수 있다.
- 스트림을 다중화 함으로써 버퍼오버플로우도 방지할 수 있다.
- TCP연결에서 클라이언트와 서버 모두 아직 처리되지 않은 들어오는 요청을 보유하는 데 사용할 수 있는 특정 양의 버퍼 공간을 가지고 있는데, 이 버퍼의 양이 처리 불가능한 수준의 데이터가 온다면 문제가 됨.
- 이러한 상황을 막기 위해서 흐름 제어 메커니즘이 필요한데, HTTP/2에서는 스트림을 다중화 해서 스트림 수준에서 사용가능한 버퍼의 크기를 클라이언트와 서버가 설정한다.
- 이러한 흐름 제어는 WINDOW_UPDATE프레임을 통한 초기 연결 후에 수정, 유지관리한다.
- 중복된 헤더정보를 압축하기 우해서 Header Table, Huffman Encoding기법을 사용해서 처리
- 중복값이 존재하는 경우 Static/Dynamic Header Table개념을 사용해서 중복된 Header를 검출하고, 중복된 Header는 index값만 전송하고 중복되지 않은 Header정보의 값은 Huffman Encoding기법으로 인코딩 처리 하여 전송

- 위에 존재하는 그림처럼 한번 사용한 헤더는 Dynamic Table에 존재하는 인덱스값만 보내서 헤더의 크기를 줄임.
- 이방법을 이용해서 index를 브루트포싱해서 header reuse attack이 수행될 수 있다.
Stream Prioritization
- HTTP/2에서는 소스 간의 의존관계에 따라서 우선순위를 설정해줘서 리소스 로드 문제를 해결
Server Push
- 클라이언트가 요청하지 않은 리소스를 서버가 푸쉬해줄 수 있어서 클라이언트가 어차피 요청해야 하는 리소스를 요청하지 않아도 되도록 함.

- RFC 7540에 정의 되어이 있는 HTTP2의 Format은 위와 같음.

- 이런식으로 Frame payload에 헤더 정보들이 binary로 들어가 있음을 볼 수 있다.
- 아래 나온 Header들은 wireshark에서 보기 좋게 보여준거고, 실제 바이너리로 전달되는 값은 Header Block Fragment에 나와있는 값으로 전달됨.
POST /login HTTP/1.1\r\n
Host: psres.net\r\n
User-Agent: burp\r\n
Content-Length: 9\r\n
\r\n
x=123&y=4
:method POST
:path /login
:authority psres.net
:scheme https
user-agent burp
x=123&y=4
- 위 두개의 요청은 각각 HTTP/1.1, HTTP/2로 동일한 요청을 보냈을때의 포멧임.

- 위에서 내가 날렸던 HTTP/2패킷도 decompressed된 데이터를 확인해보면 유사함을 알 수 있다.
- HTTP2에서 보내는 이런식의 Header를 pseudo-Headers라고 부르는데, 5가지 종류가 잇고, 딱봐도 잘 모르겠는건 :authority로 HTTP/1.1의 host헤더라고 보면 됨.
- :status헤더도 존재하는데, 이건 reponse에서 볼 수 있는 헤더이다.
- HTTP/2에서는 위에서 본 format처럼 length에대한 명시가 되어 있기 때문에 desync공격이 힘듦
- 하지만 HTTP2 downgrade를 통해서 desync attack이 가능하다.
HTTP/2 Desync attack
Request Smuggling via HTTP/2 Downgrade

- 위 그림을 보면 알 수 있다 시피 기존의 http smuggling과 비슷하게 front-end서버에서 HTTP/2로 받은 데이터를 back-end서버에 HTTP/1.1로 받을 때 취약점이 발생한다.
- front-end에서 back-end에 전송할 때 Content-Length나 Transfer-Encoding헤더를 사용해야 할 수 밖에 없고 여기서 취약점이 발생