add Schannel certificate authentication via --schannel - #1277
Conversation
|
Very cool, thanks for the PR! |
NeffIsBack
left a comment
There was a problem hiding this comment.
Two thoughts:
- Since we are authenticating with a cert the DC very likely has a TLS installed as well. So imo we should probably make schannel auth the default method and use PKINIT only when requested (as this is more likely to be unsupported imo). Thoughts?
- Since we are using standard impacket ldap conn and auth please integrate it into the existing
plaintext_loginmethod.
Done, Schannel auth is now integrated into On making Schannel the default: Schannel still requires either |
But when we are doing Schannel auth we do authenticate with a certificate. How realistic is it that we have a PKI (likely ADCS) with which we can authenticate, but the Domain Controller is missing the TLS cert? Isn't that quite unlikely when having a PKI connected to the domain in the first place? I would argue that this is much more unlikely than having PKINIT support. |
Actually, that assumption can break in practice. I've hit an AD environment where My hypothesis is that the PKI was hosted on |
Signed-off-by: Azox <213314124+azoxlpf@users.noreply.github.com>
Signed-off-by: Azox <213314124+azoxlpf@users.noreply.github.com>
5461bb0 to
9bd4e8e
Compare
Okay sounds good, convinced :D One day we should probably also integrate PKINIT without UnPAC-the-hash so we can use the TGT directly. I don't think this is possible atm. |
Yeah, there's currently no real implementation of PKINIT, we're only using U2U but that's not the official implementation. I had started implementing RFC 4556 on Impacket, but it's taking a huge amount of time |
NeffIsBack
left a comment
There was a problem hiding this comment.
Some minor details left, after that we should be good. Logic looks good so far
| self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host, signing=self.auth_choice != "simple", timeout=self.args.ldap_timeout) | ||
| self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash, authenticationChoice=self.auth_choice) | ||
| authentication_choice = "external" if self.args.schannel else self.auth_choice | ||
| self.logger.info(f"Connecting to {ldap_url} using Schannel" if self.args.schannel else f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [3]") |
There was a problem hiding this comment.
Feel free to add your using Schannel info, but please leave self.baseDN and self.host in the output. That can get pretty important for debugging (especially the latter).
| if self.args.schannel: | ||
| mapped_user = self.get_ldap_username() | ||
| if mapped_user: | ||
| self.username = mapped_user |
There was a problem hiding this comment.
This looks kinda redundant to line 443, is there a specific reason we need that? Shouldn't the authenticated user always be the specified one when we are using a pfx file?
There was a problem hiding this comment.
Ah right, I remember now why I did it this way: with schannel the -u isn't used for authentication, the cert is mapped to an account server-side, so a wrong -u still succeeds and would display the wrong user. The get_ldap_username() whoami is the only way to show the account we were actually mapped to.
Example with the wrong user in -u :
There was a problem hiding this comment.
Hmm is there some way that we can extract/check the user in the cert? If possible we shouldn't allow such janky arg input. I think --pfc-cert also checks and restricts the specified user to the one in the certificate
There was a problem hiding this comment.
Hmm is there some way that we can extract/check the user in the cert? If possible we shouldn't allow such janky arg input. I think
--pfc-certalso checks and restricts the specified user to the one in the certificate
Yeah we can extract it (SAN UPN, else dNSName, else subject CN). One thing to consider though: with Schannel the mapping is done by the DC, and explicit altSecurityIdentities mappings carry no UPN at all, so we'd reject valid certs.
Also --pfx-cert doesn't check on our side either, it just puts -u in the AS-REQ cname and lets the KDC reject it.
Another option would be to stop requiring -u with --schannel and rely on the whoami result, which removes the janky input entirely. What do you think?
There was a problem hiding this comment.
Hmm okay makes sense. Yeah then let's get the user by doing the whoami. Then it is similar to --use-kcache where the user is ignored as well
Signed-off-by: Azox <213314124+azoxlpf@users.noreply.github.com>
Description
This PR adds LDAP Schannel certificate authentication to NetExec via a new
--schannelflag. When used with--pfx-cert(or--pem-cert/--pem-key), authentication is performed directly over LDAP/LDAPS using the client certificate, without going through PKINIT.This PR depends on impacket changes from fortra/impacket#2205. That PR must be merged before this feature can work.
Type of change
Insert an "x" inside the brackets for relevant items (do not delete options)
Setup guide for the review
run :
Screenshots (if appropriate):
Checklist:
Insert an "x" inside the brackets for completed and relevant items (do not delete options)
poetry run ruff check ., use--fixto automatically fix what it can)tests/e2e_commands.txtfile if necessary (new modules or features are required to be added to the e2e tests)