Cover the GetValueData guard added in the previous commit with two
table-driven tests: the rejection path (dataLen ∈ {5, 8, 0xFF,
0x7FFFFFFF} with resident bit set) confirms the guard fires without
panicking and surfaces a helpful error; the happy path (dataLen ∈
{1, 2, 3, 4}) confirms the inline DataOffset bytes are returned
correctly so the guard didn't regress valid hives.
The matching guards in SetValueData (resident + non-resident write
paths) and enumSubKeys (riSig branch) require full NK/VK/sub-list
hive synthesis to exercise end-to-end; they're structurally identical
to the read-path guard and are validated by build/vet plus the lab
regression check on secretsdump. Worth a follow-up to extend the
synth helpers and cover them directly.
Four parser-panic / silent-corruption bugs in pkg/registry/hive.go,
all reachable from attacker-controlled hive bytes:
1. GetValueData resident branch: VKRecord.DataLen lower 31 bits are
read verbatim and used to slice a 4-byte buffer at [:dataLen]. A
DataLen of 0x80000005 panics with "slice bounds out of range".
Found by kajaaz using Zorya (issue #25); fix matches her suggested
one-line bounds check.
2. SetValueData resident branch (structurally identical to #1): the
existing len(newData) == dataLen check doesn't enforce the 4-byte
cap, so a hostile dataLen=5 with a matching 5-byte newData slices
one byte past the DataOffset field into the adjacent cell. Same
guard.
3. SetValueData non-resident branch: vk.DataOffset is attacker-
controlled and the code does copy(h.data[dataPos:dataPos+dataLen],
newData) without ever validating the destination. Hostile offsets
either panic on out-of-bounds or silently scribble over arbitrary
hive bytes (a value of 0xFFFFF000 lands near the regf header). Route
through readCell (which validates the cell header and bounds) and
verify dataLen fits before mutating.
4. enumSubKeys riSig branch: readCell can return a slice shorter than
the 4-byte cell header (minimum-size cell with no usable bytes), so
the immediate subCell[0:2] / subCell[2:4] reads can panic on a
malformed sub-list. The sibling code at line 397/437 already guards
the analogous index; mirror it here.
Fixes#25 plus three structurally similar bugs surfaced while patching.
The embedded gokrb5/v8 library hard-coded net.DialTimeout for AS/TGS
exchanges, bypassing -proxy and leaking the operator's source IP to the
KDC (UDP/88 first, TCP/88 fallback). The DCERPC Kerberos auth path used
a separate library (oiweiwei/gokrb5.fork/v9 via go-msrpc) that leaked the
same way.
Vendor jcmturner/gokrb5/v8 in-tree at pkg/third_party/gokrb5 with a
required KDCDialer first argument on every client constructor, so
proxy-bypass becomes a compile error. Wire kerberos.TransportKDCDialer
everywhere a gokrb5 client is built. Stamp udp_preference_limit=1 and
dns_lookup_kdc/realm=false unconditionally so KRB5 is TCP-only and the
OS resolver is never consulted; /etc/krb5.conf and $KRB5_CONFIG are
deliberately not read.
For DCERPC: set krbConfig.KDCDialer on every krb5.Config, pass
dcerpc.WithDialer(transport.ContextDialer{}) on every dcerpc.Dial, and
use the "ncacn_ip_tcp:" StringBinding prefix on the OXID-pivot dial so
go-msrpc's hard-coded pre-dial net.LookupIP is skipped (defers FQDN
resolution to the SOCKS5 proxy).
Verified against a live GOAD lab: 8 Kerberos-touching tools plus 5
NTLM/password/PtH regressions all operate through SOCKS5 with zero
direct packets to the AD subnet. Negative control (no -proxy)
immediately emits direct SYNs to the KDC, confirming both the leak
class and the fix.