뒤늦게 라이트업 쓰느라 사진 찍어둔게 하나도 없어서 운영진분들이 올려주신 깃허브 소스코드 참고해서 작성합니다...
https://github.com/SSTF-Office/SamsungCTF
1. Investigation
문제 설명을 읽어보니 쿠키는 쓸데 없고 취약하다면서 AES 암호화를 이용하여 커스텀 제작한 세션을 만들었으니 자신은 이제 안전하다고 한다.
그리고 문제 풀이에 필요한 웹페이지를 제공해주었다.
들어가보면 다음과 같은 로그인 화면이 나온다.
Register 버튼을 눌러도 관리자만이 계정을 추가할 수 있다면서 아무것도 되지 않았기 때문에 페이지 소스코드에 힌트를 남겨놓지 않을까 해서 살펴보았더니 아니나 다를까 게스트 계정에 대한 아이디/패스워드를 알려주고 있었다.
<!-- guest account: guest / guestpassword -->
주어진 게스트 계정으로 로그인해보면 다음과 같은 페이지를 확인할 수 있다.
오직 관리자만 Flag 를 볼 수 있다고 한다.
저것 외에 별다른 내용이 없기 때문에 View Source 를 눌러보면 다음과 같은 PHP 코드를 확인이 가능하다.
<?php
include "secret.php"; //server_secret, iv, flag
$cookie_name = "SESSION";
if (!isset($_COOKIE[$cookie_name])) {
header('Location: /signin.php');
exit;
}
$cipher="aes-128-ctr";
list($iv, $encrypted_session_data) = explode("|", base64_decode($_COOKIE[$cookie_name]), 2);
$session_data = openssl_decrypt($encrypted_session_data, $cipher, $server_secret, OPENSSL_RAW_DATA, $iv);
list($username, $auth_code) = explode("|", $session_data);
if ($auth_code !== $server_secret) {
die("No hack!");
}
?>
...
<?php
print("Welcome, ".$username." :)<br>");
if ($username === "admin") {
print("Flag: ".$flag);
}else{
print("Only admin can see the flag. Sorry.");
}
?>
대략적인 내용은 이렇다.
AES-128-CTR 암호화 기법을 사용하고 있으며, SESSION 이라는 쿠키값을 Base64 Decoding 하여 나오는 암호문에서 첫번째 파이프 문자('|')를 기준으로 암호문을 나눠 앞은 Initial Vector(iv 혹은 nonce), 뒤는 암호문(encrypted_session_data) 변수에 저장한다.
이해를 돕기 위한 Pseudo-code - (1)
SESSION = 'lAe5TTqLylml7jKOwWLqGXzmoVhzUCYv7g70/0lj3hbWOz2/hUB7eCUJAlWMvSNpEzshaD9PCm2xD7Dz6p005GhOLa4tGlAV0KlL3eqhjm8aQpKqAHA0wQ=='
SESSION = base64_decode(SESSION)
SESSION = SESSION.split('|')
iv, encrypted_session_data = SESSION[0], SESSION[1]
이후에 AES CTR mode 에 iv, server_secret 을 넣고 encrypted_session_data 를 복호화하여 session_data 변수에 저장한다. 다시 파이프 문자('|')를 기준으로 평문을 나누어 앞은 username, 뒤는 auth_code 라는 변수에 저장하여 auth_code 가 server_secret 과 일치하지 않을 시 "No hack!" 이라는 문구를 띄운다.
이해를 돕기 위한 Pseudo-code - (2)
cipher = AES(server_secret, AES.MODE_CTR, nonce=iv, counter=ctr)
session_data = cipher.decrypt(encrypted_session_data)
session_data = session_data.split('|')
username, auth_code = session_data[0], session_data[1]
if auth_code != server_secret:
print("No hack!")
결국 SESSION 이라는 쿠키값은 다음과 같이 구성된다는 것을 알 수 있다:
SESSION = base64_encode(iv | AES-128-CTR_encrypt(username | server_secret))
이해를 돕기 위한 AES CTR-mode 그림 - (3)
초록색 ✅ 는 내가 알고 있는 정보(nonce(=iv), ctr(0, 1, 2, 3...), ciphertext(=encrypted_session_data))
빨간색 ❌ 는 내가 알 수 없는 정보(server_secret, plaintext(=session_data=(username | server_secret)))
server_secret 도 모르고, plaintext 도 모르는데 어떻게 관리자 세션 쿠키를 만들 수 있을까?
2. Solution
위에서 우리가 소스코드 분석을 통해 SESSION 쿠키값이 어떻게 구성되어 있는지 살펴보았다.
SESSION = base64_encode(iv | AES-128-CTR_encrypt(username | server_secret))
iv 값은 Base64 Decoding 을 통해 쉽게 알아낼 수 있으니 제외하고 AES Encrypt 부분을 유의깊게 살펴보자.
바로 위 그림에서도 알 수 있듯이 AES CTR mode 는 (nonce=iv + ctr) 를 server_secret 이라는 passphrase 를 이용해 AES 암호화한 뒤 나온 keystream 과 plaintext 를 XOR 하여 나온 것이 최종적인 암호문(ciphertext)이 되는 기법이다.
또, 잘 생각해보면 plaintext 는 (username | server_secret) 이기 때문에 server_secret 은 우리가 알 수 있는 방법이 없지만, username 의 경우에는 guest 혹은 admin 이 되어야 함을 알 수 있다.
이해를 돕기 위한 그림 - (4)
위 그림을 식으로 표현해보면 다음과 같다:
ciphertext(=encrypted_session_data) = keystream ^ plaintext(=guest | server_secret)
현재 나는 guest 계정으로 로그인해있고 guest 계정에 대한 SESSION 쿠키값을 알고 있다.
여기서 admin 계정에 대한 SESSION 쿠키값을 만드려면 어떻게 해야할까?
먼저 우리가 만들어야할 admin 계정에 대한 ciphertext 는 다음과 같다:
ciphertext(=admin_enc_session_data) = keystream ^ plaintext(=admin | server_secret)
하지만 keystream, server_secret 을 모르기 때문에 ciphertext(=admin_enc_session_data) 를 구할 수 없다.
그렇다면 이미 우리가 알고 있는 guest 계정에 대한 ciphertext(=encrypted_session_data) 를 이용해보면 어떨까?
ciphertext(=encrypted_session_data) = keystream ^ plaintext(=guest | server_secret)
위 식에서 guest 가 5 글자이기 때문에 5 글자만 집중해서 보자.
이해를 돕기 위한 그림 - (5)
위 그림에서 guest 부분을 admin 으로 바꾸기 위해서 먼저 동일한 값으로 XOR 연산하면 결과값이 0 이 되는 특징을 이용한다.
ciphertext(=c1.c2.c3.c4.c5) = keystream(=k1.k2.k3.k4.k5) ^ plaintext(=guest)
위와 같은 식에서 양변에 plaintext(=guest) 를 XOR 해준다.
ciphertext(=c1.c2.c3.c4.c5) ^ plaintext(=guest)
= keystream(=k1.k2.k3.k4.k5) ^ plaintext(=guest) ^ plaintext(=guest)
= keystream(=k1.k2.k3.k4.k5) ^ 0
= keystream(=k1.k2.k3.k4.k5)
=> ciphertext(=c1.c2.c3.c4.c5) ^ plaintext(=guest) = keystream(=k1.k2.k3.k4.k5)
이번에는 양변에 plaintext(=admin) 을 XOR 해준다.
ciphertext(=c1.c2.c3.c4.c5) ^ plaintext(=guest) ^ plaintext(=admin)
= keystream(=k1.k2.k3.k4.k5) ^ plaintext(=admin)
앞서 5 글자만 집중해서 보았기 때문에 원래대로 식을 돌려보면 다음과 같다:
ciphertext(=encrypted_session_data) ^ plaintext(=guest | server_secret) ^ plaintext(=admin | server_secret) = keystream ^ plaintext(=admin | server_secret)
좀 전에 위에서 알아봤던 admin 계정에 대한 ciphertext 를 구할 때의 식과 동일해졌다는 것을 확인할 수 있다:
ciphertext(=admin_enc_session_data) = keystream ^ plaintext(=admin | server_secret)
이제 keystream, server_secret 을 모르더라도 plaintext 앞의 5 글자만 추가적인 XOR 연산을 해주면 admin 계정에 대한 ciphertext 를 구할 수 있다는 것을 알았기 때문에 이를 그대로 코드로 짜면 된다.
참고로 위 과정을 Bit Flipping Attack 이라고 한다.
>> CUSES_solve.py
import base64
import requests
b64sess = 'lAe5TTqLylml7jKOwWLqGXzmoVhzUCYv7g70/0lj3hbWOz2/hUB7eCUJAlWMvSNpEzshaD9PCm2xD7Dz6p005GhOLa4tGlAV0KlL3eqhjm8aQpKqAHA0wQ=='
sess = base64.b64decode(b64sess)
print(sess)
iv = sess[:sess.index(b'|')]
enc = sess[sess.index(b'|') + 1:]
print(iv)
print(enc)
print(len(iv))
print(len(enc))
for i in range(0, len(enc), 16):
print(enc[i:i+16].hex())
url = 'http://cuses.sstf.site/index.php'
i1 = ord('g') ^ ord('a') ^ enc[0]
i2 = ord('u') ^ ord('d') ^ enc[1]
i3 = ord('e') ^ ord('m') ^ enc[2]
i4 = ord('s') ^ ord('i') ^ enc[3]
i5 = ord('t') ^ ord('n') ^ enc[4]
polluted = iv + b'|' + bytes([i1]) + bytes([i2]) + bytes([i3]) + bytes([i4]) + bytes([i5]) + enc[5:]
print(polluted)
b64_pol = base64.b64encode(polluted).decode()
print(b64_pol)
cookie = {"SESSION": b64_pol}
res = requests.get(url, cookies=cookie)
html = res.text
index = html.find('Flag')
print("[+] html: ", html[index:index+70])
# SCTF{T3ll_me_4_r3ally_s3cure_w4y_to_m4na9e_5eSS10ns}
🚩 FLAG : SCTF{T3ll_me_4_r3ally_s3cure_w4y_to_m4na9e_5eSS10ns}
'CTF writeups > Crypto' 카테고리의 다른 글
[n00bzCTF2022] RSA-OOPS (0) | 2022.06.08 |
---|---|
[Cyber Apocalypse CTF 2022] MOVs Like Jagger (0) | 2022.05.23 |
[Cyber Apocalypse CTF 2022] How The Columns Have Turned (0) | 2022.05.20 |
[Cyber Apocalypse CTF 2022] The Three-Eyed Oracle (0) | 2022.05.20 |
[Cyber Apocalypse CTF 2022] Jenny From The Block (0) | 2022.05.20 |