▌ TRANSMISSION · [CONCEPT]

TGS(Ticket Granting Service ticket)


Kerberoasting에서 $krb5tgs$는 대체 뭘 크랙하는 걸까?

아래 설명은 Kerberoasting 이해를 위한 단순화된 흐름이다. 실제 Kerberos 암호화 타입별 세부 key usage, PAC signature, salt 생성 방식은 구현체와 Windows 버전에 따라 달라질 수 있다.

Kerberoasting을 처음 보면 약간 이상할 수 있다.

아니, 나는 서비스 계정 비밀번호를 모르는데
왜 TGS를 받아서 hashcat에 넣으면 비밀번호가 나오는 걸까?

그 이유는 다음과 같다.

TGS 안에는 서비스 계정의 키로 암호화된 ticket 부분이 있고,
그 서비스 계정 키는 결국 서비스 계정 비밀번호에서 파생된 값이기 때문

그래서 공격자는 받은 티켓을 가지고 비밀번호 후보를 하나씩 넣어본다.

후보 비밀번호

Kerberos key 생성

티켓 복호화 시도

정상적인 Kerberos 구조가 나오면?

크랙 성공

이게 Kerberoasting 크래킹의 본질이다.


전체 흐름

대충 그림으로 보면 이런 느낌이다.

서비스 계정 비밀번호


String-to-Key 함수


서비스 계정 Kerberos key


KDC가 만든 ticket 내용물 암호화


$krb5tgs$ 안의 긴 hex blob

hashcat이 크랙하는 건 “비밀번호 해시”처럼 보이지만, 정확히는 서비스 계정 키로 암호화된 Kerberos ticket blob이다.


RC4-HMAC가 빨리 깨지는 이유

RC4-HMAC, 즉 etype 23은 구조가 단순하다.

서비스 계정 비밀번호가 있으면

def rc4_key(password):
    pw_utf16 = password.encode("utf-16-le")
    key = md4(pw_utf16)
    return key

이렇게 돌아가는게 끝이다.

이 값은 사실상 NTLM hash와 같은 계산 결과다.

그래서 AD 환경에서 RC4 Kerberos key는 서비스 계정의 NTLM hash라고 봐도 된다.

password
  ↓ UTF-16LE
  ↓ MD4
NTLM hash

RC4-HMAC Kerberos key

salt도 없고, PBKDF2 반복도 없다.
그래서 후보 비밀번호 하나를 검증하는 비용이 매우 낮다.

이게 RC4 Kerberoasting이 빠른 이유다.


AES가 느린 이유?

AES etype 17, 18은 RC4처럼 간단하지 않다.

단순화하면 이런 흐름이다.

def aes_key(password, realm, principal):
    salt = realm + principal  # 실제 환경에서는 principal 형태에 따라 달라질 수 있음

    initial_key = pbkdf2_hmac_sha1(
        password=password,
        salt=salt,
        iterations=4096
    )

    final_key = derive_key(initial_key, b"kerberos")
    return final_key

여기서 중요한 건 두 가지이다.

1. salt가 들어감
2. PBKDF2-HMAC-SHA1을 4096번 반복함

RC4는 MD4 한 번으로 끝나는데, AES는 후보 하나마다 PBKDF2를 4096번 돌려야 한다.

그래서 크래킹 속도가 크게 떨어진다.

정확한 배율은 장비와 구현체마다 다르지만, Kerberoasting에서 AES 티켓이 RC4 티켓보다 훨씬 느린 이유가 바로 이것 때문이다.

추가로 AES256은 AES128보다 긴 32바이트 키를 사용하는데,
여기서 Kerberoasting에서 체감되는 느림은 AES128과 AES256의 차이라기보다는, RC4처럼 MD4 한 번으로 끝나는 구조가 아니라 PBKDF2-HMAC-SHA1 4096회 반복을 거친다는 것때문이다.


TGS info

KDC가 서비스 티켓을 만들 때, 내부에는 대략 이런 정보가 들어간다.

EncryptedTicketPart {
    flags
    session key
    client realm
    client name
    ticket start time
    ticket end time
    renew time
    client address
    authorization data
}

(참고로, Kerberoasting에서 hashcat에 넣는 부분이 바로 서비스 계정 키로 암호화된 ticket의 EncTicketPart이다.)

여기서 중요한 첫 번째는 session key다.

session key는 클라이언트와 서비스가 이후 통신할 때 사용할 임시 키로,
KDC가 매번 새로 만들어 넣는다.

요청자도 이 세션 키의 사본을 TGS-REP의 클라이언트용 암호화 영역으로 받는다.

두 번째는 PAC이다.

PAC에는 사용자의 SID, 그룹 멤버십, 권한 정보 같은 것이 들어간다.

PAC {
    user SID
    group SIDs
    username
    logon time
    signatures
}

서비스는 이 PAC를 보고 이 사용자가 어떤 권한을 가진 사람인지 판단할 수 있다.

다만 PAC 구조와 서명 방식은 Windows 버전과 보안 업데이트에 따라 세부적으로 달라질 수 있다.
여기서는 Kerberoasting 이해에 필요한 정도로만 보면 된다.


KDC 암호화 과정

RC4-HMAC 기준으로 단순화하면 이런 흐름이다.

1. ticket 평문 준비
2. 앞에 confounder 추가
3. checksum 계산
4. checksum 기반으로 RC4 암호화 키 파생
5. RC4로 암호화
6. checksum + ciphertext 형태로 저장

위에서 언급된 confounder는 랜덤 바이트로,
같은 평문을 같은 키로 암호화해도 매번 결과가 달라지게 만드는 역할을 한다.

그리고 checksum은 변조 검증에 쓰인다.

공격자가 암호문을 마음대로 바꾸면, 복호화는 될 수 있어도 checksum 검증에서 깨진다.


$krb5tgs$23$... 형태

Impacket이나 Rubeus는 TGS-REP에서 필요한 부분만 뽑아 hashcat 형식으로 정리한다.

$krb5tgs$23$*svc_winrm$VOLEUR.HTB$voleur.htb/svc_winrm*$checksum$ciphertext

각 필드는 이런 의미다.

23            → etype 23, RC4-HMAC
svc_winrm     → 서비스 계정
VOLEUR.HTB    → realm
SPN           → service principal name
checksum      → 무결성 검증용 값
ciphertext    → 서비스 계정 키로 암호화된 ticket blob

hashcat은 여기서 checksum과 ciphertext를 이용해 후보 비밀번호를 검증한다.


hashcat 동작 과정

아까 언급한 것처럼 후보 비밀번호 하나하나를 KDC가 했던 과정대로 따라 해본다.

for candidate in wordlist:
    key = string_to_key(candidate)

    decrypted = decrypt_ticket(key, ciphertext)

    if checksum_ok(decrypted) and looks_like_kerberos_ticket(decrypted):
        print("found:", candidate)
        break
후보 비밀번호

서비스 계정 key 생성

암호문 복호화 시도

checksum 검증

ASN.1 구조 확인

성공하면 비밀번호 찾음

정상적인 Kerberos ticket 구조가 나오고 checksum이 맞으면, 그 후보 비밀번호를 출력해주는 것이다.


예시 흐름

예를 들어 서비스 계정 비밀번호가 이런 값이라고 해보자.

AFireInsidedeOzarctica980219afi

KDC 쪽에서는 대략 이런 일이 일어난다.

서비스 계정 비밀번호

String-to-Key

서비스 계정 Kerberos key

KDC가 서비스 티켓 생성

서비스 계정 key로 ticket 암호화

TGS-REP에 포함해서 클라이언트에게 전달

공격자 쪽에서는 이걸 가져와서 hashcat 형식으로 바꾼다.

TGS-REP에서 암호화된 ticket 부분 추출

$krb5tgs$23$... 형식으로 저장

hashcat에 입력

wordlist 후보를 하나씩 시도

hashcat이 후보 비밀번호(정답) AFireInsidedeOzarctica980219afi이 사용할 때

후보 비밀번호

같은 String-to-Key 결과 생성

ticket 복호화 성공

checksum 일치

ASN.1 구조 정상

[+] Found

이렇게 된다.


한 줄 요약

Kerberoasting은 서비스 계정 키로 암호화된 TGS ticket을 받아온 뒤, 비밀번호 후보들로 같은 key derivation과 복호화 과정을 반복하면서 정상 Kerberos 구조와 checksum이 나오는 후보를 찾는 공격이다. (오프라인 크래킹)


핵심 정리

RC4-HMAC
- etype 23
- 비밀번호 → UTF-16LE → MD4
- 결과가 NTLM hash와 사실상 동일
- salt 없음
- 반복 없음
- 크래킹이 빠름

AES
- etype 17 / 18
- salt 사용
- PBKDF2-HMAC-SHA1 4096회 반복
- RC4보다 훨씬 느림

$krb5tgs$
- 서비스 계정 키로 암호화된 ticket blob
- hashcat은 후보 비밀번호로 key를 만들고 복호화 검증을 반복함

← ALL POSTS