+1

Giới thiệu về kĩ thuật tấn công AES-ECB Oracle

Trong bảo mật thông tin, việc hiểu rõ các phương thức mã hóa và cách chúng có thể bị tấn công là vô cùng quan trọng. Một trong những kỹ thuật tấn công phổ biến và dễ hiểu là tấn công AES ECB Oracle. Bài viết này sẽ giúp bạn hiểu rõ về phương pháp này và tầm quan trọng của việc sử dụng các chế độ mã hóa an toàn hơn.

AES ECB Là Gì?

AES (Advanced Encryption Standard) là một trong những thuật toán mã hóa đối xứng phổ biến nhất, được sử dụng rộng rãi trong việc bảo mật dữ liệu. Chế độ ECB (Electronic Codebook) là một trong những chế độ hoạt động của AES. Trong chế độ này, mỗi khối plaintext được mã hóa độc lập thành một khối ciphertext tương ứng. Mặc dù ECB dễ hiểu và dễ triển khai, nhưng nó có một nhược điểm lớn: cùng một khối plaintext sẽ luôn tạo ra cùng một khối ciphertext khi sử dụng cùng một khóa. Điều này dẫn đến việc dễ dàng phát hiện các mẫu trong dữ liệu, khiến nó dễ bị tấn công hơn. image.png

Oracle Attack Là Gì?

Oracle trong ngữ cảnh bảo mật là một hệ thống hoặc dịch vụ cung cấp thông tin phản hồi về kết quả của một thao tác mã hóa hoặc giải mã mà không tiết lộ trực tiếp khóa bí mật. Trong tấn công AES ECB Oracle, tin tặc khai thác thông tin phản hồi từ oracle để từng bước giải mã hoặc mã hóa dữ liệu.

Cách Thức Hoạt Động Của Tấn Công AES ECB Oracle

  • Bước 1: Phát Hiện Kích Thước Khối Mã Hóa Đầu tiên, ta cần xác định kích thước khối mã hóa của AES. Bằng cách gửi các chuỗi có độ dài tăng dần và quan sát sự thay đổi trong chuỗi ciphertext trả về, ta có thể phát hiện kích thước khối là bao nhiêu (thường là 16 bytes - 128 bits, đây là đặc điểm chuẩn của AES).

  • Bước 2: Xây Dựng Oracle Kế tiếp, ta sẽ lợi dụng việc biết trước một phần của plaintext để xây dựng một oracle. Giả sử ta đã biết một phần của plaintext là "KnownText", ta có thể gửi chuỗi P = "KnownText" + "A" * (16 - len("KnownText")) đến dịch vụ mã hóa. Kết quả trả về sẽ là ciphertext của khối đầu tiên chứa "KnownText" + "A" * (16 - len("KnownText")).

  • Bước 3: Tấn Công Từng Byte Tiếp theo, ta sẽ thử từng ký tự có thể có (256 ký tự từ 0x00 đến 0xFF) vào vị trí tiếp theo của "KnownText", giả sử "KnownText" = "KnownText" + chr(0x00), "KnownText" = "KnownText" + chr(0x01),... cho đến khi tìm được ký tự mà khi mã hóa, khối đầu tiên của ciphertext trùng khớp với ciphertext của chuỗi ban đầu. Khi đó, ta đã xác định được ký tự tiếp theo của plaintext. Bằng cách lặp lại quá trình này, ta có thể từng bước giải mã toàn bộ dữ liệu bí mật.

Ví Dụ Thực Tế

Hãy cùng xem qua một ví dụ minh họa từ một bài CTF để hiểu rõ hơn về cách tấn công này hoạt động.

Mô Tả Bài Toán

Giả sử ta có một dịch vụ web cho phép mã hóa một chuỗi bất kỳ bằng AES ECB với một khóa bí mật không được tiết lộ. Dịch vụ này cung cấp kết quả là chuỗi ciphertext tương ứng. Nhiệm vụ của ta là giải mã được một đoạn dữ liệu bí mật (flag) mà dịch vụ này mã hóa.

Mã nguồn server

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import string
import random
from binascii import hexlify, unhexlify


def get_key():
    list = string.ascii_letters + string.digits
    key = random.choices(list, k=16)
    key = "".join(key)
    return key


def encrypt(data: str, key: str):
    e = AES.new(key.encode(), AES.MODE_ECB)
    return hexlify(e.encrypt(pad(data.encode(), 16))).decode()


flag = open("flag.txt", "r").read().strip()
secret_key = get_key()
flag = encrypt(flag, secret_key)
print("Flag encrypt: " + flag)
try:
    for i in range(1100):
        data = input("Message you want to encode: ")
        data += secret_key
        print("Cipher: " + encrypt(data, secret_key))
except Exception as e:
    print("Something error!")
    exit(0)

Phân tích bài toán

  • Hàm get_key(): trả về một string có độ dài 16 kí tự được lấy ngẫu nhiên từ các chữ cái, chữ số.
  • Hàm encrypt(data, key): thực hiện mã hóa dữ liệu đầu vào data với khóa key bằng thuật toán AES mode ECB. Dữ liệu trước khi mã hóa sẽ được cộng thêm chuỗi padding để đảm bảo độ dài dữ liệu là bội số của 16. Kết quả trả về dưới dạng hex.
  • Server thực hiện mã hóa flag sau đó trả về phía người dùng bản mã của flag sau khi mã hóa.
  • Server nhận input từ người dùng, sau đó nối chuỗi đầu vào từ người dùng với secret_key. Tiếp theo sẽ thực hiện mã hóa sau khi đã nối chuỗi và trả kết quả về phía người dùng. Tối đa người dùng có thể mã hóa 1100 lần với mỗi lần kết nối.

Quá trình exploit

  1. Xác định kích thước khối

    Dựa vào độ dài khóa là 16 byte từ mã nguồn ta có thể biết kích thước khối mã hóa là 16 byte. Hoặc ta có thể thử như sau:

    • Gửi lần lượt các chuỗi có độ dài từ 1-15 byte, nhận thấy dữ liệu trả về có độ dài 32 byte
    • Gửi một chuỗi có độ dài 16 byte, lúc này dữ liệu trả về có độ dài là 48 byte. image.png

    => độ dài khối mã hóa là 48-32=16 byte.

  2. Brute force từng byte tại từng vị trí

    Ban đầu secret_key=""

    • Xác định giá trị byte đầu tiên của secret_key: tạo chuỗi có độ dài (16-1) + secret_key, ví dụ aaaaaaaaaaaaaaa (15 byte). Gửi chuỗi này lên server. Chuỗi này sẽ được nối với secret_key trước khi mã hóa: aaaaaaaaaaaaaaa+secret_key[0][1][...][15]. Vì độ dài khối mã hóa là 16 byte nên khối mã hóa đầu tiên sẽ là của bản rõ aaaaaaaaaaaaaaa+secret_key[0]. Lưu lại khối 16 byte đầu tiên trong ciphertext mà server trả về, tạm gọi là X. Tiếp theo thực hiện nối chuỗi aaaaaaaaaaaaaaa (15 byte) với từng byte mà secret_key[0] có thể nhận: là các chữ cái, chữ số. Ta sẽ được các chuỗi như: aaaaaaaaaaaaaaa1, aaaaaaaaaaaaaaa2, aaaaaaaaaaaaaaaA,aaaaaaaaaaaaaaaB,.... Gửi từng chuỗi lên server và so sánh khối 16 byte đầu tiên trong ciphertext mà server trả về với khối X, nếu 2 khối có cùng giá trị thì giá trị secret_key[0] chính là giá trị byte cuối cùng trong chuỗi mà ta đang brute force vì khi sử dụng mode ECB thì các plaintext giống nhau sẽ cho ra các ciphertext giống nhau.
    • Xác định giá trị byte thứ 2 của secret_key: tạo chuỗi có độ dài (16-2) + secret_key, ví dụ aaaaaaaaaaaaaa (14 byte). Gửi chuỗi này lên server. Chuỗi này sẽ được nối với secret_key trước khi mã hóa: aaaaaaaaaaaaaaa+secret_key[0][1][...][15]. Vì độ dài khối mã hóa là 16 byte nên khối mã hóa đầu tiên sẽ là của bản rõ aaaaaaaaaaaaaa(14 byte)+secret_key[0][1] trong đó secret_key[0] đã biết. Lưu lại khối 16 byte đầu tiên trong ciphertext mà server trả về, tạm gọi là X. Tiếp theo thực hiện nối chuỗi aaaaaaaaaaaaaa(14 byte) + secret_key[0] với từng byte mà secret_key[1] có thể nhận: là các chữ cái, chữ số. Ta sẽ được các chuỗi như: aaaaaaaaaaaaaa+secret_key[0]1, aaaaaaaaaaaaaa+secret_key[0]2, aaaaaaaaaaaaaa+secret_key[0]A,aaaaaaaaaaaaaa+secret_key[0]B,.... Gửi từng chuỗi lên server và so sánh khối 16 byte đầu tiên trong ciphertext mà server trả về với khối X, nếu 2 khối có cùng giá trị thì giá trị secret_key[1] chính là giá trị byte cuối cùng trong chuỗi mà ta đang brute force vì khi sử dụng mode ECB thì các plaintext giống nhau sẽ cho ra các ciphertext giống nhau như ở bước trên.
  3. Lặp lại quá trình trên cho đến khi lấy được khóa secret_key và thực hiện giải mã bản mã của flag.

Script exploit

Các bạn có thể dựng một server bằng tcpserver và sử dụng script sau để thử

from pwn import *
import string
from tqdm import tqdm
from Crypto.Cipher import AES
from binascii import unhexlify
from Crypto.Util.Padding import unpad


def decrypt(cipher, key: str):
    d = AES.new(key.encode(), AES.MODE_ECB)
    return d.decrypt(unhexlify(cipher))


list_char_secret_key = string.ascii_letters + string.digits
r = remote("192.168.1.4", 8000) #thay đổi địa chỉ phù hợp
r.recvuntil(b"Flag encrypt: ")
flag_encrypt = r.recvline().decode().strip()
secret_key = ""
for i in tqdm(range(16)):
    payload = 'a' * (15 - i)
    r.recvuntil(b"encode: ")
    r.sendline(payload.encode())
    r.recvuntil(b"Cipher: ")
    cipher = r.recvline().decode().strip()
    for char in list_char_secret_key:
        new_payload = payload + secret_key + char
        r.recvuntil(b"encode: ")
        r.sendline(new_payload.encode())
        r.recvuntil(b"Cipher: ")
        new_cipher = r.recvline().decode().strip()
        if (new_cipher[:32] == cipher[:32]):
            secret_key += char
            break
    print("Secret key recover: " + secret_key)

print(unpad(decrypt(flag_encrypt, secret_key), 16))

Kết Luận

Tấn công AES ECB Oracle cho thấy rõ ràng những nhược điểm của chế độ mã hóa ECB và tầm quan trọng của việc sử dụng các chế độ mã hóa an toàn hơn như CBC (Cipher Block Chaining) hay GCM (Galois/Counter Mode). Hiểu rõ về các kỹ thuật tấn công này không chỉ giúp chúng ta phòng tránh mà còn nâng cao khả năng thiết kế hệ thống bảo mật an toàn hơn.

Việc nắm vững các nguyên tắc và kỹ thuật tấn công như AES ECB Oracle là bước đầu để trở thành một chuyên gia bảo mật thông tin thực thụ. Hy vọng qua bài viết này, bạn đã có cái nhìn rõ ràng hơn về phương pháp tấn công này và cách bảo vệ hệ thống của mình trước các lỗ hổng bảo mật.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí