H2C Smuggling
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).
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
Detect
해당 취약점을 발견한 ByshopFox가 도구와 환경을 만들어두어서 쉽게 테스트할 수 있습니다. https://github.com/BishopFox/h2csmuggler
- Set-up
git clone https://github.com/BishopFox/h2csmuggler cd h2csmuggler pip3 install h2
- Scanning
python3 h2csmuggler.py --scan-list urls.txt --threads 5
- 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
- Bruteforce endpoint
python3 h2csmuggler.py -x https://edgeserver -i dirs.txt http://localhost/
- 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
- https://labs.bishopfox.com/tech-blog/h2c-smuggling-request-smuggling-via-http/2-cleartext-h2c
- https://www.hahwul.com/2020/09/16/http2-h2c-smuggling/
- https://developers.google.com/web/fundamentals/performance/http2?hl=ko
- https://en.wikipedia.org/wiki/HTTP/2
- https://tools.ietf.org/html/rfc7540
- https://tools.ietf.org/html/rfc7301
- https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids