1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
From: "Dr. Tobias Quathamer" <toddy@debian.org>
Date: Wed, 29 Nov 2017 14:34:16 +0100
Subject: Require explicit intention for empty password.
This is normally used for unauthenticated bind, and
https://tools.ietf.org/html/rfc4513#section-5.1.2 recommends:
> Clients SHOULD disallow an empty password input to a Name/Password
> Authentication user interface
This is (mostly) a cherry-pick of 95ede12 from upstream. I've removed
the bit in ldap_test.go, which is unrelated to the security issue.
This fixes CVE-2017-14623.
https://github.com/go-ldap/ldap/commit/95ede1266b237bf8e9aa5dce0b3250e51bfefe66
---
bind.go | 80 ++++++++++++++++++++++++++++++----------------------------------
error.go | 9 ++++++++
2 files changed, 46 insertions(+), 43 deletions(-)
diff --git a/bind.go b/bind.go
index 26b3cc7..432efa7 100644
--- a/bind.go
+++ b/bind.go
@@ -7,7 +7,7 @@ package ldap
import (
"errors"
- "gopkg.in/asn1-ber.v1"
+ ber "gopkg.in/asn1-ber.v1"
)
// SimpleBindRequest represents a username/password bind operation
@@ -18,6 +18,9 @@ type SimpleBindRequest struct {
Password string
// Controls are optional controls to send with the bind request
Controls []Control
+ // AllowEmptyPassword sets whether the client allows binding with an empty password
+ // (normally used for unauthenticated bind).
+ AllowEmptyPassword bool
}
// SimpleBindResult contains the response from the server
@@ -28,9 +31,10 @@ type SimpleBindResult struct {
// NewSimpleBindRequest returns a bind request
func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest {
return &SimpleBindRequest{
- Username: username,
- Password: password,
- Controls: controls,
+ Username: username,
+ Password: password,
+ Controls: controls,
+ AllowEmptyPassword: false,
}
}
@@ -47,6 +51,10 @@ func (bindRequest *SimpleBindRequest) encode() *ber.Packet {
// SimpleBind performs the simple bind operation defined in the given request
func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) {
+ if simpleBindRequest.Password == "" && !simpleBindRequest.AllowEmptyPassword {
+ return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
+ }
+
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
encodedBindRequest := simpleBindRequest.encode()
@@ -97,47 +105,33 @@ func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResu
return result, nil
}
-// Bind performs a bind with the given username and password
+// Bind performs a bind with the given username and password.
+//
+// It does not allow unauthenticated bind (i.e. empty password). Use the UnauthenticatedBind method
+// for that.
func (l *Conn) Bind(username, password string) error {
- packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
- packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
- bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
- bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
- bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, username, "User Name"))
- bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, password, "Password"))
- packet.AppendChild(bindRequest)
-
- if l.Debug {
- ber.PrintPacket(packet)
- }
-
- msgCtx, err := l.sendMessage(packet)
- if err != nil {
- return err
- }
- defer l.finishMessage(msgCtx)
-
- packetResponse, ok := <-msgCtx.responses
- if !ok {
- return NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
- }
- packet, err = packetResponse.ReadPacket()
- l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
- if err != nil {
- return err
- }
-
- if l.Debug {
- if err := addLDAPDescriptions(packet); err != nil {
- return err
- }
- ber.PrintPacket(packet)
+ req := &SimpleBindRequest{
+ Username: username,
+ Password: password,
+ AllowEmptyPassword: false,
}
+ _, err := l.SimpleBind(req)
+ return err
+}
- resultCode, resultDescription := getLDAPResultCode(packet)
- if resultCode != 0 {
- return NewError(resultCode, errors.New(resultDescription))
+// UnauthenticatedBind performs an unauthenticated bind.
+//
+// A username may be provided for trace (e.g. logging) purpose only, but it is normally not
+// authenticated or otherwise validated by the LDAP server.
+//
+// See https://tools.ietf.org/html/rfc4513#section-5.1.2 .
+// See https://tools.ietf.org/html/rfc4513#section-6.3.1 .
+func (l *Conn) UnauthenticatedBind(username string) error {
+ req := &SimpleBindRequest{
+ Username: username,
+ Password: "",
+ AllowEmptyPassword: true,
}
-
- return nil
+ _, err := l.SimpleBind(req)
+ return err
}
diff --git a/error.go b/error.go
index ff69787..6e1277f 100644
--- a/error.go
+++ b/error.go
@@ -54,6 +54,7 @@ const (
ErrorDebugging = 203
ErrorUnexpectedMessage = 204
ErrorUnexpectedResponse = 205
+ ErrorEmptyPassword = 206
)
// LDAPResultCodeMap contains string descriptions for LDAP error codes
@@ -97,6 +98,14 @@ var LDAPResultCodeMap = map[uint8]string{
LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited",
LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs",
LDAPResultOther: "Other",
+
+ ErrorNetwork: "Network Error",
+ ErrorFilterCompile: "Filter Compile Error",
+ ErrorFilterDecompile: "Filter Decompile Error",
+ ErrorDebugging: "Debugging Error",
+ ErrorUnexpectedMessage: "Unexpected Message",
+ ErrorUnexpectedResponse: "Unexpected Response",
+ ErrorEmptyPassword: "Empty password not allowed by the client",
}
func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) {
|