package ldap import ( "context" "crypto/tls" "errors" "fmt" "net" "net/url" "strings" "time" ldap "github.com/trufflesecurity/ldap-verify" regexp "github.com/wasilibs/go-re2" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb" ) type Scanner struct { detectors.DefaultMultiPartCredentialProvider } // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) func init() { ldap.DefaultTimeout = 5 * time.Second } var ( // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. uriPat = regexp.MustCompile(`\b(?i)ldaps?://[\S]+\b`) // ldap://127.0.0.1:389 // ldap://127.0.0.1 // ldap://mydomain.test // ldaps://[fe80:4049:92ff:fe44:4bd1]:5060 // ldap://[fe80::4bd1]:5060 // ldap://ds.example.com:389/dc=example,dc=com?givenName,sn,cn?sub?(uid=john.doe) usernamePat = regexp.MustCompile(detectors.PrefixRegex([]string{"user", "bind"}) + `["']([a-zA-Z=,]{4,150})["']`) passwordPat = regexp.MustCompile(detectors.PrefixRegex([]string{"pass"}) + `["']([\S]{4,48})["']`) // https://learn.microsoft.com/en-us/windows/win32/api/iads/nf-iads-iadsopendsobject-opendsobject?redirectedfrom=MSDN // I.E. Set ou = dso.OpenDSObject("LDAP://DC.business.com/OU=IT,DC=Business,DC=com", "Business\administrator", "Pa$$word01", 1) iadPat = regexp.MustCompile(`OpenDSObject\(\"(?i)(ldaps?://[\S]+)\", ?\"([\S]+)\", ?\"([\S]+)\",[ \d]+\)`) ) // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { return []string{"ldaps://", "ldap://"} } // FromData will find and optionally verify Ldap secrets in a given set of bytes. func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) // Check for matches in the URI + username + password format uriMatches := uriPat.FindAllString(dataStr, -1) for _, uri := range uriMatches { ldapURL, err := url.Parse(uri) if err != nil { continue } usernameMatches := usernamePat.FindAllStringSubmatch(dataStr, -1) for _, username := range usernameMatches { passwordMatches := passwordPat.FindAllStringSubmatch(dataStr, -1) for _, password := range passwordMatches { s1 := detectors.Result{ DetectorType: detector_typepb.DetectorType_LDAP, Raw: []byte(strings.Join([]string{ldapURL.String(), username[1], password[1]}, "\t")), SecretParts: map[string]string{ "url": ldapURL.String(), "username": username[1], "password": password[1], }, } if verify { verificationErr := verifyLDAP(ctx, username[1], password[1], ldapURL) s1.Verified = verificationErr == nil if !isErrDeterminate(verificationErr) { s1.SetVerificationError(verificationErr, password[1]) } } results = append(results, s1) } } } // Check for matches for the IAD library format iadMatches := iadPat.FindAllStringSubmatch(dataStr, -1) for _, iad := range iadMatches { uri := iad[1] username := iad[2] password := iad[3] ldapURL, err := url.Parse(uri) if err != nil { continue } s1 := detectors.Result{ DetectorType: detector_typepb.DetectorType_LDAP, Raw: []byte(strings.Join([]string{ldapURL.String(), username, password}, "\t")), SecretParts: map[string]string{ "url": ldapURL.String(), "username": username, "password": password, }, } if verify { verificationError := verifyLDAP(ctx, username, password, ldapURL) s1.Verified = verificationError == nil if !isErrDeterminate(verificationError) { s1.SetVerificationError(verificationError, password) } } results = append(results, s1) } return results, nil } func isErrDeterminate(err error) bool { var neterr *net.OpError if errors.As(err, &neterr) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { return false } return true } func verifyLDAP(ctx context.Context, username, password string, ldapURL *url.URL) error { // Tests with non-TLS, TLS, and STARTTLS uri := ldapURL.String() switch ldapURL.Scheme { case "ldap": // Non-TLS dial l, err := ldap.DialURL(uri, ldap.DialWithContext(ctx)) if err != nil { return err } defer l.Close() // Non-TLS verify err = l.BindContext(ctx, username, password) if err == nil { return nil } // STARTTLS err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) if err != nil { return err } // STARTTLS verify return l.BindContext(ctx, username, password) case "ldaps": // TLS dial l, err := ldap.DialURL( uri, ldap.DialWithContext(ctx), ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}), ) if err != nil { return err } defer l.Close() // TLS verify return l.BindContext(ctx, username, password) default: return fmt.Errorf("unknown ldap scheme %q", ldapURL.Scheme) } } func (s Scanner) Type() detector_typepb.DetectorType { return detector_typepb.DetectorType_LDAP } func (s Scanner) Description() string { return "LDAP (Lightweight Directory Access Protocol) is an open, vendor-neutral, industry standard application protocol for accessing and maintaining distributed directory information services over an Internet Protocol (IP) network." }