KVE-2018-0713 분석

차현수·2023년 9월 2일
0

취약점 요약

알집에서 EGG 파일에 암호를 설정할 때, 난수로 채워져야 하는 부분이 잘못된 타입 캐스팅 때문에 항상 0 또는 1로 결정되는 취약점으로, 이로 인해 난수로 채워지는 부분에 대해 256^10의 경우의 수가 발생해야 하는데, 실제로는 2^10의 경우의 수만 발생하게 되어, Known Plaintext Attack에 대한 공격 복잡도가 크게 감소합니다. 또한 기본옵션인 “최적압축” 옵션으로 특정 확장자(docx, pptx, xlsx, png, 7z, gz 등.) 의 파일이 같이 압축될 시에는 공격의 복잡도가 추가로 감소합니다.

영향 받는 버전

  • 알집 8.0.5.0 ~ 10.81
  • 최신버전은 12.17 버전으로, 취약점이 패치된지 오랜시간이 지나 악용가능성이 낮다고 판단하여 공개합니다.

영향 받는 파일

  • 알집 8.0.5.0 ~ 10.81 버전으로 압축된 암호가 걸린 EGG 파일 (기본옵션으로 압축된 경우)

취약점 설명

EGG 파일은 압축파일에 비밀번호를 설정했을 때 아래 3가지 옵션을 지원합니다.

  • Zip 2.0 Compatible (기본값)
  • AES-128
  • AES-256

이 중 Zip 2.0 Compatible 옵션은 ZipCrypto 혹은 PKZip StreamCipher 라고 불리는 암호를 의미하며, Zip 파일에서도 사용됩니다.

해당 암호의 간단한 Python 구현은 아래와 같습니다.

class PKZIPStreamCipher:
    def __init__(self, password):
        self.keys = [0x12345678, 0x23456789, 0x34567890]
        for char in password:
            self.update_keys(char)
        
    def update_keys(self, char):
        self.keys[0] = self.crc32(self.keys[0], char)
        self.keys[1] = (self.keys[1] + (self.keys[0] & 0xFF)) & 0xFFFFFFFF
        self.keys[1] = (self.keys[1] * 0x08088405 + 1) & 0xFFFFFFFF
        self.keys[2] = self.crc32(self.keys[2], self.keys[1] >> 24)
        
    def crc32(self, old_crc, char):
        
        POLY = 0xedb88320
        crc = old_crc ^ char
        for _ in range(8):
            if crc & 1:
                crc = (crc >> 1) ^ POLY
            else:
                crc >>= 1
        return crc & 0xFFFFFFFF

    def decrypt_byte(self):
        temp = (self.keys[2] & 0xFFFF) | 2
        return ((temp * (temp ^ 1)) >> 8) & 0xFF
        
    def encrypt(self, plaintext):
        ciphertext = []
        for char in plaintext:
            k = char ^ self.decrypt_byte()
            self.update_keys(char)
            ciphertext.append(k)
        return bytes(ciphertext)

    def decrypt(self, ciphertext):
        plaintext = []
        for char in ciphertext:
            k = char ^ self.decrypt_byte()
            self.update_keys(k)
            plaintext.append(k)
        return bytes(plaintext)


password = b"452345gedH"
cipher = PKZIPStreamCipher(password)
encrypted_data = cipher.encrypt(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7B\x74")
print(encrypted_data)

구현에서 보는 바와 같이 태생이 스트림 암호이기 때문에, 압축파일에서 사용할 때는 실제 데이터 암호화에 앞서 무작위한 값 12바이트(혹은 무작위 값 10바이트 + 2바이트 CRC로 구성될 수 있음.) 를 암호화 한 후 사용합니다. (참고 : 7-zip의 해당 부분 구현)

이 암호는 1994년 Biham 과 Kocher에 의해 Known plaintext attack에 취약함이 알려졌습니다. 하지만 해당 공격에서 이야기하는 평문은 압축된 데이터이기 때문에, 데이터를 압축(Deflate 등) 해서 저장하였다면, 특정 파일 전체를 아는것이 아닐 경우, 파일 헤더등 파일 내용의 일부를 알아도 공격이 쉽지 않습니다.

한편, 알집으로 압축할 때, EGG 포맷에서는 "최적압축"이 기본옵션인데, 이 기능은 확장자 별로 압축 알고리즘을 다르게 적용하는 기능입니다.
(개인적으로 이 기능은 확장자가 아니라 파일의 엔트로피값을 이용하여 구현하여야 한다고 생각합니다.)

알고리즘확장자
LZMA.ppt, .xls, .doc, .ani, .ico, .cur, .pcx, .emf,
AZO.sys, .com,
Store.ace, .alz, .arc, .arj, .bz, .bz2, .cab, .egg, .ice, .ear, .war, .gz, .ha, .jar, .lha, .lzh, .pak, .rar, .tbz, .tbz2, .tgz, .7z, .z, .zip, .zoo, .docx, .xlsx, .pptx, .jpg, .jpeg, .png, .gif, .ape, .mp4, .mov, .flac, .flv, .mp3, .ogg, .wma, .wmv
Deflate나머지

이 중 Store는 압축 없이 저장하는 것을 의미합니다.

Store로 압축하는 확장자중에서는 고정된 헤더를 쓰는 파일이 많으며, 정리하면 아래와 같습니다.

확장자고정된헤더
ace-
alz41 4C 5A 01 0A 00 00 00 42 4C 5A 01 (12바이트)
arc1A 02 (2바이트) 또는
1A 03 (2바이트) 또는
1A 04 (2바이트) 또는
1A 08 (2바이트) 또는
1A 09 (2바이트)
arj-
bz-
bz242 5A 68 39 31 41 59 26 53 59 (10바이트)
cab-
egg45 47 47 41 00 01 (6바이트)
ice-
ear-
war-
gz1F 8B 08 00 00 00 00 00 00 03 (10바이트)
ha-
jar50 4B 03 04 (4바이트)
lha-
lzh-
pak-
rar52 61 72 21 1A 07 00 (7바이트)
tbz-
tbz2-
tgz1F 8B 08 00 27 A5 4F 5A 00 03 (10바이트)
7z37 7A BC AF 27 1C 00 03 (8바이트) 또는
37 7A BC AF 27 1C 00 04 (8바이트)
z1F 9D 90 (3바이트)
zip50 4B 03 04 (4바이트)
zoo5A 4F 4F 20 (4바이트)
pptx, docx, xlsx50 4B 03 04 14 00 06 00 08 00 00 00 21 00 (14바이트)
jpgFF D8 FF DB (4바이트) 또는
FF D8 FF E0 00 10 4A 46 49 46 00 01(12바이트) 또는
FF D8 FF E1 (4바이트)
jpegjpg와 동일
png89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 (16바이트)
gif47 49 46 38 39 61 (6바이트) 또는
47 49 46 38 39 61 (6바이트)
ape-
mp400 00 00 18 66 74 79 70 (8바이트) 또는
33 67 70 35 (4바이트)
mov6D 64 61 74 (4바이트)
flac66 4C 61 43 00 00 00 22 (4바이트)
flv46 4C 56 01 (4바이트)
mp349 44 33 (3바이트)
ogg4F 67 67 53 (4바이트)
wma, wmv30 26 B2 75 8E 66 CF (7바이트) 또는
30 26 B2 75 8E 66 CF 11 A6 D9 00 AA 00 62 CE 6C (16바이트)

한편, 무작위한 데이터를 넣어야 할 부분을 분석하여 보면, rand() 함수가 사용되는 것을 알 수 있습니다.

이것 자체로도 문제가 있지만, 잘못된 type-casting 으로 인해, (rand() % 0x7fff) 의 MSB 비트만 남게 되어, 경우의수가 크게 감소합니다.

따라서 ZIP Plaintext Attack 의 전제조건인 파일의 일부 내용을 알 필요 없이도 Known Plaintext attack이 가능합니다.

또한 위에서 설명한 "최적압축" 기능 때문에 일부 확장자의 파일이 같이 압축된 경우, 공격에 소요되는 시간을 크게 단축시킬 수 있습니다.

POC

파일 내용을 아주 조금 알 수 있는 경우 (7z)

파일 내용을 조금 알 수 있는 경우 (pptx)

파일 내용을 전혀 모르는 경우 (txt)

주저리주저리

이 취약점은 제가 처음으로 버그바운티를 통해 보상금을 받은 취약점입니다.

보상금으로는 270만원을 받았는데, 당시 고등학교 2학년이였던 저에겐 꽤 큰 돈이였습니다.

보상금은 몇달 뒤에 모스크바에서 열린 한 해킹대회의 본선에 참여할 때 여행경비로 잘 썼습니다.

슬프게도 당시의 보고서와 POC코드를 분실했습니다.ㅜㅜ

남아있는 영상과 프로그램을 바탕으로 글을 작성했습니다.

profile
Hacker

0개의 댓글

관련 채용 정보