Skip to content

Implement starttls#36

Open
vikerup wants to merge 3 commits into
ShutdownRepo:mainfrom
vikerup:implement-starttls
Open

Implement starttls#36
vikerup wants to merge 3 commits into
ShutdownRepo:mainfrom
vikerup:implement-starttls

Conversation

@vikerup

@vikerup vikerup commented Dec 16, 2025

Copy link
Copy Markdown

Implementation of LDAP STARTTLS. Useful if LDAPS in unavailable while DC still requires signing/TLS

Get kerberos ticket:

getTGT.py test.local/administrator:$DAPW

export KRB5CCNAME=~/administrator.ccache
pywhisker  -t demomachine\$ -a list -u administrator -k --dc-ip 100.64.5.200 -d test.local
[!] automatic bind not successful - strongerAuthRequired
pywhisker  -t demomachine\$ -a list -u administrator -k --dc-ip 100.64.5.200 -d test.local --use-starttls
[*] Searching for the target account
[*] Target user found: CN=DEMOMACHINE,CN=Computers,DC=test,DC=local
[*] Listing devices for demomachine$
[*] DeviceID: fbf8fb45-7e50-c9a8-dd82-c65676d9c082 | Creation Time (UTC): 2025-10-03 12:32:57.648661

@vikerup

vikerup commented Dec 17, 2025

Copy link
Copy Markdown
Author

pywhisker is using ldap3 which does not seem to support username+password or NTLM hash when LDAP signing is enforced:

cannatag/ldap3#889
cannatag/ldap3#1049

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds LDAP STARTTLS support to pyWhisker so it can upgrade a plaintext LDAP connection (389) to TLS when LDAPS (636) isn’t available but the DC enforces signing/TLS.

Changes:

  • Introduces --use-starttls and a new STARTTLS-based LDAP connection initializer.
  • Improves bind failure error handling for NTLM binds and Kerberos SASL bind failures with actionable guidance.
  • Updates README usage options to mention the new STARTTLS flag.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
pywhisker/pywhisker.py Adds STARTTLS connection path, wiring via --use-starttls, and improves bind error messaging.
README.md Documents the new --use-starttls CLI option.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pywhisker/pywhisker.py
Comment on lines +170 to +173
ldap_session.open()
ldap_session.start_tls()
ldap_session.bind()
ldap3_kerberos_login(ldap_session, target, username, password, logger, domain, lmhash, nthash, args.auth_key, kdcHost=args.dc_ip)

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start_tls() is called but its return value isn’t checked. In ldap3, STARTTLS negotiation can fail and return False without raising, which would cause the subsequent bind() to send credentials over an unencrypted connection. Check the result of start_tls() (and/or ldap_session.tls_started) and raise an error before binding if TLS was not successfully established.

Copilot uses AI. Check for mistakes.
Comment thread pywhisker/pywhisker.py
Comment on lines +225 to +227
return init_ldap_starttls_connection(target, ssl.PROTOCOL_TLSv1_2, args, domain, username, password, lmhash, nthash, logger)
except ldap3.core.exceptions.LDAPSocketOpenError:
return init_ldap_starttls_connection(target, ssl.PROTOCOL_TLSv1, args, domain, username, password, lmhash, nthash, logger)

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The STARTTLS TLS-version fallback only catches LDAPSocketOpenError, but STARTTLS negotiation/handshake failures typically surface as start_tls() returning False or raising a STARTTLS/TLS-related exception instead. This can prevent fallback and/or lead to confusing errors. Consider checking the start_tls() result and broadening the exception handling to include STARTTLS/TLS handshake failures (and surface a clear error) before retrying with the older TLS version.

Suggested change
return init_ldap_starttls_connection(target, ssl.PROTOCOL_TLSv1_2, args, domain, username, password, lmhash, nthash, logger)
except ldap3.core.exceptions.LDAPSocketOpenError:
return init_ldap_starttls_connection(target, ssl.PROTOCOL_TLSv1, args, domain, username, password, lmhash, nthash, logger)
return init_ldap_starttls_connection(
target,
ssl.PROTOCOL_TLSv1_2,
args,
domain,
username,
password,
lmhash,
nthash,
logger,
)
except (
ldap3.core.exceptions.LDAPSocketOpenError,
ldap3.core.exceptions.LDAPStartTLSError,
ssl.SSLError,
) as e:
if logger is not None:
logger.warning(
f"[!] STARTTLS failed with TLSv1_2 for {target}: {e}. Retrying with TLSv1."
)
try:
return init_ldap_starttls_connection(
target,
ssl.PROTOCOL_TLSv1,
args,
domain,
username,
password,
lmhash,
nthash,
logger,
)
except (
ldap3.core.exceptions.LDAPSocketOpenError,
ldap3.core.exceptions.LDAPStartTLSError,
ssl.SSLError,
) as e2:
raise Exception(
f"[ERROR] Failed to establish STARTTLS LDAP connection to {target} "
f"using both TLSv1_2 and TLSv1: {e2}"
)

Copilot uses AI. Check for mistakes.
Comment thread pywhisker/pywhisker.py
Comment on lines 922 to 925
parser.add_argument("-a", "--action", choices=['list', 'add', 'spray', 'remove', 'clear', 'info', 'export', 'import'], nargs='?', default='list', help='Action to operate on msDS-KeyCredentialLink')
parser.add_argument('--use-ldaps', action='store_true', help='Use LDAPS instead of LDAP')
parser.add_argument('--use-starttls', action='store_true', help='Use STARTTLS for LDAP connection upgrade')
parser.add_argument('--use-schannel', action='store_true', help='Use LDAP Schannel (TLS) for certificate-based authentication')

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--use-starttls, --use-ldaps, and --use-schannel can currently be provided together; the code will silently prioritize one path (e.g., STARTTLS wins over LDAPS). This is ambiguous for users and can mask misconfiguration. Consider making these options mutually exclusive in argparse (or validating after parsing) and erroring out when more than one transport mode is selected.

Copilot uses AI. Check for mistakes.
Comment thread README.md
-a [{list,add,spray,remove,clear,info,export,import}], --action [{list,add,spray,remove,clear,info,export,import}]
Action to operate on msDS-KeyCredentialLink
--use-ldaps Use LDAPS instead of LDAP
--use-starttls Use STARTTLS for LDAP connection upgrade

Copilot AI Feb 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README’s detailed options list was updated with --use-starttls, but the usage synopsis earlier in the same code block still omits this flag. Please update the synopsis line as well so the README output matches the current CLI.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants