
💡 나는 게으른 개발자이고 CBC 암호화가 작동하기를 원합니다. 초기화 벡터에 대한 이 모든 이야기는 무엇입니까? 중요하게 들리지 않습니다.
음,, iv와 관련해서 문제가 있을 것 같은데.. 일단 문제를 보자.
from Crypto.Cipher import AES
KEY = ?
FLAG = ?
@chal.route('/lazy_cbc/encrypt/<plaintext>/')
def encrypt(plaintext):
plaintext = bytes.fromhex(plaintext)
if len(plaintext) % 16 != 0:
return {"error": "Data length must be multiple of 16"}
cipher = AES.new(KEY, AES.MODE_CBC, KEY)
encrypted = cipher.encrypt(plaintext)
return {"ciphertext": encrypted.hex()}
@chal.route('/lazy_cbc/get_flag/<key>/')
def get_flag(key):
key = bytes.fromhex(key)
if key == KEY:
return {"plaintext": FLAG.encode().hex()}
else:
return {"error": "invalid key"}
@chal.route('/lazy_cbc/receive/<ciphertext>/')
def receive(ciphertext):
ciphertext = bytes.fromhex(ciphertext)
if len(ciphertext) % 16 != 0:
return {"error": "Data length must be multiple of 16"}
cipher = AES.new(KEY, AES.MODE_CBC, KEY)
decrypted = cipher.decrypt(ciphertext)
try:
decrypted.decode() # ensure plaintext is valid ascii
except UnicodeDecodeError:
return {"error": "Invalid plaintext: " + decrypted.hex()}
return {"success": "Your message has been received"}
소스코드이다. 하나씩 살펴보자.
먼저 encrypt 부분은 입력이 16byte의 배수면 그대로 암호화 해준다.
get_flag 부분은 입력한 key가 실제 KEY와 같다면 flag를 보여준다.
receive 부분은 encrypt와 비슷하게 입력이 16byte의 배수면 그대로 복호화 해준다.
그런데 암호화, 복호화하는 부분에서 눈여겨볼 부분이 있다. 바로 iv에 다른 값을 안 쓰고 Key를 그대로 쓴다는 점이다.
이전 문제를 풀다가 key와 iv가 같을 때 발생할 수 있는 취약점을 다룬 문제를 본 적이 있었다.
위의 블로그를 정리하자면 다음과 같다.
- 최소 3블록 길이의 평문을 만든다.
- 평문을 암호화하고 결과 암호문을 얻습니다.
- 0만 포함하도록 암호문의 두 번째 블록을 수정합니다.
- 암호문의 세 번째 블록을 첫 번째 블록과 동일하게 수정합니다.
- 수정한 암호문을 해독하고 유효하지 않은 평문을 얻습니다.
- 유효하지 않은 평문의 첫 번째와 세 번째 블록을 XOR합니다.
- 결과는 iv(key)와 동일합니다.
위의 블로그를 따라해줬다.

먼저 48byte만큼의 평문을 만들어서 encrypt해준다.
암호문은 15d6ec4f9bfddc3e6af50a9e531037d3926d4dc474a59e386c0002f7e9273aa0379ba03f3ea2ba57f51b89dcb280e480
이다.
해당 암호문의 두번째 블록은 모두 0으로, 세번째 블록은 첫번째 블록으로 만들어준다. 수정하면 다음과 같다.
15d6ec4f9bfddc3e6af50a9e531037d30000000000000000000000000000000015d6ec4f9bfddc3e6af50a9e531037d3
이를 Receive에 넣어서 복호화해준다.

위와 같이 유효하지 않은 평문이라고 나온다.

위의 유효하지 않은 평문에서 첫번째 블록과 세번째 블록을 xor 해준다.

xor 값은 iv이기에 Get_flag에 넣어주면 flag가 나온다!

성공! 어떻게 이것이 가능할까?

먼저 첫번째 블록과 iv를 xor하게 되고, 세번째 블록과 두번째 블록의 암호문을 xor하게 되는데 두번째 암호문이 모두 0이었다. 따라서 이 상태에서 첫번째 블록과 세번째 블록은 같았기에 둘을 xor하면 iv가 나오게 되는 것이다.
따라서 iv는 key와 동일하게 하지 않도록 주의해야 한다.

🚩 flag: crypto{50m3_p30pl3_d0n7_7h1nk_IV_15_1mp0r74n7_?}