HTTP2 H2C Smuggling

A one-line explanation of the content

Introduction

HTTP/2 Protocol and H2C Switching

HTTP2 프로토콜은 구글에서 개발 및 시범 사용중이던 SPDY 프로토콜의 사양을 기반으로 HTTP-WG에서 진행한 프로토콜입니다. 그리고 2015년 HTTP/2 표준으로 채택됨과 동시에 SPDY는 지원 중단을 하게 되었지요. 아무튼 HTTP/2난 차세대 웹 프로토콜로 보시면 될 것 같고, 1.x 버전대보다 성능적으로 큰 개선들이 있어서 서서히 많은 서버들이 HTTP2 지원을 하고 있는 상태입니다. HTTP2에 대한 내용은 google developers 문서를 참고해주세요.

H2C는 HTTP/1.x 버전에서 HTTP/2 버전으로 프로토콜을 스위칭하기 위해 사용되는 방법입니다. 그리고 서버/클라이언트 모두 HTTP/2 지원이 필요합니다. (RFC-7540)

Request

GET / HTTP/1.1
Host: test.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c

Response - HTTP/2 지원

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
[ HTTP/2 connection ...

Response - HTTP/2 미지원

서버가 무시

RFC-7540을 읽어보면 HTTP/2 통신은 기본적으로 웹 소켓 통신과 많이 유사합니다. 클라이언트(웹브라우저)는 웹 서버와의 HTTP/2 통신을 하기 위해 서버에서 HTTP/2 지원 여부를 물어보고, 그에 따라서 HTTP/2를 통신을 사용할지, HTTP/1.x 통신을 사용할지 결정합니다.

HTTP/2 통신은 7 Layer(Application) 에서 수행되는 프로토콜이고, TCP Connection을 사용합니다. 그래서 기존 HTTP 통신과는 다르기 때문에 웹 소켓과 같이 지원여부 체크 후 프로토콜 변환인 101 Switching protocol을 사용합니다. (uri는 http와 동일하게 http->80, https->443을 사용합니다.)

An HTTP/2 connection is an application-layer protocol running on top
of a TCP connection ([TCP]).  The client is the TCP connection
initiator.

HTTP/2 uses the same "http" and "https" URI schemes used by HTTP/1.1.
HTTP/2 shares the same default port numbers: 80 for "http" URIs and
443 for "https" URIs.  As a result, implementations processing
requests for target resource URIs like "http://example.org/foo" or
"https://example.com/bar" are required to first discover whether the
upstream server (the immediate peer to which the client wishes to
establish a connection) supports HTTP/2.

The means by which support for HTTP/2 is determined is different for
"http" and "https" URIs.  Discovery for "http" URIs is described in
Section 3.2.  Discovery for "https" URIs is described in Section 3.3.

http/1.x -> http/2로 업그레이드를 위해선 http request의 Upgrade 헤더에 indicator와 HTTP2-Settings 헤더를 전달합니다. indicator 종류는 HTTP는 h2c HTTPS는 h2 를 의미합니다. 즉 Upgrade: h2c 는 HTTP/2를 평문으로 통신하겠다는 의미입니다.

GET / HTTP/1.1
Host: test.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>

이후 HTTP/2를 지원하게 되면 클라이언트에겐 101 Switching protocol을 전달하고 TLS Connection(HTTP/2)을 사용하여 클라이언트와 통신합니다. 이 때 프로토콜에 대한 합의는 TLS-ALPN(Application-Layer-Protocol Negotiation)을 사용합니다. 이 과정에서 APLN extension으로 클라이언트가 서버에게 버전의 리스트를 제공하고, 서버는 하나를 선택합니다. https를 사용하는 HTTP/2의 값은 h2입니다.

물론 HTTP/2를 바로 사용하는 케이스의 경우 TLS-ALPN을 통해 바로 프로토콜 협상 후 TLS 커넥션을 사용하게 됩니다.

H2C Smuggling

많은 웹 서비스들은 Reverse Proxy를 사용하고 있습니다. 이러한 과정에서 101 Switching 을 사용해야하는 상황이 오면, 프록시 서버는 별도의 처리없이 중재자 역할을 수행하게 됩니다. 여기서 bishopfox는 HTTP/2 프로토콜에 대한 연구를 진행했고, 결국 재미있는 결함을 발견하게 됩니다.

RFC-7540의 3.2.1 부분에 명시된 내용과 TLS 내 HTTP/2 설정에선 h2c Upgrade는 cleartext connection에서만 허용되고 이 때 HTTP2-Settings 헤더는 전달하지 말아야한다고 명시되어 있습니다.

HTTP2-Settings    = token68

A server MUST NOT upgrade the connection to HTTP/2 if this header
field is not present or if more than one is present.  A server MUST
NOT send this header field.

https://tools.ietf.org/html/rfc7540#section-3.2.1

HTTP2 스펙에는 이렇게 명시되어 있습니다.

3.3 Starting HTTP/2 for "https" URIs
A client that makes a request to an "https" URI uses TLS [TLS12] with the application-layer protocol negotiation (ALPN) extension [TLS-ALPN].

HTTP/2 over TLS uses the "h2" protocol identifier. The "h2c" protocol identifier MUST NOT be sent by a client or selected by a server; the "h2c" protocol identifier describes a protocol that does not use TLS.

Once TLS negotiation is complete, both the client and the server MUST send a connection preface (Section 3.5).

https://http2.github.io/http2-spec/#discover-https

TLS를 통한 HTTP/2를 사용할 땐 h2c가 아닌 h2 프로토콜 식별자를 사용하라고 되어있습니다. 맨 위에서 이야기드렸듯이 h2c는 http, h2는 https를 사용하기 위해 고안된 indicator이기 때문이죠.만약 proxy가 껴있는 구조에서 cleartext가 아닌 TLS 상에서 proxy가 h2c 를 백엔드에 전달하여 upgrade가 발생하면 어떻게 될까요?

Proxy가 있는 hops 환경에서 백엔드 서버는 클라이언트가 Cleartext인지 TLS인지 알 수 있는 방법이 h2c,h2 등의 indicator 뿐이라 웹이 아닌 TLS Connection을 HTTP로 판단하여 TLS Connection 위에 TCP Tunnel 을 만듭니다. 이 때 클라이언트는 HTTP가 아니기 때문에 기존 커넥션을 그대로 사용합니다. (Over TLS)

즉 이미 연결되어 있는 커넥션이고 HTTP 통신이 아니기 떄문에 Proxy의 ACL 정책에 영향을 받지 않지만, TCP Tunnel에서 발생한 요청이 HTTP로 동작할 수 있기 때문에 차단된 리소스에 접근할 수 있는 포인트가 생깁니다.

Flows

  • 클라이언트가 서버(프록시)로 HTTP/1.1 Upgrade 요청 전송 (잘못된 헤더를 전송)
  • 프록시가 해당 요청을 백엔드에 전달하고 101 Swiching protocol로 response를 내려줌
  • 백엔드 서버에서 101을 받으면 TCP Tunnel을 생성함
  • 클라이언트가 프록시로부터 101을 받으면 기존 커넥션을 재사용하고, HTTP/2 초기화를 진행
  • 클라이언트가 HTTP/2 multiplexing을 사용하여 priavte한 경로에 대해 추가 요청을 전송함
  • 프록시는 TCP 통신을 감시하지 않기 때문에(정책상 HTTP) 제한된 페이지에 추가 요청을 전송하고, 서버는 응답을 TLS Tunnel로 전달함

Offensive techniques

Testing with h2csmuggler

해당 취약점을 발견한 ByshopFox가 도구와 환경을 만들어두어서 쉽게 테스트할 수 있습니다. https://github.com/BishopFox/h2csmuggler

  1. Set-up
    $ git clone https://github.com/BishopFox/h2csmuggler
    $ cd h2csmuggler
    $ pip3 install h2
    
  2. Scanning
    $ python3 h2csmuggler.py --scan-list urls.txt --threads 5
    
  3. Get internal endpoint
    $ python3 h2csmuggler.py -x https://edgeserver -X POST -d '{"user":128457 "role": "admin"}' -H "Content-Type: application/json" -H "X-SYSTEM-USER: true" http://backend/api/internal/user/permissions
    
  4. Bruteforce endpoint
    $ python3 h2csmuggler.py -x https://edgeserver -i dirs.txt http://localhost/
    
  5. Get AWS Metadata API
    $ python3 h2csmuggler.py -x https://edgeserver -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" http://169.254.169.254/latest/api/token`
    

Defensive techniques

HTTP Request Smuggling / WebSocket Connection Smuggling과 같이 여러가지 대응방안이 있겠지만, 원리적인 측면에서 보면 RFC 문서에서 이야기된대로 tls connection에서 h2c upgrade를 사용할 수 없도록 제한합니다.

Tools

Articles

References