projectdiscovery/nuclei · error
DsGetNCChanges returned no objects
Error message
DsGetNCChanges returned no objects
What it means
The replication RPC completed without a transport-level fault but the response contained zero objects, so there is no secret to return. This happens when the resolved DN points at an object that carries no replicated secret (a container or organizational unit), or the DC silently filtered the object out.
Source
Thrown at pkg/js/libs/secretsdump/secretsdump.go:167
// Resolve target -> DN if it doesn't already look like one.
userDN := target
if len(target) < 3 || (target[:3] != "CN=" && target[:3] != "cn=") {
cracked, err := gpdrs.DsCrackNames(rpc, bind.Handle, 7 /* DS_NT4_ACCOUNT_NAME */, 1 /* DS_FQDN_1779_NAME */, []string{c.Domain + "\\" + target})
if err != nil || len(cracked) == 0 || cracked[0].Name == "" {
cracked, err = gpdrs.DsCrackNames(rpc, bind.Handle, 11 /* DS_UNIQUE_ID_NAME (SID) */, 1, []string{target})
if err != nil || len(cracked) == 0 || cracked[0].Name == "" {
return nil, fmt.Errorf("could not resolve %q to a DN", target)
}
}
userDN = cracked[0].Name
}
res, err := gpdrs.DsGetNCChanges(rpc, bind.Handle, domainDN, userDN, dcInfo.NtdsDsaObjectGuid, rpc.GetSessionKey())
if err != nil {
return nil, fmt.Errorf("DsGetNCChanges: %w", err)
}
if len(res.Objects) == 0 {
return nil, fmt.Errorf("DsGetNCChanges returned no objects")
}
o := res.Objects[0]
out := &Secret{
SAMAccountName: o.SAMAccountName,
DistinguishedName: o.DN,
RID: o.RID,
NTHash: hex.EncodeToString(o.NTHash),
LMHash: hex.EncodeToString(o.LMHash),
UserAccountControl: o.UserAccountControl,
PwdLastSet: o.PwdLastSet,
}
for _, h := range o.NTHashHistory {
out.NTHashHistory = append(out.NTHashHistory, hex.EncodeToString(h))
}
for _, h := range o.LMHashHistory {
out.LMHashHistory = append(out.LMHashHistory, hex.EncodeToString(h))
}
return out, nilView on GitHub (pinned to 265b3a3dec)
Solutions
- Target a concrete security principal (user or computer sAMAccountName)
- Let the library resolve the target from sAMAccountName or SID instead of hand-writing the DN
- Re-verify the object still exists via LDAP immediately before the sync
Example fix
// before
const s = c.DCSync('OU=Employees,DC=acme,DC=local'); // OU carries no secrets
// after
const s = c.DCSync('jdoe'); Defensive patterns
Strategy: validation
Validate before calling
// Only sync objects that are security principals
if strings.HasPrefix(dn, "OU=") || !isPrincipal(dn) {
return fmt.Errorf("refusing to sync non-principal DN %q", dn)
} Type guard
func looksLikePrincipalDN(dn string) bool {
return strings.HasPrefix(dn, "CN=") && !strings.HasPrefix(dn, "OU=")
} Try / catch
if err != nil && strings.Contains(err.Error(), "returned no objects") {
// DN resolved but no secret replicated: re-resolve target via sAMAccountName and retry once
} Prevention
- Prefer sAMAccountName/SID targets over hand-written DNs
- Skip container-class DNs (OU=, CN=LostAndFound) at the call site
- Re-check object existence via LDAP immediately before replication
When it happens
Trigger: Target DN resolved to OU=... or CN=LostAndFound instead of a user/computer object; account exists but its secret attributes are excluded (e.g. RODC filtered attribute set); race where the object was deleted between crack-names and replication.
Common situations: Passing a raw DN that looks right but is an OU; syncing machine accounts on DCs with filtered attribute sets; chasing accounts found in stale enumeration data.
Related errors
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/ccd91317f390745b.
Report an issue: GitHub.