
🔪 CRIME 공격 PoC : CVE-2012-4929를 공격하는 압축 오라클 🔪
CRIME 공격: Juliano Rizzo와 Thai Duong이 발견한 압축 오라클 공격(compression oracle attack) CVE-2012-4929;
압축 오라클 공격에서는 선택 평문(chosen plaintext)과 알려지지 않은 평문이 섞인 데이터에 적응형 데이터 압축을 사용하면, 압축된 텍스트 자체는 암호화되더라도 압축된 텍스트의 길이에 콘텐츠에 민감한 변화가 생기고 이를 감지할 수 있습니다. 이는 주입된 알려진 평문이 메시지의 비밀 부분에 있는 알려지지 않은 콘텐츠와 일부라도 유사할 때 이를 감지하는 프로토콜 공격에 사용될 수 있으며, 비밀 텍스트와 일치하는 값을 찾는 검색의 복잡성을 크게 줄여줍니다. CRIME 및 BREACH 공격은 이 현상을 이용하는 프로토콜 공격의 예입니다.
CRIME 공격을 사용하면 암호화된 데이터의 길이를 이용해 클라이언트가 서버로 보낸 암호화된 데이터를 알아낼 수 있습니다. 메시지나 HTTP 요청을 암호화하는 데 사용된 개인 키를 알아내는 것은 불가능합니다.
CRIME 공격의 작동 방식을 설명하는 많은 글이 있지만, 제가 인터넷에서 찾은 최고의 설명은 다음과 같습니다:
이 공격은 사실 복잡하지 않습니다. 정말 흥미로운 부분은 '이론'과는 조금 다른 구현입니다.
글에 설명된 단순한 방법을 살펴보겠습니다. 작동 방식을 이해하기 좋은 방법입니다:
공격자는 (예를 들어 자바스크립트를 사용해) 클라이언트가 보내는 요청을 제어할 수 있습니다. 목표는 비밀 쿠키를 알아내는 것입니다. 공격자는 다음과 같은 여러 요청을 보내고 암호화된 데이터의 길이를 확인합니다:
| 요청 | 길이 |
|---|---|
| GET /cookie= DATA cookie=quokkalight | 80 |
| GET /cookie=a DATA cookie=quokkalight | 81 |
| GET /cookie=b DATA cookie=quokkalight | 81 |
| GET /cookie=. DATA cookie=quokkalight | 81 |
| GET /cookie=q DATA cookie=quokkalight | 80 |
cookie=q가 비밀 쿠키의 cookie=quokkalight와 일치하므로 암호화된 데이터의 길이가 같아지고, 공격자는 한 바이트를 찾았음을 알게 됩니다.
하지만 이 방법은 때때로 실패하여 신뢰할 수 없으므로 다른 방법을 사용하겠습니다.
먼저 찾으려는 문자 뒤에 초기 요청에서는 찾을 수 없는 여러 특수 문자(예: #:/[@/&)를 붙여 요청을 보냅니다: chr(i) + "#:/[@/&". 그런 다음 두 번째 요청에서는 페이로드를 이렇게 반대로 바꿔 보냅니다: "#:/[@/&" + chr(i) 그리고 두 길이를 비교합니다. len(enc(req1)) < len(enc(req2))이면 한 바이트를 찾은 것입니다. 이 방법을 two_tries라고 하며 훨씬 더 신뢰할 수 있습니다:
공격자가 한 바이트를 찾았습니다!
if len(enc(request1)) < len(enc(request2)):
print("found byte")
제가 구현한 two_tries 메서드는 완전히 재귀적입니다. 왜일까요? 압축이 여러 패턴과 일치하기 때문에 때때로 한 번에 두 개 이상의 바이트를 찾을 수 있기 때문입니다.
비밀 값을 cookie=quokkalight라고 가정하고 two_tries 알고리즘을 실행하면 다음 결과를 얻게 됩니다:
result 1: cookie=quokie=quokie=quokie=quokie=quokie=
result 2: cookie=quokkalight
알고리즘은 가능한 모든 해를 찾기 위해 트리의 모든 경로를 탐색해야 합니다. 모든 결과는 다음과 같이 표현되는 트리로 볼 수 있습니다:

스트림 암호 모드에 대한 CRIME 공격의 개념 증명은 CRIME-RC4-poc.py 파일에서 확인할 수 있습니다. 이 파일은 앞서 설명한 내용을 파이썬으로 구현한 것입니다.
python3 CRIME-RC4-poc.py
전체 데모 및 결과:
CBC 암호 모드를 AES나 DES와 함께 사용하면 공격은 RC4만큼 간단하지 않습니다. 모든 것이 블록으로 나뉘기 때문에 공격이 조금 더 까다롭습니다(아주 심하지는 않지만).
예를 들어 AES를 CBC 암호 모드로 사용한다고 가정하면 블록은 16바이트 길이로 나뉘고 len(data)%16 != 0인 경우 끝에 패딩이 추가됩니다.
CBC 모드에서는 페이로드 payload=rand의 길이가 12가 아닌 16이 된다는 점에 유의해야 합니다. 데이터 끝에 패딩이 암호화 전에 추가되기 때문입니다. 따라서 앞선 공격은 작동하지 않습니다.
예:
이 예에서 길이는 항상 같습니다. 바이트를 추가하거나 제거해도 길이는 항상 같고 패딩만 변경되기 때문입니다. 공격자는 암호화된 데이터만 볼 수 있으며 암호화된 데이터로는 패딩을 알 방법이 없습니다.
해결책: CBC 모드의 규격을 활용해 GARB 변수에 임의의 값을 추가하여 패딩 길이를 1로 만듭니다. (공격자는 GET과 POST 데이터를 제어할 수 있으므로 GET 파라미터에 추가합니다.)
바이트가 패턴과 일치하면 길이가 같아지고, 일치하지 않으면 길이가 달라집니다.
다음으로는 RC4 부분에서 설명한 것과 동일한 방법을 사용하면 됩니다. 한 단계만 앞에 추가하면 됩니다.
adjust_padding() 함수를 호출합니다.two_tries_recursive() 함수를 호출하면 비밀 FLAG를 찾을 수 있습니다!python3 CRIME-cbc-poc.py
정말 곧 공개됩니다...
\ /
\ _ /
----/_\----
x--------------( . )--------------x
x|x | |_|\_/|_| | x|x
x x x x
https://www.nccgroup.trust/globalassets/our-research/us/whitepapers/ssl_attacks_survey.pdf https://github.com/cloudflare/cf-nocompress https://www.ekoparty.org/archive/2012/CRIME_ekoparty2012.pdf https://security.stackexchange.com/questions/19911/crime-how-to-beat-the-beast-successor/19914#19914
| 바이트 q를 알아내기 위한 요청 | 길이 |
|---|
| GET /cookie=a~#:/[@/& DATA cookie=quokkalight | 81 |
| GET /cookie=~#:/[@/&a DATA cookie=quokkalight | 81 |
| GET /cookie=b~#:/[@/& DATA cookie=quokkalight | 81 |
| GET /cookie=~#:/[@/&b DATA cookie=quokkalight | 81 |
| GET /cookie=q~#:/[@/& DATA cookie=quokkalight | 80 |
| GET /cookie=~#:/[@/&q DATA cookie=quokkalight | 81 |
| 블록1 | 블록2 | 블록3 | 길이 |
|---|
| GET /cookie= DA | TA cookie=quokka | light + PAD(11) | 48 |
| GET /cookie=a DA | TA cookie=quokka | light + PAD(10) | 48 |
| GET /cookie=b DA | TA cookie=quokka | light + PAD(10) | 48 |
| GET /cookie=. DA | TA cookie=quokka | light + PAD(10) | 48 |
| GET /cookie=q DA | TA cookie=quokka | light + PAD(11) | 48 |
| 블록1 | 블록2 | 블록3 | 블록4 | 길이 |
|---|
| GET /GARBc | ookie= DATA coo | kie=quokkalight + PAD(1) | 48 | |
| GET /GARBc | ookie=a DATA coo | kie=quokkalight | PAD(16) | 64 |
| GET /GARBc | ookie=b DATA coo | kie=quokkalight | PAD(16) | 64 |
| GET /GARBc | ookie=. DATA coo | kie=quokkalight | PAD(16) | 64 |
| GET /GARBc | ookie=q DATA coo | kie=quokkalight + PAD(1) | 48 |