Hi hackers and bugbounty hunter, This is written in Korean for Koreans. if you use english, look at the bishopfox’s original post is the best!
최근에 bishopfox에서 h2c smuggling 에 대한 이야기를 공유하였습니다. 작년에 나왔었던 WebSocket Connection Smuggling과 비슷하기도 하고 재미있는 부분들이 있어서 한글로 풀어봅니다.
h2c는 웹 프로토콜인 HTTP/2로의 Switching 단계에서 사용하는 헤더로 먼저 HTTP/2에 대한 내용을 하나 알고가야합니다. HTTP/2 프로토콜은 클라이언트와 서버 모두 HTTP/2를 지원해야 사용할 수 있습니다. (RFC-7540)
Request
GET / HTTP/1.1
Host: test.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
Response - HTTP/2 지원하지 않는 서버
Response - HTTP/2 지원하는 서버
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
[ HTTP/2 connection ...
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 커넥션을 사용하게 됩니다.
많은 웹 서비스들은 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-spec에도 이렇게 명시되어 있습니다.
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로 동작할 수 있기 때문에 차단된 리소스에 접근할 수 있는 포인트가 생깁니다.
** 사실 완벽하게 이해가 된 부분은 아니라, 혹시나 잘못 이해했다면 댓글로 알려주세요! **
전반적인 동작 방식을 보면 WebSocket Connection Smuggling과 많이 유사합니다. WebSocket Connection Smuggling은 제가 작년에 썼던 글을 참조하시면 될 것 같습니다.
아무튼 플로우는 이렇습니다.
정말 다행스럽게도, 점검 도구와 환경을 다 만들어두셨습니다.
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/
이 과정에선 HTTP/2의 multiplexing을 사용한다고 하네요. multiplexing 이란 HTTP/2의 주요 기능으로 동시에 여러 리소스를 요청하는걸 의미합니다. Connection: keep-alive , pipeline의 개선버전이죠
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`
more.. https://github.com/BishopFox/h2csmuggler
HTTP Request Smuggling / WebSocket Connection Smuggling과 같이 여러가지 대응방안이 있겠지만, 원리적인 측면에서 보면 RFC 문서에서 이야기된대로 tls connection에서 h2c upgrade를 사용할 수 없도록 제한하는게 가장 정확한 것 같습니다. 물론 스펙적으로 가능한다면(이 문제조차 없었겠죠) 좋겠지만, 안되는건 안되는거기 때문에 프록시 서버에서 헤더를 무조건 전달하지 않고 서비스에서 사용할 헤더만 처리하는 방식으로 위험을 완화시킬 수 있습니다. 다만 HTTP Request Smuggling에서 몇몇 벤더사들이 선택한 방식인 Request 가공/처리하지 않고 그대로 백엔드로 넘겨주는 방식 때문에 사실상 조치가 굉장히 어려워질 수도 있습니다. (둘다 존재하면 누굴 선택해야하는가?)
그냥 제 개인적인 생각으론, 사용하지 않는 헤더패턴을 막는 것도 중요하지만, private 한 경로를 직접 접근할 수 없도록 host, x-forwarded-for 등의 다른 호스트를 바라볼 수 있는 헤더를 제한하고, 중요한 API나 경로를 호출할 수 없도록 제한하는 방식도 필요할 것 같네요.
모든 Smuggling 그렇듯, 패치에 의존하는 것보다 hop간의 간격 차이를 이해하고 거기서 조치방안을 찾는게 더 유리해보입니다 :D
원리는 무척 단순하지만, 환경은 단순하지 않습니다. HTTP Request Smuggling과 WebSocket Connection Smuggling과 다르게 서버의 설정에 따라 발생하는 부분이고, 생각보단 흔하지 않은 케이스일 것 같다는 생각이 드네요. 다만 이 기법을 만든 bishopfox가 이야기했듯이 Burp extension과 Nuclei template으로 만들어진다면 식별 자체는 굉장히 쉬울테니, 아마 광범위한 영역에 테스팅이 이루어질 것 같습니다. (Nuclei가 정말 대단하죠..ㅋㅋ)