-
Notifications
You must be signed in to change notification settings - Fork 101
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
401 Unauthorized on v1.2.0, Works Fine on v1.1.0 #136
Comments
Same here, authenticating to SharePoint 2019 on-prem. Updating to 1.2.0 breaks the authentication code, giving me an HTTP 401 and reverting to 1.1.0 fixes the issue. |
1.2.0 replaced ntlm_auth with spnego: v1.1.0...v1.2.0#diff-3590900fb531e3b46542fdf34352cfdd58edf42d2fca824e5d518ab3375e6e4aL9 I suspect they are doing things differently under the hood. |
Finding the same issue in my project |
The pyspnego library should be support all the same features as the old dependency An example of all three messages in an exchange are shown below (I used Wireshark to get these values). A not in that the nonces will have different values per exchange and
Ultimately this issue is not something I can replicate, more information will need to be provided to compare the differences between the old and new way to find out why this might be happening for you. Negotiate Message$ python -m spnego --format yaml --token TlRMTVNTUAABAAAAN4II4gAAAAAoAAAAAAAAACgAAAAACQEAAAAADw== MessageType: NEGOTIATE_MESSAGE (1)
Data:
NegotiateFlags:
raw: 3792208439
flags:
- NTLMSSP_NEGOTIATE_56 (2147483648)
- NTLMSSP_NEGOTIATE_KEY_EXCH (1073741824)
- NTLMSSP_NEGOTIATE_128 (536870912)
- NTLMSSP_NEGOTIATE_VERSION (33554432)
- NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY (524288)
- NTLMSSP_NEGOTIATE_ALWAYS_SIGN (32768)
- NTLMSSP_NEGOTIATE_NTLM (512)
- NTLMSSP_NEGOTIATE_SEAL (32)
- NTLMSSP_NEGOTIATE_SIGN (16)
- NTLMSSP_REQUEST_TARGET (4)
- NTLMSSP_NEGOTIATE_OEM (2)
- NTLMSSP_NEGOTIATE_UNICODE (1)
DomainNameFields:
Len: 0
MaxLen: 0
BufferOffset: 40
WorkstationFields:
Len: 0
MaxLen: 0
BufferOffset: 40
Version:
Major: 0
Minor: 9
Build: 1
Reserved: '000000'
NTLMRevision: 15
Payload:
DomainName:
Workstation:
RawData: 4E544C4D5353500001000000378208E200000000280000000000000028000000000901000000000F Challenge Message$ python -m spnego --format yaml --token TlRMTVNTUAACAAAADAAMADgAAAA1goniQInKuHLN48QAAAAAAAAAAJwAnABEAAAACgB8TwAAAA9EAE8ATQBBAEkATgACAAwARABPAE0AQQBJAE4AAQAUAFMARQBSAFYARQBSADIAMAAyADIABAAWAGQAbwBtAGEAaQBuAC4AdABlAHMAdAADACwAUwBFAFIAVgBFAFIAMgAwADIAMgAuAGQAbwBtAGEAaQBuAC4AdABlAHMAdAAFABYAZABvAG0AYQBpAG4ALgB0AGUAcwB0AAcACACdH8ukxsTZAQAAAAA= MessageType: CHALLENGE_MESSAGE (2)
Data:
TargetNameFields:
Len: 12
MaxLen: 12
BufferOffset: 56
NegotiateFlags:
raw: 3800662581
flags:
- NTLMSSP_NEGOTIATE_56 (2147483648)
- NTLMSSP_NEGOTIATE_KEY_EXCH (1073741824)
- NTLMSSP_NEGOTIATE_128 (536870912)
- NTLMSSP_NEGOTIATE_VERSION (33554432)
- NTLMSSP_NEGOTIATE_TARGET_INFO (8388608)
- NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY (524288)
- NTLMSSP_TARGET_TYPE_DOMAIN (65536)
- NTLMSSP_NEGOTIATE_ALWAYS_SIGN (32768)
- NTLMSSP_NEGOTIATE_NTLM (512)
- NTLMSSP_NEGOTIATE_SEAL (32)
- NTLMSSP_NEGOTIATE_SIGN (16)
- NTLMSSP_REQUEST_TARGET (4)
- NTLMSSP_NEGOTIATE_UNICODE (1)
ServerChallenge: 4089CAB872CDE3C4
Reserved: '0000000000000000'
TargetInfoFields:
Len: 156
MaxLen: 156
BufferOffset: 68
Version:
Major: 10
Minor: 0
Build: 20348
Reserved: '000000'
NTLMRevision: 15
Payload:
TargetName: DOMAIN
TargetInfo:
- AvId: MSV_AV_NB_DOMAIN_NAME (2)
Value: DOMAIN
- AvId: MSV_AV_NB_COMPUTER_NAME (1)
Value: SERVER2022
- AvId: MSV_AV_DNS_DOMAIN_NAME (4)
Value: domain.test
- AvId: MSV_AV_DNS_COMPUTER_NAME (3)
Value: SERVER2022.domain.test
- AvId: MSV_AV_DNS_TREE_NAME (5)
Value: domain.test
- AvId: MSV_AV_TIMESTAMP (7)
Value: '2023-08-01T22:22:23.1484317Z'
- AvId: MSV_AV_EOL (0)
Value:
RawData:
4E544C4D53535000020000000C000C0038000000358289E24089CAB872CDE3C400000000000000009C009C00440000000A007C4F0000000F44004F004D00410049004E0002000C0044004F004D00410049004E000100140053004500520056004500520032003000320032000400160064006F006D00610069006E002E00740065007300740003002C0053004500520056004500520032003000320032002E0064006F006D00610069006E002E0074006500730074000500160064006F006D00610069006E002E007400650073007400070008009D1FCBA4C6C4D90100000000 Authenticate Message$ python -m spnego --format yaml --token TlRMTVNTUAADAAAAGAAYAFgAAAD4APgAcAAAAAAAAABoAQAANAA0AGgBAAAaABoAnAEAABAAEAC2AQAANYKJ4gAJAQAAAAAPLYyl/bMH17nMmNwANMCtpwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMHtVmMbCoV1TZuSGbteLeoBAQAAAAAAAJ0fy6TGxNkBh5sLMRRmtb4AAAAAAgAMAEQATwBNAEEASQBOAAEAFABTAEUAUgBWAEUAUgAyADAAMgAyAAQAFgBkAG8AbQBhAGkAbgAuAHQAZQBzAHQAAwAsAFMARQBSAFYARQBSADIAMAAyADIALgBkAG8AbQBhAGkAbgAuAHQAZQBzAHQABQAWAGQAbwBtAGEAaQBuAC4AdABlAHMAdAAHAAgAnR/LpMbE2QEJACAAaABvAHMAdAAvAHUAbgBzAHAAZQBjAGkAZgBpAGUAZAAGAAQAAgAAAAAAAAAAAAAAdgBhAGcAcgBhAG4AdAAtAGQAbwBtAGEAaQBuAEAARABPAE0AQQBJAE4ALgBUAEUAUwBUAEoAQgBPAFIARQBBAE4ALQBMAEkATgBVAFgA5ucL7LuCRwB+Lnly16t1DA== MessageType: AUTHENTICATE_MESSAGE (3)
Data:
LmChallengeResponseFields:
Len: 24
MaxLen: 24
BufferOffset: 88
NtChallengeResponseFields:
Len: 248
MaxLen: 248
BufferOffset: 112
DomainNameFields:
Len: 0
MaxLen: 0
BufferOffset: 360
UserNameFields:
Len: 52
MaxLen: 52
BufferOffset: 360
WorkstationFields:
Len: 26
MaxLen: 26
BufferOffset: 412
EncryptedRandomSessionKeyFields:
Len: 16
MaxLen: 16
BufferOffset: 438
NegotiateFlags:
raw: 3800662581
flags:
- NTLMSSP_NEGOTIATE_56 (2147483648)
- NTLMSSP_NEGOTIATE_KEY_EXCH (1073741824)
- NTLMSSP_NEGOTIATE_128 (536870912)
- NTLMSSP_NEGOTIATE_VERSION (33554432)
- NTLMSSP_NEGOTIATE_TARGET_INFO (8388608)
- NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY (524288)
- NTLMSSP_TARGET_TYPE_DOMAIN (65536)
- NTLMSSP_NEGOTIATE_ALWAYS_SIGN (32768)
- NTLMSSP_NEGOTIATE_NTLM (512)
- NTLMSSP_NEGOTIATE_SEAL (32)
- NTLMSSP_NEGOTIATE_SIGN (16)
- NTLMSSP_REQUEST_TARGET (4)
- NTLMSSP_NEGOTIATE_UNICODE (1)
Version:
Major: 0
Minor: 9
Build: 1
Reserved: '000000'
NTLMRevision: 15
MIC: 2D8CA5FDB307D7B9CC98DC0034C0ADA7
Payload:
LmChallengeResponse:
ResponseType: LMv2
LMProofStr: '00000000000000000000000000000000'
ChallengeFromClient: '0000000000000000'
NtChallengeResponse:
ResponseType: NTLMv2
NTProofStr: C1ED56631B0A85754D9B9219BB5E2DEA
ClientChallenge:
RespType: 1
HiRespType: 1
Reserved1: 0
Reserved2: 0
TimeStamp: '2023-08-01T22:22:23.1484317Z'
ChallengeFromClient: 879B0B311466B5BE
Reserved3: 0
AvPairs:
- AvId: MSV_AV_NB_DOMAIN_NAME (2)
Value: DOMAIN
- AvId: MSV_AV_NB_COMPUTER_NAME (1)
Value: SERVER2022
- AvId: MSV_AV_DNS_DOMAIN_NAME (4)
Value: domain.test
- AvId: MSV_AV_DNS_COMPUTER_NAME (3)
Value: SERVER2022.domain.test
- AvId: MSV_AV_DNS_TREE_NAME (5)
Value: domain.test
- AvId: MSV_AV_TIMESTAMP (7)
Value: '2023-08-01T22:22:23.1484317Z'
- AvId: MSV_AV_TARGET_NAME (9)
Value: host/unspecified
- AvId: MSV_AV_FLAGS (6)
Value:
raw: 2
flags:
- MIC_PROVIDED (2)
- AvId: MSV_AV_EOL (0)
Value:
Reserved4: 0
DomainName:
UserName: [email protected]
Workstation: JBOREAN-LINUX
EncryptedRandomSessionKey: E6E70BECBB8247007E2E7972D7AB750C
SessionKey: Failed to derive
RawData:
4E544C4D53535000030000001800180058000000F800F80070000000000000006801000034003400680100001A001A009C01000010001000B6010000358289E2000901000000000F2D8CA5FDB307D7B9CC98DC0034C0ADA7000000000000000000000000000000000000000000000000C1ED56631B0A85754D9B9219BB5E2DEA01010000000000009D1FCBA4C6C4D901879B0B311466B5BE0000000002000C0044004F004D00410049004E000100140053004500520056004500520032003000320032000400160064006F006D00610069006E002E00740065007300740003002C0053004500520056004500520032003000320032002E0064006F006D00610069006E002E0074006500730074000500160064006F006D00610069006E002E007400650073007400070008009D1FCBA4C6C4D9010900200068006F00730074002F0075006E0073007000650063006900660069006500640006000400020000000000000000000000760061006700720061006E0074002D0064006F006D00610069006E00400044004F004D00410049004E002E0054004500530054004A0042004F005200450041004E002D004C0049004E0055005800E6E70BECBB8247007E2E7972D7AB750C |
Changing this line https://github.com/jborean93/pyspnego/blob/main/src/spnego/_ntlm.py#L201 - return to_text(workstation) if workstation else None
+ return None fixes it for me. I did a few more tests and apparently this is a length thing - e.g. this works as well (len = 20): - return to_text(workstation) if workstation else None
+ return '12345678901234567890' but this fails: - return to_text(workstation) if workstation else None
+ return '123456789012345678901' What I put there doesn't seem to matter - any string with len <= 20 works. This: https://serverfault.com/a/585824/371201 quotes this: http://technet.microsoft.com/en-us/library/ee617211.aspx which says:
Emphasis mine. Note that I've found services on my end that do work with len > 20, so this issue seems to be server-dependent. Apparently 1.1.0 is sending empty workstation, so that's why it's working. If others can apply the same thing (just temporarily update the mentioned line in Enough NTLM for me for tonight. Hopefully this was helpful, enjoy your weekend! |
@cebaa thanks for the investigation, are you able to share the information on the server that is rejecting the NTLM token when the workstation is more than 20 characters? I've found that the Windows hosts I've tested with are just fine but knowing more info about the servers which do reject it might help figure out the next best steps. Just as an FYI for compatibility with gss-ntlmssp the pyspnego code does use the environment variable |
Thanks @jborean93
It's a custom java server. I'm not certain, but AFAIK it's using jespa for NTLM authentication.
Doh, I should have read the code just above. Even easier, but yeah that fixes as well and I feel it's enough of a escape hatch for this particular case. |
Thanks for sharing, considering Windows and all the other platforms I've tested with seem to be fine with a longer workstation name I would be reluctant from trimming it automatically in the pyspnego code. The env var seems like a decent workaround to me for people who are affected by this. |
Hi, @jborean93 v110.txt - success Update:
I don't know if this is a correct fix but hopefully it might provide more context to you. Update 2: |
Update 3:
This
The "unspecified" string is specified as default value for hostname parameter of the client class (spnego/auth.py). |
Thanks for the investigation, it should be simple to add the target hostname of the request to the SPN value used there but it'll only be valid if the actual HTTP target host is valid. If you use an IP then that will stay as the IP.
When using SSPI actual with a localhost target, Windows can now verify the origin of the request as SSPI includes the Single_Host_Data structure in the AV pairs to indicate the origin. With this Windows can do things like verify the origin was elevated or not amongst other things. By switching over to the pure NTLM provider it will no longer include this structure so Windows thinks it is remote. Overall SSPI is really the true mechanism here and offers more features that the pure NTLM provider does not; cached credentials, etc. While we can do more to provide a more accurate target/SPN this will only be as good as the target hostname used in the original request. |
The PR #137 does a few things to try and make it more like past releases. It will:
This ensures it'll continue to support using the Windows current context if none are specified but solve the loopback authentication problem that previously wasn't an issue. |
I'm running into the same issue in my project. |
@jborean93 I have tried your changes from #137 and they still did not resolve the 401 error. I am running this from a python:3.8-slim container image. The only thing that I could get working was |
@airpaio the PR makes it so requests-ntlm will always use the pure Python NTLM code rather than SSPI on Windows. This has connotations around how authentication is done when talking back to localhost but doesn't deal with the hostname problem mentioned elsewhere in this thread. If you are finding the workstation is a problem with your NTLM service you can set the env var |
Hmm, I couldn't get it to work even with setting For what it's worth, I was able to monkey patch spnego directly from my calling code instead of having to change the spnego code directly in site-packages. Here's what worked for me (using from requests_ntlm import HttpNtlmAuth
# mokey patch _get_workstation
import spnego._ntlm
spnego._ntlm._get_workstation = lambda: None
...
# write code that uses HttpNtlmAuth This will work for my use case because the vendor's software that we are interfacing with is getting rid of NTLM auth in a new release soon. |
@jborean93: This solves the issue in my case. Will this PR be merged to main anytime soon? |
A script that utilizes requests-ntlm.HttpNtlmAuth to authenticate with a website had been working fine until I upgraded the package to the latest version 1.2.0 then it started throwing 401 unauthorized exception. Downgrading to v1.1.0 without any changes to the code resolved the issue.
Note: the script runs behind an HTTP corporate proxy if it makes any difference.
The text was updated successfully, but these errors were encountered: