Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with "Server SPN target name validation" GPO #169

Closed
grawity opened this issue Mar 15, 2022 · 10 comments
Closed

Compatibility with "Server SPN target name validation" GPO #169

grawity opened this issue Mar 15, 2022 · 10 comments

Comments

@grawity
Copy link

grawity commented Mar 15, 2022

This is somewhat more of a pyspnego issue, but it involves pysmbprotocol passing it the correct information, so I ended up filing it here.

Windows servers have a Server SPN target name validation security policy available. When enabled, it causes SMB servers to reject authentication if the client claims it's connecting to a different hostname than what the server expects. With Kerberos this is already business as usual (either the client wouldn't be able to get tickets for the wrong SPN, or the server would be unable to decrypt the tickets), but the GPO extends this protection also to NTLM, in order to prevent NTLM relay attacks.

Now none of my python-smbprotocol scripts use NTLM, so this is not an issue for me, but I still decided to test it anyway and found that even when connecting to an allowed server name (i.e. where Samba's smbclient would work), python-smbprotocol nevertheless gets an "Access denied": smbprotocol.exceptions.AccessDenied: Received unexpected status from the server: A process has requested access to an object but has not been granted those access rights. (3221225506) STATUS_ACCESS_DENIED: 0xc0000022

Specifically, the difference seems to be that with Samba (which works), the MSV_AV_TARGET_NAME field in AvPairs includes the cifs/ service name like a Kerberos principal would, whereas with python-smbprotocol it does not. [MS-NLMP] seems to confirm that "ClientSuppliedTargetName" is a SPN, so the caller needs to supply the correct service name and not just the hostname.

$ cat security-blob-samba.hex | unhex | python -m spnego
[...]
                                {
                                    "AvId": "MSV_AV_TARGET_NAME (9)",
                                    "Value": "cifs/fs1.example.com"
                                },

$ cat security-blob-pysmbprotocol.hex | unhex | python -m spnego
[...]
                                {
                                    "AvId": "MSV_AV_TARGET_NAME (9)",
                                    "Value": "fs1.example.com"
                                },

Samba also sends MSV_AV_SINGLE_HOST but I don't know if it's related.

To enable strict SPN validation on a SMB server:

  • secpol.msc > Security Options > "Microsoft network server: Server SPN target name validation level"
  • Path: HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters
    Name: SmbServerNameHardeningLevel
    Value: REG_DWORD, 1 (validate target only if client sends it) or 2 (validate and require clients to send it)

The place to configure "allowed" aliases seems to be SrvAllowedServerNames REG_MULTI_SZ at the same location.

@jborean93
Copy link
Owner

Interesting, it looks like pyspnego's NTLM implementation is setting MSV_AV_TARGET_NAME to the SPN passed in from smbprotocol which will be cifs/hostname https://github.com/jborean93/pyspnego/blob/main/src/spnego/_ntlm.py#L706.

When testing this purely I see the NTLM auth message contains the SPN in the NT challenge response

NtChallengeResponse:
  ResponseType: NTLMv2
  NTProofStr: 321303F0587F894BF528994391DAB3BE
  ClientChallenge:
    RespType: 1
    HiRespType: 1
    Reserved1: 0
    Reserved2: 0
    TimeStamp: '2022-03-15T10:13:11.2261824Z'
    ChallengeFromClient: 471AE177EF44C84D
    Reserved3: 0
    AvPairs:
    - AvId: MSV_AV_NB_DOMAIN_NAME (2)
      Value: DOMAIN
    - AvId: MSV_AV_NB_COMPUTER_NAME (1)
      Value: SERVER2019
    - AvId: MSV_AV_DNS_DOMAIN_NAME (4)
      Value: domain.test
    - AvId: MSV_AV_DNS_COMPUTER_NAME (3)
      Value: SERVER2019.domain.test
    - AvId: MSV_AV_DNS_TREE_NAME (5)
      Value: domain.test
    - AvId: MSV_AV_TIMESTAMP (7)
      Value: '2022-03-15T10:13:11.2261824Z'
    - AvId: MSV_AV_TARGET_NAME (9)
      Value: cifs/server2019.domain.test
    - AvId: MSV_AV_FLAGS (6)
      Value:
        raw: 2
        flags:
        - MIC_PROVIDED (2)
    - AvId: MSV_AV_EOL (0)
      Value:
    Reserved4: 0

When testing further it looks like the problem is stemming from gss-ntlmssp which is used instead of the Python implementation as a GSSAPI mechanism if available. When running that I see the target name is just the hostname without the SPN. Looks like this has recently been reported and also fixed with gssapi/gss-ntlmssp#63. Will require a new release to make it's way through the various package repositories.

Unfortunately there is no flag to state use the builtin Python NTLM implementation over the GSSAPI one if the latter is available as it is considered to be the source implementation for Linux. In this case if you need to use NTLM with this mode you have 3 options:

  • Disable the NTLM mech by either uninstalling gss-ntlmssp or removing it from the GSSAPI mech file entries
  • Uninstall the gssapi Python package so no GSSAPI mech is used, will also disable Kerberos unfortunately
  • Manually update the NTLM mech so with the changes from Fix hostbased name gssapi/gss-ntlmssp#66

If you are able to do at least step 2 for your tests to ensure that the pyspnego NTLM implementation is all good. From what I can see it should work.

@jborean93
Copy link
Owner

Samba also sends MSV_AV_SINGLE_HOST but I don't know if it's related.

I'm somewhat surprised it sends that, the value here is somewhat undocumented but it's mostly used for loopback authentication. I don't think it really applies here.

@simo5
Copy link

simo5 commented Mar 15, 2022

Would you be able to test with gssntlssmp from the main branch where this should be fixed?

@jborean93
Copy link
Owner

Yep, was planning on building the main branch and testing that my code correctly passes the SPN to gss-ntlmssp sometime tomorrow. Will also enable that policy myself to verify the settings work with it as well.

@grawity
Copy link
Author

grawity commented Mar 15, 2022

Thanks for the information – I just tried latest gss-ntlmssp from Git, and it indeed sends the full cifs/foo.example.com SPN, but unfortunately that still doesn't work and I get the same "Access denied".

I'm guessing this is because it now also sets the UNTRUSTED_SPN_SOURCE flag in MSV_AV_FLAGS, and if I'm reading the NTLM spec right, this makes the server pretend no SPN was sent at all.

If I uninstall gss-ntlmssp and let pyspnego do things on its own, NTLM indeed works.

@simo5
Copy link

simo5 commented Mar 15, 2022

This is interesting,
@grawity could you open an issue in gssntlmssp about this problem?

I tried to be conservative and not claim anything trusted because it was unclear to me what that flag meant.
@jborean93 are you aware of a MS doc that clearly explain what it means for a name to be trusted vs untrusted?

@grawity
Copy link
Author

grawity commented Mar 15, 2022

I would guess that it means the SPN was obtained from the server itself – there's a field in "negHints" where modern systems send not_defined_in_RFC4178@please_ignore but I faintly remember that old servers, maybe Win2000 or XP, used to advertise their actual SPN there.

On the other hand, if the SPN was directly derived from the user-specified URL or UNC path, then it would make sense for it to be "trusted", following SSL/TLS logic.

@simo5
Copy link

simo5 commented Mar 15, 2022

That would seem odd, my thinking was that untrusted may be a name derived by a reverse DNS query for example... but that is one reason why I set untrusted I did not want to claim anything that I was not sure I should claim. Documentation is something I really need to be sure I can set this appropriately. And I would like to do it before making a release.

@jborean93
Copy link
Owner

@jborean93 are you aware of a MS doc that clearly explain what it means for a name to be trusted vs untrusted?

I've briefly seen it before but cannot remember how it is done through SSPI but it makes sense that having it set also still fails. Let me do some digging and I'll get back to you.

@jborean93
Copy link
Owner

Closing as per the resolution in gssapi/gss-ntlmssp#67. Have tested the proposed fix and things work just fine.

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

No branches or pull requests

3 participants