Skip to content

Instantly share code, notes, and snippets.

@project0
Last active May 6, 2024 09:47
Show Gist options
  • Save project0/61c13130563cf7f595e031d54fe55aab to your computer and use it in GitHub Desktop.
Save project0/61c13130563cf7f595e031d54fe55aab to your computer and use it in GitHub Desktop.
Go AD password reset
package passwordresetservice
import (
"crypto/tls"
"fmt"
ldap "github.com/go-ldap/ldap"
"golang.org/x/text/encoding/unicode"
ber "gopkg.in/asn1-ber.v1"
)
// need at least v3 of library
const (
ldapAttrAccountName = "sAMAccountName"
ldapAttrDN = "dn"
ldapAttrUAC = "userAccountControl"
ldapAttrUPN = "userPrincipalName" // username@logon.domain
ldapAttrEmail = "mail"
ldapAttrUnicodePw = "unicodePwd"
controlTypeLdapServerPolicyHints = "1.2.840.113556.1.4.2239"
controlTypeLdapServerPolicyHintsDeprecated = "1.2.840.113556.1.4.2066"
)
type (
// ldapControlServerPolicyHints implements ldap.Control
ldapControlServerPolicyHints struct {
oid string
}
)
// GetControlType implements ldap.Control
func (c *ldapControlServerPolicyHints) GetControlType() string {
return c.oid
}
// Encode implements ldap.Control
func (c *ldapControlServerPolicyHints) Encode() *ber.Packet {
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.GetControlType(), "Control Type (LDAP_SERVER_POLICY_HINTS_OID)"))
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, true, "Criticality"))
p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Policy Hints)")
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PolicyHintsRequestValue")
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 1, "Flags"))
p2.AppendChild(seq)
packet.AppendChild(p2)
return packet
}
// String implements ldap.Control
func (c *ldapControlServerPolicyHints) String() string {
return "Enforce password history policies during password set: " + c.GetControlType()
}
// ChangePassword modifies the user password of a user
func ChangePassword(userdn string, password string) error {
// requires ldaps connection
conn, err = ldap.DialTLS("tcp", "contoso.local:636", &tls.Config{
InsecureSkipVerify: false,
ServerName: "contoso.local",
})
if err != nil {
return err
}
defer conn.Close()
// PasswordModify does not work with AD
// https://github.com/go-ldap/ldap/issues/106
// request := ldap.NewPasswordModifyRequest(username, "", password)
// _, err = conn.PasswordModify(request)
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
// The password needs to be enclosed in quotes
pwdEncoded, err := utf16.NewEncoder().String(fmt.Sprintf("\"%s\"", password))
if err != nil {
return err
}
// add additional control to request if supported
controlTypes, err := getSupportedControl(conn)
if err != nil {
return err
}
control := []ldap.Control{}
for _, oid := range controlTypes {
if oid == controlTypeLdapServerPolicyHints || oid == controlTypeLdapServerPolicyHintsDeprecated {
control = append(control, &ldapControlServerPolicyHints{oid: oid})
break
}
}
passReq := ldap.NewModifyRequest(userdn, control)
passReq.Replace(ldapAttrUnicodePw, []string{pwdEncoded})
return onn.Modify(passReq)
}
// getSupportedControl retrieves supported extended control types
func getSupportedControl(conn ldap.Client) ([]string, error) {
req := ldap.NewSearchRequest("", ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", []string{"supportedControl"}, nil)
res, err := conn.Search(req)
if err != nil {
return nil, err
}
return res.Entries[0].GetAttributeValues("supportedControl"), nil
}
@project0
Copy link
Author

project0 commented Sep 28, 2018

Common errors:

LDAP Result Code 53 "Unwilling To Perform": 0000001F: SvcErr: DSID-031A12D2, problem 5003 (WILL_NOT_PERFORM)
No secure connection used (tls)

LDAP Result Code 53 "Unwilling To Perform": 0000052D: SvcErr: DSID-031A129B, problem 5003 (WILL_NOT_PERFORM)
PW in Password history

LDAP Result Code 53 "Unwilling To Perform": 0000052D: SvcErr: DSID-031A12D2, problem 5003 (WILL_NOT_PERFORM)
Unknown (policy failed?)

I use the following lines to catch them with a more user friendly message

var (
	ldapErrorMatchRegex = regexp.MustCompile(`: ([A-F0-9]+): SvcErr: ([\w-]+)`)
)
	// catch some common ldap error messages and make them human readable
	if ldap.IsErrorWithCode(err, ldap.LDAPResultUnwillingToPerform) {
		errCodes := ldapErrorMatchRegex.FindStringSubmatch(err.Error())
		if errCodes != nil {
			switch errCodes[1] {
			case "0000052D":
				err = fmt.Errorf("password does not match the policy on server (password history, complexity)")
			default:
			}
		}
           }

@idanmantin
Copy link

Hi, I got this error: LDAP Result Code 53 "Unwilling To Perform": 0000001F: SvcErr: DSID-031A12DD, problem 5003 (WILL_NOT_PERFORM), data 0
How can I solve this error?

@project0
Copy link
Author

project0 commented Jun 21, 2022

@idanmantin you need to use TLS based client connection to the server (ldaps), this happens for example with azure AD as a hard requirement.

// edit: maybe you just missed the quotes? LDAP error messages are not very informative and depends a lot on the codes.
https://gist.github.com/Project0/61c13130563cf7f595e031d54fe55aab#file-ad_password_reset-go-L75-L76
https://stackoverflow.com/questions/71407111/microsoft-active-directory-ldap-error-unwilling-to-perform-0000001f-svc

@idanmantin
Copy link

utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
pwdEncoded, err := utf16.NewEncoder().String(fmt.Sprintf("\"%s\"", newPassword))

I Working with AWS Managed Microsoft AD, using LDAP (not LDAPS), and I succeed to get list of users for example.... but I can't change user password successfully.

@project0
Copy link
Author

Then it is very likely you have to use ldaps. In any case i cannot really provide more information that what i have written above. Some policy on the server does not like how the request is made. It is very difficult to debug such issues if there is no exact 1:1 mapping of the error codes to a proper description.
I really suggest turning on LDAPs first, before digging more around.
https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_ldap_server_side.html

And if you run on AWS, you may also just use the AWS SDK: https://docs.aws.amazon.com/directoryservice/latest/devguide/API_ResetUserPassword.html

@pigletfly
Copy link

pigletfly commented Jun 29, 2022

I got LDAP Result Code 32 "No Such Object": 0000208D: NameErr: DSID-0310020A, problem 2001(NO_OBJECT), data 0. I tried to use "sAMAccountName" as useDN, but did't work as well.And I' using it without tls.

@idanmantin
Copy link

utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
pwdEncoded, err := utf16.NewEncoder().String(fmt.Sprintf("\"%s\"", newPassword))

I Working with AWS Managed Microsoft AD, using LDAP (not LDAPS), and I succeed to get list of users for example.... but I can't change user password successfully.

Update: Active Directory Require to use secure connection in order to change users passwords (Like: LDAPS, NTLM or Kerberos).

From Microsoft Docs: Change a Windows Active Directory and LDS user password through LDAP

In order to modify this attribute, the client must have a 128-bit Transport Layer Security (TLS)/Secure Socket Layer (SSL) connection to the server.

The "attribute" they are referring to is "unicodePwd".

@polin-x
Copy link

polin-x commented Jul 26, 2023

LDAP Result Code 53 "Unwilling To Perform": 0000001F: SvcErr: DSID-031A12E8, problem 5003 (WILL_NOT_PERFORM), data 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment