일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- paddingoracle
- Burp
- crosssitescripting
- xsstool
- 취약점
- extension
- 루키즈
- web
- CSRF
- 특수대학원
- 정보보안
- httpOnly
- Mobile
- 쉴더스
- bypass
- 대학원
- 성균관대학교
- XSS
- Android
- PADDING
- Trace
- 웹 해킹
- SK쉴더스
- OWASP
- Burp suite
- URL
- xst
- hacking
- reflected
- attack
- Today
- Total
Why Always Me!
4. Padding Oracle Attack 본문
1. 개요
Padding Oracle Attack은 서버에서 Padding의 유효 여부를 검증하고 그 결과를 반환할 때 발생하는 취약점으로, 공격자는 해당 취약점을 통해 암호문을 key 없이 복호화할 수 있습니다. 그후 공격자는 암호문을 복호화한 후 byte flipping 공격을 통해 암호문을 변조하여 접근 제어 우회, 권한 상승 등의 공격을 수행할 수 있습니다.
2. 사전 지식
Padding Oracle Attack은 간단한 암호학적 지식이 필요합니다. 또한 XOR 동작 원리를 이해하고 있어야 합니다.
2.1 CBC
Padding Oracle Attack은 보통 CBC 모드에서 발생합니다.
CBC는 운영모드 중 하나로, 블록 암호(DES, AES 등)를 이용하여 여러 크기의 데이터를 암호화할 때 사용하는 방식 중 하나라고 생각하시면 됩니다.
동일 평문에 대해 동일 암호문이 떨어지는 ECB 모드와 다르게 CBC는 IV(Initialization Vector)와 XOR 연산이 되어 암호문이 결정되기 때문에, 동일 평문이라도 다른 암호문이 출력됩니다.
CBC를 이용한 암호화 시에는 IV를 첫 블록에 XOR 연산하고 암호화를 수행합니다. 그 후, 암호문을 다음 블록의 IV로 사용합니다.
복호화 시에는 암호문을 복호화 이후에 IV와 XOR 연산을 하여 평문을 구하게 됩니다.
여기서 첫 블록이 아닌 경우, 이전 암호문을 XOR 한다는 것을 기억해 주세요.
2.2 XOR
XOR은 비트 연산의 하나로 다음과 같은 특징을 가지고 있습니다.
0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0
조금 더 나아가서 다음과 같은 식이 성립합니다.
A XOR A = 0
0 XOR B = B
A XOR A XOR B = 0 XOR B = B
2.3 Padding
패딩은 블록 암호에서 빈 자릿수를 채우기 위해 사용하며 PKCS5 혹은 7의 방식을 채용합니다. (둘은 바이트 차이 외엔 동일합니다. AES의 경우 PKCS7을 사용)
예를 들어 AES는 128 비트, 즉 16바이트의 블록 사이즈를 채용하고 있습니다. 만약 다음과 같은 데이터가 있다고 가정합니다.
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D
총 14바이트입니다. 그런데, 한 블록은 16바이트니까 2바이트가 부족합니다. 이를 패딩으로 채워 16바이트를 맞춰줍니다.
패딩 값은 부족한 바이트 값이 됩니다. 이 예시의 경우 2바이트가 부족하기 때문에 0x02로 남은 두 바이트를 채웁니다.
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x02 0x02
추가로 다음과 같이 모든 바이트가 꽉 차 있는 경우, 블록의 사이즈만큼 패딩을 더해줍니다.
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
위 데이터는 모든 값이 차 있기 때문에, 블록 하나를 더해 다음과 같이 표현됩니다.
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F 0x0F
이는 모든 데이터가 가득 차 있을 시 다음 두 값을 구분할 수 없기 때문입니다.
1. 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x02 0x02 (0x02가 패딩이 아닌 경우)\
2. 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x02 0x02 (0x02가 패딩인 경우)
3. 공격 방법
먼저 서버에서 Padding 에러가 반환되는지 확인해야 합니다. 직접적으로 Padding 관련된 에러가 출력될 수도 있고, 다른 방식으로 에러가 출력될 수도 있습니다. 공격을 수행하기 위해 다음과 같은 경우를 찾아야 합니다.
1. 정상적인 데이터가 반환되는 경우
2. Padding 에러가 반환되는 경우
3. 정상적인 데이터가 반환되지 않는 경우
1)의 경우, 정상 데이터를 보냈으니 당연히 정상적인 데이터가 반환될 것입니다. ( A -> good )
2)의 경우, 복호화 과정에서 padding이 맞지 않으니 padding 관련 에러를 반환될 것입니다. ( B -> padding error )
3)의 경우, 복호화 과정에서 padding은 맞으나 복호화 후 데이터가 정상적인 데이터가 아니기 때문에 1)과 다른 결과가 반환될 것입니다. ( C -> bad )
여기서 마지막 바이트를 조작하여 2)번이 안 나오고 3)이 나오는 값을 브루트 포싱합니다.
SESSION 값의 마지막 바이트를 변조하면 다음과 같은 응답 값이 떨어집니다.
이를 통해 Padding Oracle Attack 가능성이 있다고 판단한 후, 마지막 바이트를 0부터 255까지 브루트 포싱합니다. (어느 블록부터 시작하는 것은 상관 없습니다. 이전 블록과 현재 블록만 알고 있으면 됩니다. 저는 편의상 마지막 블록부터 공격을 수행했습니다.)
해당 AES의 형식은 base64이기 때문에 실제 바이트 연산을 위해 먼저 base64 디코딩을 수행합니다.
그러면 다음과 같이 데이터가 떨어지게 됩니다.
\x19\x17\xb6p\xa5\xf0[\x94Mg\x95\xc7\x94\xf82\x9c\xab\xdc\x94\xf2\x17\xd7Bz9\x15\x0f_\x12\x19 \x7fE\xa3\rtp\x0c\x04\x1f\x90DqW\xa0\xf5\x0b-LL\x98\xaf1\x9b\x8bZm\x8f\xf9\xa8\xc8N\x97\x1dh\xb6\xac`\xc4\xd3\xb7_\xddI\x97\xe8\x81\xf9w@\x80\x04)\xd7\x17\x80\x99D\x94@\x8f\x8d,2\xe5\x9c\xba\x86\xbc\x89\xcb\xa8N\x16m\x88\xfdyZs3\xf0/\xe3H \xa9\xf4\x9a\xd4\x951\x88\xd7\xf6.\xc1|l\xe6\xbbNDK\x08\xd9\x06\x8f0\xe3\xd3\xcfQ(
16바이트 단위로 나누면 다음과 같이 됩니다.
1. \x19\x17\xb6p\xa5\xf0[\x94Mg\x95\xc7\x94\xf82\x9c
2. \xab\xdc\x94\xf2\x17\xd7Bz9\x15\x0f_\x12\x19 \x7f
3. E\xa3\rtp\x0c\x04\x1f\x90DqW\xa0\xf5\x0b-
4. LL\x98\xaf1\x9b\x8bZm\x8f\xf9\xa8\xc8N\x97\x1d
5. h\xb6\xac\xc4\xd3\xb7_\xddI\x97\xe8\x81\xf9w@`
6. \x80\x04)\xd7\x17\x80\x99D\x94@\x8f\x8d,2\xe5\x9c
7. \xba\x86\xbc\x89\xcb\xa8N\x16m\x88\xfdyZs3\xf0
8. /\xe3H \xa9\xf4\x9a\xd4\x951\x88\xd7\xf6.\xc1|
9. l\xe6\xbbNDK\x08\xd9\x06\x8f0\xe3\xd3\xcfQ(
위에서 2)와 3)이 발생했을 때 3)이 발생할 때까지 브루트 포싱한다고 했습니다. 그러면 어디를 조작해야할까요?
바로 복호화할 블록의 이전 블록입니다.
사실 1)은 그리 필요하진 않습니다. 따라서 이전 블록을 전부 0x00으로 초기화해도 문제는 없습니다.
우리는 Padding이 잘 적용되어있는지만 알면 되기 때문이죠!
따라서 다음과 같이 8번 블록의 마지막 바이트( | )를 0부터 255까지 변조해서 서버에 전송합니다. 3)이 뜰 때까지요.
그러면 0x79가 나옵니다.
여기서부터 중요합니다.
3)이 출력되었다는 것은 Padding 에러가 발생하지 않았다는 것입니다. 그렇다는 것은 Padding이 잘 들어갔다는 건데 저희는 8번 째 블록의 마지막 바이트를 건드렸잖아요?
마지막 바이트에 들어갈 수 있는 값은 0x01 입니다.
* 예를 들어 마지막 바이트가 0x02가 되면, 그 이전 바이트도 0x02가 되어야 하는데 이 과정에서 Padding 에러가 발생해 버립니다.
즉, 우리가 8번째 마지막 바이트를 변조해서 얻은 값은 0x01이라는 것이고, Padding 에러는 발생하지 않지만 복호화 결과가 정상적인 데이터 값은 아닐 것이기 때문에 3)이 출력되게 되는 것입니다.
자, 그러면 우리는 IV가 0x79일 때 0x01이 출력된다는 것을 알았습니다.
여기서 CBC 복호화 과정을 다시 보겠습니다.
암호문을 복호화한 후, 이전 바이트(IV) 값과 XOR하면 평문이 구해지죠?
그러면 IV와 XOR 연산 직전 값은 IV XOR Plaintext가 될 겁니다. A XOR A XOR B = B 이기 때문이죠.
이 과정에서 중요한 것은 IV XOR Plaintext는 우리가 변조한 이전 블록의 바이트에 영향을 받지 않는다는 것입니다.
즉, 우리가 Padding Oracle Attack을 통해 얻은 0x72라는 값과 0x01을 XOR한 값은, IV XOR Plaintext과 동일한 값을 가진다는 것입니다. 이를 통해 다음이 성립함을 알 수 있습니다.
IV XOR Plaintext = 가짜 IV XOR 가짜 Plaintext = 진짜 IV XOR 진짜 Plaintext
여기서 가짜 IV는 0x79, 가짜 Plaintext는 0x01이 될 겁니다.
진짜 IV는 이전 블록의 데이터입니다. 즉, 8번 블록의 마지막 바이트( | )가 진짜 IV가 됩니다.
따라서 진짜 Plaintext를 다음 과정을 통해 구할 수 있습니다.
1) IV XOR Plaintext 구하기
0x79 XOR 0x01 = 0x78
위 계산을 통해 IV XOR Plaintext는 0x78이라는 것을 알았습니다.
2) 진짜 Plaintext 값 구하기
0x78 XOR 0x7C = 0x04
8번 블록의 마지막 바이트( | )를 0x78과 XOR 해줍니다. | 는 124이고 hex로는 0x7C입니다. 그러면 0x04가 나오네요.
따라서 9번 블록의 마지막 평문은 0x04라는 것을 알 수 있습니다.
자, 마지막 바이트를 구했습니다. 그러면 그 이전 바이트(15번)는 어떻게 구할 수 있을까요?
구하는 방식은 똑같지만, IV를 살짝 조정 해줘야 합니다.
8번 블록의 15번 째 바이트를 브루트 포싱하여 9번 블록의 15번 바이트를 구할 건데, 마지막 바이트는 0x01을 가정하고 공격을 수행했잖아요? 그러면 15번 바이트는 어떤 값이 될까요?
정답은 0x02입니다. 15번 바이트에서 Padding 에러를 발생시키지 않는 값은 0x02밖에 없거든요.
그런데 문제가 있죠? 마지막 바이트도 같이 0x02로 바꿔줘야 합니다. 만약 0x02가 아니면, 15번 바이트 이전에 Padding 에러가 발생할 겁니다.
그래서 IV 16번 바이트에 IV XOR Plaintext에 0x02를 XOR 연산한 값으로 설정합니다. 그러면 16번 바이트 IV는 IV XOR Plaintext XOR 0x02가 될 거고, 복호화 한 값(IV XOR Plaintext)과 XOR이 되면서 0x02가 남게될 겁니다.
IV XOR Plaintext XOR 0x02 XOR IV XOR Plaintext = 0x02
그 후 이전 과정과 동일하게 15번 IV를 브루트포싱하여 Padding 에러가 발생하지 않는 값을 구합니다.
이 과정을 반복하게 되면, 암호문에 대한 평문을 구할 수 있습니다.
4. PoC
위에서 설명한 방법대로 익스플로잇 코드를 작성하고 복호화를 시도하면 다음과 같은 결과가 나옵니다.
먼저 암호화되기 전 평문입니다.
cookie = {
"blog":"racilu.tistory.com",
"Maker":"racilU",
"Vul":"Padding_Oracle",
"com":"SK infosec",
"flag": random_key
}
익스플로잇 코드 실행 결과는 다음과 같습니다.
[*] Result : .tistory.com", "Maker": "racilU", "Vul": "Padding_Oracle", "com": "SK infosec", "flag": "bJhYevCB9YdqDDJ3"}
첫 번째 블럭은 실제 IV가 필요한데, 추측하기 쉬운 IV를 사용하지 않는 경우 첫 번째 블록은 복호화가 힘듭니다.
아무튼, 복호화를 통해 얻은 flag를 통해 Padding Oracle 공격이 성공했음을 확인해 보면,
다음과 같이 공격이 성공한 것을 확인할 수 있습니다.
'취약점 CASE > Web' 카테고리의 다른 글
5. XST (Cross Site Tracing) (0) | 2025.03.01 |
---|---|
3. Zip Slip (0) | 2024.05.02 |
2. JWT None Algorithm Attack (0) | 2024.04.09 |
1. XSS / 공백 필터링 우회 (1) | 2024.02.29 |
WEB/API (0) | 2024.02.28 |